Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/usr/bin/env python2
2# SPDX-License-Identifier: GPL-2.0
3# exported-sql-viewer.py: view data from sql database
4# Copyright (c) 2014-2018, Intel Corporation.
5
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
8# scripts for details.
9#
10# Following on from the example in the export scripts, a
11# call-graph can be displayed for the pt_example database like this:
12#
13# python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14#
15# Note that for PostgreSQL, this script supports connecting to remote databases
16# by setting hostname, port, username, password, and dbname e.g.
17#
18# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19#
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:
23#
24# Call Graph: pt_example
25# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
26# v- ls
27# v- 2638:2638
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
38#
39# Points to note:
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
48
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
53# libxed.so:
54# git clone https://github.com/intelxed/mbuild.git mbuild
55# git clone https://github.com/intelxed/xed
56# cd xed
57# ./mfile.py --share
58# sudo ./mfile.py --prefix=/usr/local install
59# sudo ldconfig
60#
61# Example report:
62#
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])
90
91from __future__ import print_function
92
93import sys
94import weakref
95import threading
96import string
97try:
98 # Python2
99 import cPickle as pickle
100 # size of pickled integer big enough for record size
101 glb_nsz = 8
102except ImportError:
103 import pickle
104 glb_nsz = 16
105import re
106import os
107from PySide.QtCore import *
108from PySide.QtGui import *
109from PySide.QtSql import *
110pyside_version_1 = True
111from decimal import *
112from ctypes import *
113from multiprocessing import Process, Array, Value, Event
114
115# xrange is range in Python3
116try:
117 xrange
118except NameError:
119 xrange = range
120
121def printerr(*args, **keyword_args):
122 print(*args, file=sys.stderr, **keyword_args)
123
124# Data formatting helpers
125
126def tohex(ip):
127 if ip < 0:
128 ip += 1 << 64
129 return "%x" % ip
130
131def offstr(offset):
132 if offset:
133 return "+0x%x" % offset
134 return ""
135
136def dsoname(name):
137 if name == "[kernel.kallsyms]":
138 return "[kernel]"
139 return name
140
141def findnth(s, sub, n, offs=0):
142 pos = s.find(sub)
143 if pos < 0:
144 return pos
145 if n <= 1:
146 return offs + pos
147 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
148
149# Percent to one decimal place
150
151def PercentToOneDP(n, d):
152 if not d:
153 return "0.0"
154 x = (n * Decimal(100)) / d
155 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
156
157# Helper for queries that must not fail
158
159def QueryExec(query, stmt):
160 ret = query.exec_(stmt)
161 if not ret:
162 raise Exception("Query failed: " + query.lastError().text())
163
164# Background thread
165
166class Thread(QThread):
167
168 done = Signal(object)
169
170 def __init__(self, task, param=None, parent=None):
171 super(Thread, self).__init__(parent)
172 self.task = task
173 self.param = param
174
175 def run(self):
176 while True:
177 if self.param is None:
178 done, result = self.task()
179 else:
180 done, result = self.task(self.param)
181 self.done.emit(result)
182 if done:
183 break
184
185# Tree data model
186
187class TreeModel(QAbstractItemModel):
188
189 def __init__(self, glb, parent=None):
190 super(TreeModel, self).__init__(parent)
191 self.glb = glb
192 self.root = self.GetRoot()
193 self.last_row_read = 0
194
195 def Item(self, parent):
196 if parent.isValid():
197 return parent.internalPointer()
198 else:
199 return self.root
200
201 def rowCount(self, parent):
202 result = self.Item(parent).childCount()
203 if result < 0:
204 result = 0
205 self.dataChanged.emit(parent, parent)
206 return result
207
208 def hasChildren(self, parent):
209 return self.Item(parent).hasChildren()
210
211 def headerData(self, section, orientation, role):
212 if role == Qt.TextAlignmentRole:
213 return self.columnAlignment(section)
214 if role != Qt.DisplayRole:
215 return None
216 if orientation != Qt.Horizontal:
217 return None
218 return self.columnHeader(section)
219
220 def parent(self, child):
221 child_item = child.internalPointer()
222 if child_item is self.root:
223 return QModelIndex()
224 parent_item = child_item.getParentItem()
225 return self.createIndex(parent_item.getRow(), 0, parent_item)
226
227 def index(self, row, column, parent):
228 child_item = self.Item(parent).getChildItem(row)
229 return self.createIndex(row, column, child_item)
230
231 def DisplayData(self, item, index):
232 return item.getData(index.column())
233
234 def FetchIfNeeded(self, row):
235 if row > self.last_row_read:
236 self.last_row_read = row
237 if row + 10 >= self.root.child_count:
238 self.fetcher.Fetch(glb_chunk_sz)
239
240 def columnAlignment(self, column):
241 return Qt.AlignLeft
242
243 def columnFont(self, column):
244 return None
245
246 def data(self, index, role):
247 if role == Qt.TextAlignmentRole:
248 return self.columnAlignment(index.column())
249 if role == Qt.FontRole:
250 return self.columnFont(index.column())
251 if role != Qt.DisplayRole:
252 return None
253 item = index.internalPointer()
254 return self.DisplayData(item, index)
255
256# Table data model
257
258class TableModel(QAbstractTableModel):
259
260 def __init__(self, parent=None):
261 super(TableModel, self).__init__(parent)
262 self.child_count = 0
263 self.child_items = []
264 self.last_row_read = 0
265
266 def Item(self, parent):
267 if parent.isValid():
268 return parent.internalPointer()
269 else:
270 return self
271
272 def rowCount(self, parent):
273 return self.child_count
274
275 def headerData(self, section, orientation, role):
276 if role == Qt.TextAlignmentRole:
277 return self.columnAlignment(section)
278 if role != Qt.DisplayRole:
279 return None
280 if orientation != Qt.Horizontal:
281 return None
282 return self.columnHeader(section)
283
284 def index(self, row, column, parent):
285 return self.createIndex(row, column, self.child_items[row])
286
287 def DisplayData(self, item, index):
288 return item.getData(index.column())
289
290 def FetchIfNeeded(self, row):
291 if row > self.last_row_read:
292 self.last_row_read = row
293 if row + 10 >= self.child_count:
294 self.fetcher.Fetch(glb_chunk_sz)
295
296 def columnAlignment(self, column):
297 return Qt.AlignLeft
298
299 def columnFont(self, column):
300 return None
301
302 def data(self, index, role):
303 if role == Qt.TextAlignmentRole:
304 return self.columnAlignment(index.column())
305 if role == Qt.FontRole:
306 return self.columnFont(index.column())
307 if role != Qt.DisplayRole:
308 return None
309 item = index.internalPointer()
310 return self.DisplayData(item, index)
311
312# Model cache
313
314model_cache = weakref.WeakValueDictionary()
315model_cache_lock = threading.Lock()
316
317def LookupCreateModel(model_name, create_fn):
318 model_cache_lock.acquire()
319 try:
320 model = model_cache[model_name]
321 except:
322 model = None
323 if model is None:
324 model = create_fn()
325 model_cache[model_name] = model
326 model_cache_lock.release()
327 return model
328
329# Find bar
330
331class FindBar():
332
333 def __init__(self, parent, finder, is_reg_expr=False):
334 self.finder = finder
335 self.context = []
336 self.last_value = None
337 self.last_pattern = None
338
339 label = QLabel("Find:")
340 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
341
342 self.textbox = QComboBox()
343 self.textbox.setEditable(True)
344 self.textbox.currentIndexChanged.connect(self.ValueChanged)
345
346 self.progress = QProgressBar()
347 self.progress.setRange(0, 0)
348 self.progress.hide()
349
350 if is_reg_expr:
351 self.pattern = QCheckBox("Regular Expression")
352 else:
353 self.pattern = QCheckBox("Pattern")
354 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
355
356 self.next_button = QToolButton()
357 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
358 self.next_button.released.connect(lambda: self.NextPrev(1))
359
360 self.prev_button = QToolButton()
361 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
362 self.prev_button.released.connect(lambda: self.NextPrev(-1))
363
364 self.close_button = QToolButton()
365 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
366 self.close_button.released.connect(self.Deactivate)
367
368 self.hbox = QHBoxLayout()
369 self.hbox.setContentsMargins(0, 0, 0, 0)
370
371 self.hbox.addWidget(label)
372 self.hbox.addWidget(self.textbox)
373 self.hbox.addWidget(self.progress)
374 self.hbox.addWidget(self.pattern)
375 self.hbox.addWidget(self.next_button)
376 self.hbox.addWidget(self.prev_button)
377 self.hbox.addWidget(self.close_button)
378
379 self.bar = QWidget()
380 self.bar.setLayout(self.hbox);
381 self.bar.hide()
382
383 def Widget(self):
384 return self.bar
385
386 def Activate(self):
387 self.bar.show()
388 self.textbox.setFocus()
389
390 def Deactivate(self):
391 self.bar.hide()
392
393 def Busy(self):
394 self.textbox.setEnabled(False)
395 self.pattern.hide()
396 self.next_button.hide()
397 self.prev_button.hide()
398 self.progress.show()
399
400 def Idle(self):
401 self.textbox.setEnabled(True)
402 self.progress.hide()
403 self.pattern.show()
404 self.next_button.show()
405 self.prev_button.show()
406
407 def Find(self, direction):
408 value = self.textbox.currentText()
409 pattern = self.pattern.isChecked()
410 self.last_value = value
411 self.last_pattern = pattern
412 self.finder.Find(value, direction, pattern, self.context)
413
414 def ValueChanged(self):
415 value = self.textbox.currentText()
416 pattern = self.pattern.isChecked()
417 index = self.textbox.currentIndex()
418 data = self.textbox.itemData(index)
419 # Store the pattern in the combo box to keep it with the text value
420 if data == None:
421 self.textbox.setItemData(index, pattern)
422 else:
423 self.pattern.setChecked(data)
424 self.Find(0)
425
426 def NextPrev(self, direction):
427 value = self.textbox.currentText()
428 pattern = self.pattern.isChecked()
429 if value != self.last_value:
430 index = self.textbox.findText(value)
431 # Allow for a button press before the value has been added to the combo box
432 if index < 0:
433 index = self.textbox.count()
434 self.textbox.addItem(value, pattern)
435 self.textbox.setCurrentIndex(index)
436 return
437 else:
438 self.textbox.setItemData(index, pattern)
439 elif pattern != self.last_pattern:
440 # Keep the pattern recorded in the combo box up to date
441 index = self.textbox.currentIndex()
442 self.textbox.setItemData(index, pattern)
443 self.Find(direction)
444
445 def NotFound(self):
446 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
447
448# Context-sensitive call graph data model item base
449
450class CallGraphLevelItemBase(object):
451
452 def __init__(self, glb, row, parent_item):
453 self.glb = glb
454 self.row = row
455 self.parent_item = parent_item
456 self.query_done = False;
457 self.child_count = 0
458 self.child_items = []
459 if parent_item:
460 self.level = parent_item.level + 1
461 else:
462 self.level = 0
463
464 def getChildItem(self, row):
465 return self.child_items[row]
466
467 def getParentItem(self):
468 return self.parent_item
469
470 def getRow(self):
471 return self.row
472
473 def childCount(self):
474 if not self.query_done:
475 self.Select()
476 if not self.child_count:
477 return -1
478 return self.child_count
479
480 def hasChildren(self):
481 if not self.query_done:
482 return True
483 return self.child_count > 0
484
485 def getData(self, column):
486 return self.data[column]
487
488# Context-sensitive call graph data model level 2+ item base
489
490class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
491
492 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
493 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
494 self.comm_id = comm_id
495 self.thread_id = thread_id
496 self.call_path_id = call_path_id
497 self.branch_count = branch_count
498 self.time = time
499
500 def Select(self):
501 self.query_done = True;
502 query = QSqlQuery(self.glb.db)
503 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
504 " FROM calls"
505 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
506 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
507 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
508 " WHERE parent_call_path_id = " + str(self.call_path_id) +
509 " AND comm_id = " + str(self.comm_id) +
510 " AND thread_id = " + str(self.thread_id) +
511 " GROUP BY call_path_id, name, short_name"
512 " ORDER BY call_path_id")
513 while query.next():
514 child_item = CallGraphLevelThreeItem(self.glb, 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)), int(query.value(5)), self)
515 self.child_items.append(child_item)
516 self.child_count += 1
517
518# Context-sensitive call graph data model level three item
519
520class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
521
522 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
523 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
524 dso = dsoname(dso)
525 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
526 self.dbid = call_path_id
527
528# Context-sensitive call graph data model level two item
529
530class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
531
532 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
533 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
534 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
535 self.dbid = thread_id
536
537 def Select(self):
538 super(CallGraphLevelTwoItem, self).Select()
539 for child_item in self.child_items:
540 self.time += child_item.time
541 self.branch_count += child_item.branch_count
542 for child_item in self.child_items:
543 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
544 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
545
546# Context-sensitive call graph data model level one item
547
548class CallGraphLevelOneItem(CallGraphLevelItemBase):
549
550 def __init__(self, glb, row, comm_id, comm, parent_item):
551 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
552 self.data = [comm, "", "", "", "", "", ""]
553 self.dbid = comm_id
554
555 def Select(self):
556 self.query_done = True;
557 query = QSqlQuery(self.glb.db)
558 QueryExec(query, "SELECT thread_id, pid, tid"
559 " FROM comm_threads"
560 " INNER JOIN threads ON thread_id = threads.id"
561 " WHERE comm_id = " + str(self.dbid))
562 while query.next():
563 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
564 self.child_items.append(child_item)
565 self.child_count += 1
566
567# Context-sensitive call graph data model root item
568
569class CallGraphRootItem(CallGraphLevelItemBase):
570
571 def __init__(self, glb):
572 super(CallGraphRootItem, self).__init__(glb, 0, None)
573 self.dbid = 0
574 self.query_done = True;
575 query = QSqlQuery(glb.db)
576 QueryExec(query, "SELECT id, comm FROM comms")
577 while query.next():
578 if not query.value(0):
579 continue
580 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
581 self.child_items.append(child_item)
582 self.child_count += 1
583
584# Context-sensitive call graph data model base
585
586class CallGraphModelBase(TreeModel):
587
588 def __init__(self, glb, parent=None):
589 super(CallGraphModelBase, self).__init__(glb, parent)
590
591 def FindSelect(self, value, pattern, query):
592 if pattern:
593 # postgresql and sqlite pattern patching differences:
594 # postgresql LIKE is case sensitive but sqlite LIKE is not
595 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
596 # postgresql supports ILIKE which is case insensitive
597 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
598 if not self.glb.dbref.is_sqlite3:
599 # Escape % and _
600 s = value.replace("%", "\%")
601 s = s.replace("_", "\_")
602 # Translate * and ? into SQL LIKE pattern characters % and _
603 trans = string.maketrans("*?", "%_")
604 match = " LIKE '" + str(s).translate(trans) + "'"
605 else:
606 match = " GLOB '" + str(value) + "'"
607 else:
608 match = " = '" + str(value) + "'"
609 self.DoFindSelect(query, match)
610
611 def Found(self, query, found):
612 if found:
613 return self.FindPath(query)
614 return []
615
616 def FindValue(self, value, pattern, query, last_value, last_pattern):
617 if last_value == value and pattern == last_pattern:
618 found = query.first()
619 else:
620 self.FindSelect(value, pattern, query)
621 found = query.next()
622 return self.Found(query, found)
623
624 def FindNext(self, query):
625 found = query.next()
626 if not found:
627 found = query.first()
628 return self.Found(query, found)
629
630 def FindPrev(self, query):
631 found = query.previous()
632 if not found:
633 found = query.last()
634 return self.Found(query, found)
635
636 def FindThread(self, c):
637 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
638 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
639 elif c.direction > 0:
640 ids = self.FindNext(c.query)
641 else:
642 ids = self.FindPrev(c.query)
643 return (True, ids)
644
645 def Find(self, value, direction, pattern, context, callback):
646 class Context():
647 def __init__(self, *x):
648 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
649 def Update(self, *x):
650 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
651 if len(context):
652 context[0].Update(value, direction, pattern)
653 else:
654 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
655 # Use a thread so the UI is not blocked during the SELECT
656 thread = Thread(self.FindThread, context[0])
657 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
658 thread.start()
659
660 def FindDone(self, thread, callback, ids):
661 callback(ids)
662
663# Context-sensitive call graph data model
664
665class CallGraphModel(CallGraphModelBase):
666
667 def __init__(self, glb, parent=None):
668 super(CallGraphModel, self).__init__(glb, parent)
669
670 def GetRoot(self):
671 return CallGraphRootItem(self.glb)
672
673 def columnCount(self, parent=None):
674 return 7
675
676 def columnHeader(self, column):
677 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
678 return headers[column]
679
680 def columnAlignment(self, column):
681 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
682 return alignment[column]
683
684 def DoFindSelect(self, query, match):
685 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
686 " FROM calls"
687 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
688 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
689 " WHERE symbols.name" + match +
690 " GROUP BY comm_id, thread_id, call_path_id"
691 " ORDER BY comm_id, thread_id, call_path_id")
692
693 def FindPath(self, query):
694 # Turn the query result into a list of ids that the tree view can walk
695 # to open the tree at the right place.
696 ids = []
697 parent_id = query.value(0)
698 while parent_id:
699 ids.insert(0, parent_id)
700 q2 = QSqlQuery(self.glb.db)
701 QueryExec(q2, "SELECT parent_id"
702 " FROM call_paths"
703 " WHERE id = " + str(parent_id))
704 if not q2.next():
705 break
706 parent_id = q2.value(0)
707 # The call path root is not used
708 if ids[0] == 1:
709 del ids[0]
710 ids.insert(0, query.value(2))
711 ids.insert(0, query.value(1))
712 return ids
713
714# Call tree data model level 2+ item base
715
716class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
717
718 def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
719 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
720 self.comm_id = comm_id
721 self.thread_id = thread_id
722 self.calls_id = calls_id
723 self.branch_count = branch_count
724 self.time = time
725
726 def Select(self):
727 self.query_done = True;
728 if self.calls_id == 0:
729 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
730 else:
731 comm_thread = ""
732 query = QSqlQuery(self.glb.db)
733 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
734 " FROM calls"
735 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
736 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
737 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
738 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
739 " ORDER BY call_time, calls.id")
740 while query.next():
741 child_item = CallTreeLevelThreeItem(self.glb, 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)), int(query.value(5)), self)
742 self.child_items.append(child_item)
743 self.child_count += 1
744
745# Call tree data model level three item
746
747class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
748
749 def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
750 super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
751 dso = dsoname(dso)
752 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
753 self.dbid = calls_id
754
755# Call tree data model level two item
756
757class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
758
759 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
760 super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
761 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
762 self.dbid = thread_id
763
764 def Select(self):
765 super(CallTreeLevelTwoItem, self).Select()
766 for child_item in self.child_items:
767 self.time += child_item.time
768 self.branch_count += child_item.branch_count
769 for child_item in self.child_items:
770 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
771 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
772
773# Call tree data model level one item
774
775class CallTreeLevelOneItem(CallGraphLevelItemBase):
776
777 def __init__(self, glb, row, comm_id, comm, parent_item):
778 super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
779 self.data = [comm, "", "", "", "", "", ""]
780 self.dbid = comm_id
781
782 def Select(self):
783 self.query_done = True;
784 query = QSqlQuery(self.glb.db)
785 QueryExec(query, "SELECT thread_id, pid, tid"
786 " FROM comm_threads"
787 " INNER JOIN threads ON thread_id = threads.id"
788 " WHERE comm_id = " + str(self.dbid))
789 while query.next():
790 child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
791 self.child_items.append(child_item)
792 self.child_count += 1
793
794# Call tree data model root item
795
796class CallTreeRootItem(CallGraphLevelItemBase):
797
798 def __init__(self, glb):
799 super(CallTreeRootItem, self).__init__(glb, 0, None)
800 self.dbid = 0
801 self.query_done = True;
802 query = QSqlQuery(glb.db)
803 QueryExec(query, "SELECT id, comm FROM comms")
804 while query.next():
805 if not query.value(0):
806 continue
807 child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
808 self.child_items.append(child_item)
809 self.child_count += 1
810
811# Call Tree data model
812
813class CallTreeModel(CallGraphModelBase):
814
815 def __init__(self, glb, parent=None):
816 super(CallTreeModel, self).__init__(glb, parent)
817
818 def GetRoot(self):
819 return CallTreeRootItem(self.glb)
820
821 def columnCount(self, parent=None):
822 return 7
823
824 def columnHeader(self, column):
825 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
826 return headers[column]
827
828 def columnAlignment(self, column):
829 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
830 return alignment[column]
831
832 def DoFindSelect(self, query, match):
833 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
834 " FROM calls"
835 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
836 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
837 " WHERE symbols.name" + match +
838 " ORDER BY comm_id, thread_id, call_time, calls.id")
839
840 def FindPath(self, query):
841 # Turn the query result into a list of ids that the tree view can walk
842 # to open the tree at the right place.
843 ids = []
844 parent_id = query.value(0)
845 while parent_id:
846 ids.insert(0, parent_id)
847 q2 = QSqlQuery(self.glb.db)
848 QueryExec(q2, "SELECT parent_id"
849 " FROM calls"
850 " WHERE id = " + str(parent_id))
851 if not q2.next():
852 break
853 parent_id = q2.value(0)
854 ids.insert(0, query.value(2))
855 ids.insert(0, query.value(1))
856 return ids
857
858# Vertical widget layout
859
860class VBox():
861
862 def __init__(self, w1, w2, w3=None):
863 self.vbox = QWidget()
864 self.vbox.setLayout(QVBoxLayout());
865
866 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
867
868 self.vbox.layout().addWidget(w1)
869 self.vbox.layout().addWidget(w2)
870 if w3:
871 self.vbox.layout().addWidget(w3)
872
873 def Widget(self):
874 return self.vbox
875
876# Tree window base
877
878class TreeWindowBase(QMdiSubWindow):
879
880 def __init__(self, parent=None):
881 super(TreeWindowBase, self).__init__(parent)
882
883 self.model = None
884 self.find_bar = None
885
886 self.view = QTreeView()
887 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
888 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
889
890 self.context_menu = TreeContextMenu(self.view)
891
892 def DisplayFound(self, ids):
893 if not len(ids):
894 return False
895 parent = QModelIndex()
896 for dbid in ids:
897 found = False
898 n = self.model.rowCount(parent)
899 for row in xrange(n):
900 child = self.model.index(row, 0, parent)
901 if child.internalPointer().dbid == dbid:
902 found = True
903 self.view.setCurrentIndex(child)
904 parent = child
905 break
906 if not found:
907 break
908 return found
909
910 def Find(self, value, direction, pattern, context):
911 self.view.setFocus()
912 self.find_bar.Busy()
913 self.model.Find(value, direction, pattern, context, self.FindDone)
914
915 def FindDone(self, ids):
916 found = True
917 if not self.DisplayFound(ids):
918 found = False
919 self.find_bar.Idle()
920 if not found:
921 self.find_bar.NotFound()
922
923
924# Context-sensitive call graph window
925
926class CallGraphWindow(TreeWindowBase):
927
928 def __init__(self, glb, parent=None):
929 super(CallGraphWindow, self).__init__(parent)
930
931 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
932
933 self.view.setModel(self.model)
934
935 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
936 self.view.setColumnWidth(c, w)
937
938 self.find_bar = FindBar(self, self)
939
940 self.vbox = VBox(self.view, self.find_bar.Widget())
941
942 self.setWidget(self.vbox.Widget())
943
944 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
945
946# Call tree window
947
948class CallTreeWindow(TreeWindowBase):
949
950 def __init__(self, glb, parent=None):
951 super(CallTreeWindow, self).__init__(parent)
952
953 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
954
955 self.view.setModel(self.model)
956
957 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
958 self.view.setColumnWidth(c, w)
959
960 self.find_bar = FindBar(self, self)
961
962 self.vbox = VBox(self.view, self.find_bar.Widget())
963
964 self.setWidget(self.vbox.Widget())
965
966 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
967
968# Child data item finder
969
970class ChildDataItemFinder():
971
972 def __init__(self, root):
973 self.root = root
974 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
975 self.rows = []
976 self.pos = 0
977
978 def FindSelect(self):
979 self.rows = []
980 if self.pattern:
981 pattern = re.compile(self.value)
982 for child in self.root.child_items:
983 for column_data in child.data:
984 if re.search(pattern, str(column_data)) is not None:
985 self.rows.append(child.row)
986 break
987 else:
988 for child in self.root.child_items:
989 for column_data in child.data:
990 if self.value in str(column_data):
991 self.rows.append(child.row)
992 break
993
994 def FindValue(self):
995 self.pos = 0
996 if self.last_value != self.value or self.pattern != self.last_pattern:
997 self.FindSelect()
998 if not len(self.rows):
999 return -1
1000 return self.rows[self.pos]
1001
1002 def FindThread(self):
1003 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1004 row = self.FindValue()
1005 elif len(self.rows):
1006 if self.direction > 0:
1007 self.pos += 1
1008 if self.pos >= len(self.rows):
1009 self.pos = 0
1010 else:
1011 self.pos -= 1
1012 if self.pos < 0:
1013 self.pos = len(self.rows) - 1
1014 row = self.rows[self.pos]
1015 else:
1016 row = -1
1017 return (True, row)
1018
1019 def Find(self, value, direction, pattern, context, callback):
1020 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1021 # Use a thread so the UI is not blocked
1022 thread = Thread(self.FindThread)
1023 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1024 thread.start()
1025
1026 def FindDone(self, thread, callback, row):
1027 callback(row)
1028
1029# Number of database records to fetch in one go
1030
1031glb_chunk_sz = 10000
1032
1033# Background process for SQL data fetcher
1034
1035class SQLFetcherProcess():
1036
1037 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1038 # Need a unique connection name
1039 conn_name = "SQLFetcher" + str(os.getpid())
1040 self.db, dbname = dbref.Open(conn_name)
1041 self.sql = sql
1042 self.buffer = buffer
1043 self.head = head
1044 self.tail = tail
1045 self.fetch_count = fetch_count
1046 self.fetching_done = fetching_done
1047 self.process_target = process_target
1048 self.wait_event = wait_event
1049 self.fetched_event = fetched_event
1050 self.prep = prep
1051 self.query = QSqlQuery(self.db)
1052 self.query_limit = 0 if "$$last_id$$" in sql else 2
1053 self.last_id = -1
1054 self.fetched = 0
1055 self.more = True
1056 self.local_head = self.head.value
1057 self.local_tail = self.tail.value
1058
1059 def Select(self):
1060 if self.query_limit:
1061 if self.query_limit == 1:
1062 return
1063 self.query_limit -= 1
1064 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1065 QueryExec(self.query, stmt)
1066
1067 def Next(self):
1068 if not self.query.next():
1069 self.Select()
1070 if not self.query.next():
1071 return None
1072 self.last_id = self.query.value(0)
1073 return self.prep(self.query)
1074
1075 def WaitForTarget(self):
1076 while True:
1077 self.wait_event.clear()
1078 target = self.process_target.value
1079 if target > self.fetched or target < 0:
1080 break
1081 self.wait_event.wait()
1082 return target
1083
1084 def HasSpace(self, sz):
1085 if self.local_tail <= self.local_head:
1086 space = len(self.buffer) - self.local_head
1087 if space > sz:
1088 return True
1089 if space >= glb_nsz:
1090 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1091 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1092 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1093 self.local_head = 0
1094 if self.local_tail - self.local_head > sz:
1095 return True
1096 return False
1097
1098 def WaitForSpace(self, sz):
1099 if self.HasSpace(sz):
1100 return
1101 while True:
1102 self.wait_event.clear()
1103 self.local_tail = self.tail.value
1104 if self.HasSpace(sz):
1105 return
1106 self.wait_event.wait()
1107
1108 def AddToBuffer(self, obj):
1109 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1110 n = len(d)
1111 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1112 sz = n + glb_nsz
1113 self.WaitForSpace(sz)
1114 pos = self.local_head
1115 self.buffer[pos : pos + len(nd)] = nd
1116 self.buffer[pos + glb_nsz : pos + sz] = d
1117 self.local_head += sz
1118
1119 def FetchBatch(self, batch_size):
1120 fetched = 0
1121 while batch_size > fetched:
1122 obj = self.Next()
1123 if obj is None:
1124 self.more = False
1125 break
1126 self.AddToBuffer(obj)
1127 fetched += 1
1128 if fetched:
1129 self.fetched += fetched
1130 with self.fetch_count.get_lock():
1131 self.fetch_count.value += fetched
1132 self.head.value = self.local_head
1133 self.fetched_event.set()
1134
1135 def Run(self):
1136 while self.more:
1137 target = self.WaitForTarget()
1138 if target < 0:
1139 break
1140 batch_size = min(glb_chunk_sz, target - self.fetched)
1141 self.FetchBatch(batch_size)
1142 self.fetching_done.value = True
1143 self.fetched_event.set()
1144
1145def SQLFetcherFn(*x):
1146 process = SQLFetcherProcess(*x)
1147 process.Run()
1148
1149# SQL data fetcher
1150
1151class SQLFetcher(QObject):
1152
1153 done = Signal(object)
1154
1155 def __init__(self, glb, sql, prep, process_data, parent=None):
1156 super(SQLFetcher, self).__init__(parent)
1157 self.process_data = process_data
1158 self.more = True
1159 self.target = 0
1160 self.last_target = 0
1161 self.fetched = 0
1162 self.buffer_size = 16 * 1024 * 1024
1163 self.buffer = Array(c_char, self.buffer_size, lock=False)
1164 self.head = Value(c_longlong)
1165 self.tail = Value(c_longlong)
1166 self.local_tail = 0
1167 self.fetch_count = Value(c_longlong)
1168 self.fetching_done = Value(c_bool)
1169 self.last_count = 0
1170 self.process_target = Value(c_longlong)
1171 self.wait_event = Event()
1172 self.fetched_event = Event()
1173 glb.AddInstanceToShutdownOnExit(self)
1174 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))
1175 self.process.start()
1176 self.thread = Thread(self.Thread)
1177 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1178 self.thread.start()
1179
1180 def Shutdown(self):
1181 # Tell the thread and process to exit
1182 self.process_target.value = -1
1183 self.wait_event.set()
1184 self.more = False
1185 self.fetching_done.value = True
1186 self.fetched_event.set()
1187
1188 def Thread(self):
1189 if not self.more:
1190 return True, 0
1191 while True:
1192 self.fetched_event.clear()
1193 fetch_count = self.fetch_count.value
1194 if fetch_count != self.last_count:
1195 break
1196 if self.fetching_done.value:
1197 self.more = False
1198 return True, 0
1199 self.fetched_event.wait()
1200 count = fetch_count - self.last_count
1201 self.last_count = fetch_count
1202 self.fetched += count
1203 return False, count
1204
1205 def Fetch(self, nr):
1206 if not self.more:
1207 # -1 inidcates there are no more
1208 return -1
1209 result = self.fetched
1210 extra = result + nr - self.target
1211 if extra > 0:
1212 self.target += extra
1213 # process_target < 0 indicates shutting down
1214 if self.process_target.value >= 0:
1215 self.process_target.value = self.target
1216 self.wait_event.set()
1217 return result
1218
1219 def RemoveFromBuffer(self):
1220 pos = self.local_tail
1221 if len(self.buffer) - pos < glb_nsz:
1222 pos = 0
1223 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1224 if n == 0:
1225 pos = 0
1226 n = pickle.loads(self.buffer[0 : glb_nsz])
1227 pos += glb_nsz
1228 obj = pickle.loads(self.buffer[pos : pos + n])
1229 self.local_tail = pos + n
1230 return obj
1231
1232 def ProcessData(self, count):
1233 for i in xrange(count):
1234 obj = self.RemoveFromBuffer()
1235 self.process_data(obj)
1236 self.tail.value = self.local_tail
1237 self.wait_event.set()
1238 self.done.emit(count)
1239
1240# Fetch more records bar
1241
1242class FetchMoreRecordsBar():
1243
1244 def __init__(self, model, parent):
1245 self.model = model
1246
1247 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1248 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1249
1250 self.fetch_count = QSpinBox()
1251 self.fetch_count.setRange(1, 1000000)
1252 self.fetch_count.setValue(10)
1253 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1254
1255 self.fetch = QPushButton("Go!")
1256 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1257 self.fetch.released.connect(self.FetchMoreRecords)
1258
1259 self.progress = QProgressBar()
1260 self.progress.setRange(0, 100)
1261 self.progress.hide()
1262
1263 self.done_label = QLabel("All records fetched")
1264 self.done_label.hide()
1265
1266 self.spacer = QLabel("")
1267
1268 self.close_button = QToolButton()
1269 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1270 self.close_button.released.connect(self.Deactivate)
1271
1272 self.hbox = QHBoxLayout()
1273 self.hbox.setContentsMargins(0, 0, 0, 0)
1274
1275 self.hbox.addWidget(self.label)
1276 self.hbox.addWidget(self.fetch_count)
1277 self.hbox.addWidget(self.fetch)
1278 self.hbox.addWidget(self.spacer)
1279 self.hbox.addWidget(self.progress)
1280 self.hbox.addWidget(self.done_label)
1281 self.hbox.addWidget(self.close_button)
1282
1283 self.bar = QWidget()
1284 self.bar.setLayout(self.hbox);
1285 self.bar.show()
1286
1287 self.in_progress = False
1288 self.model.progress.connect(self.Progress)
1289
1290 self.done = False
1291
1292 if not model.HasMoreRecords():
1293 self.Done()
1294
1295 def Widget(self):
1296 return self.bar
1297
1298 def Activate(self):
1299 self.bar.show()
1300 self.fetch.setFocus()
1301
1302 def Deactivate(self):
1303 self.bar.hide()
1304
1305 def Enable(self, enable):
1306 self.fetch.setEnabled(enable)
1307 self.fetch_count.setEnabled(enable)
1308
1309 def Busy(self):
1310 self.Enable(False)
1311 self.fetch.hide()
1312 self.spacer.hide()
1313 self.progress.show()
1314
1315 def Idle(self):
1316 self.in_progress = False
1317 self.Enable(True)
1318 self.progress.hide()
1319 self.fetch.show()
1320 self.spacer.show()
1321
1322 def Target(self):
1323 return self.fetch_count.value() * glb_chunk_sz
1324
1325 def Done(self):
1326 self.done = True
1327 self.Idle()
1328 self.label.hide()
1329 self.fetch_count.hide()
1330 self.fetch.hide()
1331 self.spacer.hide()
1332 self.done_label.show()
1333
1334 def Progress(self, count):
1335 if self.in_progress:
1336 if count:
1337 percent = ((count - self.start) * 100) / self.Target()
1338 if percent >= 100:
1339 self.Idle()
1340 else:
1341 self.progress.setValue(percent)
1342 if not count:
1343 # Count value of zero means no more records
1344 self.Done()
1345
1346 def FetchMoreRecords(self):
1347 if self.done:
1348 return
1349 self.progress.setValue(0)
1350 self.Busy()
1351 self.in_progress = True
1352 self.start = self.model.FetchMoreRecords(self.Target())
1353
1354# Brance data model level two item
1355
1356class BranchLevelTwoItem():
1357
1358 def __init__(self, row, text, parent_item):
1359 self.row = row
1360 self.parent_item = parent_item
1361 self.data = [""] * 8
1362 self.data[7] = text
1363 self.level = 2
1364
1365 def getParentItem(self):
1366 return self.parent_item
1367
1368 def getRow(self):
1369 return self.row
1370
1371 def childCount(self):
1372 return 0
1373
1374 def hasChildren(self):
1375 return False
1376
1377 def getData(self, column):
1378 return self.data[column]
1379
1380# Brance data model level one item
1381
1382class BranchLevelOneItem():
1383
1384 def __init__(self, glb, row, data, parent_item):
1385 self.glb = glb
1386 self.row = row
1387 self.parent_item = parent_item
1388 self.child_count = 0
1389 self.child_items = []
1390 self.data = data[1:]
1391 self.dbid = data[0]
1392 self.level = 1
1393 self.query_done = False
1394
1395 def getChildItem(self, row):
1396 return self.child_items[row]
1397
1398 def getParentItem(self):
1399 return self.parent_item
1400
1401 def getRow(self):
1402 return self.row
1403
1404 def Select(self):
1405 self.query_done = True
1406
1407 if not self.glb.have_disassembler:
1408 return
1409
1410 query = QSqlQuery(self.glb.db)
1411
1412 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1413 " FROM samples"
1414 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1415 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1416 " WHERE samples.id = " + str(self.dbid))
1417 if not query.next():
1418 return
1419 cpu = query.value(0)
1420 dso = query.value(1)
1421 sym = query.value(2)
1422 if dso == 0 or sym == 0:
1423 return
1424 off = query.value(3)
1425 short_name = query.value(4)
1426 long_name = query.value(5)
1427 build_id = query.value(6)
1428 sym_start = query.value(7)
1429 ip = query.value(8)
1430
1431 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1432 " FROM samples"
1433 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1434 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1435 " ORDER BY samples.id"
1436 " LIMIT 1")
1437 if not query.next():
1438 return
1439 if query.value(0) != dso:
1440 # Cannot disassemble from one dso to another
1441 return
1442 bsym = query.value(1)
1443 boff = query.value(2)
1444 bsym_start = query.value(3)
1445 if bsym == 0:
1446 return
1447 tot = bsym_start + boff + 1 - sym_start - off
1448 if tot <= 0 or tot > 16384:
1449 return
1450
1451 inst = self.glb.disassembler.Instruction()
1452 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1453 if not f:
1454 return
1455 mode = 0 if Is64Bit(f) else 1
1456 self.glb.disassembler.SetMode(inst, mode)
1457
1458 buf_sz = tot + 16
1459 buf = create_string_buffer(tot + 16)
1460 f.seek(sym_start + off)
1461 buf.value = f.read(buf_sz)
1462 buf_ptr = addressof(buf)
1463 i = 0
1464 while tot > 0:
1465 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1466 if cnt:
1467 byte_str = tohex(ip).rjust(16)
1468 for k in xrange(cnt):
1469 byte_str += " %02x" % ord(buf[i])
1470 i += 1
1471 while k < 15:
1472 byte_str += " "
1473 k += 1
1474 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1475 self.child_count += 1
1476 else:
1477 return
1478 buf_ptr += cnt
1479 tot -= cnt
1480 buf_sz -= cnt
1481 ip += cnt
1482
1483 def childCount(self):
1484 if not self.query_done:
1485 self.Select()
1486 if not self.child_count:
1487 return -1
1488 return self.child_count
1489
1490 def hasChildren(self):
1491 if not self.query_done:
1492 return True
1493 return self.child_count > 0
1494
1495 def getData(self, column):
1496 return self.data[column]
1497
1498# Brance data model root item
1499
1500class BranchRootItem():
1501
1502 def __init__(self):
1503 self.child_count = 0
1504 self.child_items = []
1505 self.level = 0
1506
1507 def getChildItem(self, row):
1508 return self.child_items[row]
1509
1510 def getParentItem(self):
1511 return None
1512
1513 def getRow(self):
1514 return 0
1515
1516 def childCount(self):
1517 return self.child_count
1518
1519 def hasChildren(self):
1520 return self.child_count > 0
1521
1522 def getData(self, column):
1523 return ""
1524
1525# Branch data preparation
1526
1527def BranchDataPrep(query):
1528 data = []
1529 for i in xrange(0, 8):
1530 data.append(query.value(i))
1531 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1532 " (" + dsoname(query.value(11)) + ")" + " -> " +
1533 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1534 " (" + dsoname(query.value(15)) + ")")
1535 return data
1536
1537def BranchDataPrepWA(query):
1538 data = []
1539 data.append(query.value(0))
1540 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1541 data.append("{:>19}".format(query.value(1)))
1542 for i in xrange(2, 8):
1543 data.append(query.value(i))
1544 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1545 " (" + dsoname(query.value(11)) + ")" + " -> " +
1546 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1547 " (" + dsoname(query.value(15)) + ")")
1548 return data
1549
1550# Branch data model
1551
1552class BranchModel(TreeModel):
1553
1554 progress = Signal(object)
1555
1556 def __init__(self, glb, event_id, where_clause, parent=None):
1557 super(BranchModel, self).__init__(glb, parent)
1558 self.event_id = event_id
1559 self.more = True
1560 self.populated = 0
1561 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1562 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1563 " ip, symbols.name, sym_offset, dsos.short_name,"
1564 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1565 " FROM samples"
1566 " INNER JOIN comms ON comm_id = comms.id"
1567 " INNER JOIN threads ON thread_id = threads.id"
1568 " INNER JOIN branch_types ON branch_type = branch_types.id"
1569 " INNER JOIN symbols ON symbol_id = symbols.id"
1570 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1571 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1572 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1573 " WHERE samples.id > $$last_id$$" + where_clause +
1574 " AND evsel_id = " + str(self.event_id) +
1575 " ORDER BY samples.id"
1576 " LIMIT " + str(glb_chunk_sz))
1577 if pyside_version_1 and sys.version_info[0] == 3:
1578 prep = BranchDataPrepWA
1579 else:
1580 prep = BranchDataPrep
1581 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1582 self.fetcher.done.connect(self.Update)
1583 self.fetcher.Fetch(glb_chunk_sz)
1584
1585 def GetRoot(self):
1586 return BranchRootItem()
1587
1588 def columnCount(self, parent=None):
1589 return 8
1590
1591 def columnHeader(self, column):
1592 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1593
1594 def columnFont(self, column):
1595 if column != 7:
1596 return None
1597 return QFont("Monospace")
1598
1599 def DisplayData(self, item, index):
1600 if item.level == 1:
1601 self.FetchIfNeeded(item.row)
1602 return item.getData(index.column())
1603
1604 def AddSample(self, data):
1605 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1606 self.root.child_items.append(child)
1607 self.populated += 1
1608
1609 def Update(self, fetched):
1610 if not fetched:
1611 self.more = False
1612 self.progress.emit(0)
1613 child_count = self.root.child_count
1614 count = self.populated - child_count
1615 if count > 0:
1616 parent = QModelIndex()
1617 self.beginInsertRows(parent, child_count, child_count + count - 1)
1618 self.insertRows(child_count, count, parent)
1619 self.root.child_count += count
1620 self.endInsertRows()
1621 self.progress.emit(self.root.child_count)
1622
1623 def FetchMoreRecords(self, count):
1624 current = self.root.child_count
1625 if self.more:
1626 self.fetcher.Fetch(count)
1627 else:
1628 self.progress.emit(0)
1629 return current
1630
1631 def HasMoreRecords(self):
1632 return self.more
1633
1634# Report Variables
1635
1636class ReportVars():
1637
1638 def __init__(self, name = "", where_clause = "", limit = ""):
1639 self.name = name
1640 self.where_clause = where_clause
1641 self.limit = limit
1642
1643 def UniqueId(self):
1644 return str(self.where_clause + ";" + self.limit)
1645
1646# Branch window
1647
1648class BranchWindow(QMdiSubWindow):
1649
1650 def __init__(self, glb, event_id, report_vars, parent=None):
1651 super(BranchWindow, self).__init__(parent)
1652
1653 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1654
1655 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1656
1657 self.view = QTreeView()
1658 self.view.setUniformRowHeights(True)
1659 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1660 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1661 self.view.setModel(self.model)
1662
1663 self.ResizeColumnsToContents()
1664
1665 self.context_menu = TreeContextMenu(self.view)
1666
1667 self.find_bar = FindBar(self, self, True)
1668
1669 self.finder = ChildDataItemFinder(self.model.root)
1670
1671 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1672
1673 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1674
1675 self.setWidget(self.vbox.Widget())
1676
1677 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1678
1679 def ResizeColumnToContents(self, column, n):
1680 # Using the view's resizeColumnToContents() here is extrememly slow
1681 # so implement a crude alternative
1682 mm = "MM" if column else "MMMM"
1683 font = self.view.font()
1684 metrics = QFontMetrics(font)
1685 max = 0
1686 for row in xrange(n):
1687 val = self.model.root.child_items[row].data[column]
1688 len = metrics.width(str(val) + mm)
1689 max = len if len > max else max
1690 val = self.model.columnHeader(column)
1691 len = metrics.width(str(val) + mm)
1692 max = len if len > max else max
1693 self.view.setColumnWidth(column, max)
1694
1695 def ResizeColumnsToContents(self):
1696 n = min(self.model.root.child_count, 100)
1697 if n < 1:
1698 # No data yet, so connect a signal to notify when there is
1699 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1700 return
1701 columns = self.model.columnCount()
1702 for i in xrange(columns):
1703 self.ResizeColumnToContents(i, n)
1704
1705 def UpdateColumnWidths(self, *x):
1706 # This only needs to be done once, so disconnect the signal now
1707 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1708 self.ResizeColumnsToContents()
1709
1710 def Find(self, value, direction, pattern, context):
1711 self.view.setFocus()
1712 self.find_bar.Busy()
1713 self.finder.Find(value, direction, pattern, context, self.FindDone)
1714
1715 def FindDone(self, row):
1716 self.find_bar.Idle()
1717 if row >= 0:
1718 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1719 else:
1720 self.find_bar.NotFound()
1721
1722# Line edit data item
1723
1724class LineEditDataItem(object):
1725
1726 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1727 self.glb = glb
1728 self.label = label
1729 self.placeholder_text = placeholder_text
1730 self.parent = parent
1731 self.id = id
1732
1733 self.value = default
1734
1735 self.widget = QLineEdit(default)
1736 self.widget.editingFinished.connect(self.Validate)
1737 self.widget.textChanged.connect(self.Invalidate)
1738 self.red = False
1739 self.error = ""
1740 self.validated = True
1741
1742 if placeholder_text:
1743 self.widget.setPlaceholderText(placeholder_text)
1744
1745 def TurnTextRed(self):
1746 if not self.red:
1747 palette = QPalette()
1748 palette.setColor(QPalette.Text,Qt.red)
1749 self.widget.setPalette(palette)
1750 self.red = True
1751
1752 def TurnTextNormal(self):
1753 if self.red:
1754 palette = QPalette()
1755 self.widget.setPalette(palette)
1756 self.red = False
1757
1758 def InvalidValue(self, value):
1759 self.value = ""
1760 self.TurnTextRed()
1761 self.error = self.label + " invalid value '" + value + "'"
1762 self.parent.ShowMessage(self.error)
1763
1764 def Invalidate(self):
1765 self.validated = False
1766
1767 def DoValidate(self, input_string):
1768 self.value = input_string.strip()
1769
1770 def Validate(self):
1771 self.validated = True
1772 self.error = ""
1773 self.TurnTextNormal()
1774 self.parent.ClearMessage()
1775 input_string = self.widget.text()
1776 if not len(input_string.strip()):
1777 self.value = ""
1778 return
1779 self.DoValidate(input_string)
1780
1781 def IsValid(self):
1782 if not self.validated:
1783 self.Validate()
1784 if len(self.error):
1785 self.parent.ShowMessage(self.error)
1786 return False
1787 return True
1788
1789 def IsNumber(self, value):
1790 try:
1791 x = int(value)
1792 except:
1793 x = 0
1794 return str(x) == value
1795
1796# Non-negative integer ranges dialog data item
1797
1798class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1799
1800 def __init__(self, glb, label, placeholder_text, column_name, parent):
1801 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1802
1803 self.column_name = column_name
1804
1805 def DoValidate(self, input_string):
1806 singles = []
1807 ranges = []
1808 for value in [x.strip() for x in input_string.split(",")]:
1809 if "-" in value:
1810 vrange = value.split("-")
1811 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1812 return self.InvalidValue(value)
1813 ranges.append(vrange)
1814 else:
1815 if not self.IsNumber(value):
1816 return self.InvalidValue(value)
1817 singles.append(value)
1818 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1819 if len(singles):
1820 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1821 self.value = " OR ".join(ranges)
1822
1823# Positive integer dialog data item
1824
1825class PositiveIntegerDataItem(LineEditDataItem):
1826
1827 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1828 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1829
1830 def DoValidate(self, input_string):
1831 if not self.IsNumber(input_string.strip()):
1832 return self.InvalidValue(input_string)
1833 value = int(input_string.strip())
1834 if value <= 0:
1835 return self.InvalidValue(input_string)
1836 self.value = str(value)
1837
1838# Dialog data item converted and validated using a SQL table
1839
1840class SQLTableDataItem(LineEditDataItem):
1841
1842 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1843 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1844
1845 self.table_name = table_name
1846 self.match_column = match_column
1847 self.column_name1 = column_name1
1848 self.column_name2 = column_name2
1849
1850 def ValueToIds(self, value):
1851 ids = []
1852 query = QSqlQuery(self.glb.db)
1853 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1854 ret = query.exec_(stmt)
1855 if ret:
1856 while query.next():
1857 ids.append(str(query.value(0)))
1858 return ids
1859
1860 def DoValidate(self, input_string):
1861 all_ids = []
1862 for value in [x.strip() for x in input_string.split(",")]:
1863 ids = self.ValueToIds(value)
1864 if len(ids):
1865 all_ids.extend(ids)
1866 else:
1867 return self.InvalidValue(value)
1868 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1869 if self.column_name2:
1870 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1871
1872# Sample time ranges dialog data item converted and validated using 'samples' SQL table
1873
1874class SampleTimeRangesDataItem(LineEditDataItem):
1875
1876 def __init__(self, glb, label, placeholder_text, column_name, parent):
1877 self.column_name = column_name
1878
1879 self.last_id = 0
1880 self.first_time = 0
1881 self.last_time = 2 ** 64
1882
1883 query = QSqlQuery(glb.db)
1884 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1885 if query.next():
1886 self.last_id = int(query.value(0))
1887 self.last_time = int(query.value(1))
1888 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1889 if query.next():
1890 self.first_time = int(query.value(0))
1891 if placeholder_text:
1892 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1893
1894 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1895
1896 def IdBetween(self, query, lower_id, higher_id, order):
1897 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1898 if query.next():
1899 return True, int(query.value(0))
1900 else:
1901 return False, 0
1902
1903 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1904 query = QSqlQuery(self.glb.db)
1905 while True:
1906 next_id = int((lower_id + higher_id) / 2)
1907 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1908 if not query.next():
1909 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1910 if not ok:
1911 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1912 if not ok:
1913 return str(higher_id)
1914 next_id = dbid
1915 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1916 next_time = int(query.value(0))
1917 if get_floor:
1918 if target_time > next_time:
1919 lower_id = next_id
1920 else:
1921 higher_id = next_id
1922 if higher_id <= lower_id + 1:
1923 return str(higher_id)
1924 else:
1925 if target_time >= next_time:
1926 lower_id = next_id
1927 else:
1928 higher_id = next_id
1929 if higher_id <= lower_id + 1:
1930 return str(lower_id)
1931
1932 def ConvertRelativeTime(self, val):
1933 mult = 1
1934 suffix = val[-2:]
1935 if suffix == "ms":
1936 mult = 1000000
1937 elif suffix == "us":
1938 mult = 1000
1939 elif suffix == "ns":
1940 mult = 1
1941 else:
1942 return val
1943 val = val[:-2].strip()
1944 if not self.IsNumber(val):
1945 return val
1946 val = int(val) * mult
1947 if val >= 0:
1948 val += self.first_time
1949 else:
1950 val += self.last_time
1951 return str(val)
1952
1953 def ConvertTimeRange(self, vrange):
1954 if vrange[0] == "":
1955 vrange[0] = str(self.first_time)
1956 if vrange[1] == "":
1957 vrange[1] = str(self.last_time)
1958 vrange[0] = self.ConvertRelativeTime(vrange[0])
1959 vrange[1] = self.ConvertRelativeTime(vrange[1])
1960 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1961 return False
1962 beg_range = max(int(vrange[0]), self.first_time)
1963 end_range = min(int(vrange[1]), self.last_time)
1964 if beg_range > self.last_time or end_range < self.first_time:
1965 return False
1966 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1967 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1968 return True
1969
1970 def AddTimeRange(self, value, ranges):
1971 n = value.count("-")
1972 if n == 1:
1973 pass
1974 elif n == 2:
1975 if value.split("-")[1].strip() == "":
1976 n = 1
1977 elif n == 3:
1978 n = 2
1979 else:
1980 return False
1981 pos = findnth(value, "-", n)
1982 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1983 if self.ConvertTimeRange(vrange):
1984 ranges.append(vrange)
1985 return True
1986 return False
1987
1988 def DoValidate(self, input_string):
1989 ranges = []
1990 for value in [x.strip() for x in input_string.split(",")]:
1991 if not self.AddTimeRange(value, ranges):
1992 return self.InvalidValue(value)
1993 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1994 self.value = " OR ".join(ranges)
1995
1996# Report Dialog Base
1997
1998class ReportDialogBase(QDialog):
1999
2000 def __init__(self, glb, title, items, partial, parent=None):
2001 super(ReportDialogBase, self).__init__(parent)
2002
2003 self.glb = glb
2004
2005 self.report_vars = ReportVars()
2006
2007 self.setWindowTitle(title)
2008 self.setMinimumWidth(600)
2009
2010 self.data_items = [x(glb, self) for x in items]
2011
2012 self.partial = partial
2013
2014 self.grid = QGridLayout()
2015
2016 for row in xrange(len(self.data_items)):
2017 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2018 self.grid.addWidget(self.data_items[row].widget, row, 1)
2019
2020 self.status = QLabel()
2021
2022 self.ok_button = QPushButton("Ok", self)
2023 self.ok_button.setDefault(True)
2024 self.ok_button.released.connect(self.Ok)
2025 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2026
2027 self.cancel_button = QPushButton("Cancel", self)
2028 self.cancel_button.released.connect(self.reject)
2029 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2030
2031 self.hbox = QHBoxLayout()
2032 #self.hbox.addStretch()
2033 self.hbox.addWidget(self.status)
2034 self.hbox.addWidget(self.ok_button)
2035 self.hbox.addWidget(self.cancel_button)
2036
2037 self.vbox = QVBoxLayout()
2038 self.vbox.addLayout(self.grid)
2039 self.vbox.addLayout(self.hbox)
2040
2041 self.setLayout(self.vbox);
2042
2043 def Ok(self):
2044 vars = self.report_vars
2045 for d in self.data_items:
2046 if d.id == "REPORTNAME":
2047 vars.name = d.value
2048 if not vars.name:
2049 self.ShowMessage("Report name is required")
2050 return
2051 for d in self.data_items:
2052 if not d.IsValid():
2053 return
2054 for d in self.data_items[1:]:
2055 if d.id == "LIMIT":
2056 vars.limit = d.value
2057 elif len(d.value):
2058 if len(vars.where_clause):
2059 vars.where_clause += " AND "
2060 vars.where_clause += d.value
2061 if len(vars.where_clause):
2062 if self.partial:
2063 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2064 else:
2065 vars.where_clause = " WHERE " + vars.where_clause + " "
2066 self.accept()
2067
2068 def ShowMessage(self, msg):
2069 self.status.setText("<font color=#FF0000>" + msg)
2070
2071 def ClearMessage(self):
2072 self.status.setText("")
2073
2074# Selected branch report creation dialog
2075
2076class SelectedBranchDialog(ReportDialogBase):
2077
2078 def __init__(self, glb, parent=None):
2079 title = "Selected Branches"
2080 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2081 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2082 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2083 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2084 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2085 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2086 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2087 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2088 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2089 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2090
2091# Event list
2092
2093def GetEventList(db):
2094 events = []
2095 query = QSqlQuery(db)
2096 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2097 while query.next():
2098 events.append(query.value(0))
2099 return events
2100
2101# Is a table selectable
2102
2103def IsSelectable(db, table, sql = ""):
2104 query = QSqlQuery(db)
2105 try:
2106 QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2107 except:
2108 return False
2109 return True
2110
2111# SQL table data model item
2112
2113class SQLTableItem():
2114
2115 def __init__(self, row, data):
2116 self.row = row
2117 self.data = data
2118
2119 def getData(self, column):
2120 return self.data[column]
2121
2122# SQL table data model
2123
2124class SQLTableModel(TableModel):
2125
2126 progress = Signal(object)
2127
2128 def __init__(self, glb, sql, column_headers, parent=None):
2129 super(SQLTableModel, self).__init__(parent)
2130 self.glb = glb
2131 self.more = True
2132 self.populated = 0
2133 self.column_headers = column_headers
2134 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2135 self.fetcher.done.connect(self.Update)
2136 self.fetcher.Fetch(glb_chunk_sz)
2137
2138 def DisplayData(self, item, index):
2139 self.FetchIfNeeded(item.row)
2140 return item.getData(index.column())
2141
2142 def AddSample(self, data):
2143 child = SQLTableItem(self.populated, data)
2144 self.child_items.append(child)
2145 self.populated += 1
2146
2147 def Update(self, fetched):
2148 if not fetched:
2149 self.more = False
2150 self.progress.emit(0)
2151 child_count = self.child_count
2152 count = self.populated - child_count
2153 if count > 0:
2154 parent = QModelIndex()
2155 self.beginInsertRows(parent, child_count, child_count + count - 1)
2156 self.insertRows(child_count, count, parent)
2157 self.child_count += count
2158 self.endInsertRows()
2159 self.progress.emit(self.child_count)
2160
2161 def FetchMoreRecords(self, count):
2162 current = self.child_count
2163 if self.more:
2164 self.fetcher.Fetch(count)
2165 else:
2166 self.progress.emit(0)
2167 return current
2168
2169 def HasMoreRecords(self):
2170 return self.more
2171
2172 def columnCount(self, parent=None):
2173 return len(self.column_headers)
2174
2175 def columnHeader(self, column):
2176 return self.column_headers[column]
2177
2178 def SQLTableDataPrep(self, query, count):
2179 data = []
2180 for i in xrange(count):
2181 data.append(query.value(i))
2182 return data
2183
2184# SQL automatic table data model
2185
2186class SQLAutoTableModel(SQLTableModel):
2187
2188 def __init__(self, glb, table_name, parent=None):
2189 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2190 if table_name == "comm_threads_view":
2191 # For now, comm_threads_view has no id column
2192 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2193 column_headers = []
2194 query = QSqlQuery(glb.db)
2195 if glb.dbref.is_sqlite3:
2196 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2197 while query.next():
2198 column_headers.append(query.value(1))
2199 if table_name == "sqlite_master":
2200 sql = "SELECT * FROM " + table_name
2201 else:
2202 if table_name[:19] == "information_schema.":
2203 sql = "SELECT * FROM " + table_name
2204 select_table_name = table_name[19:]
2205 schema = "information_schema"
2206 else:
2207 select_table_name = table_name
2208 schema = "public"
2209 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2210 while query.next():
2211 column_headers.append(query.value(0))
2212 if pyside_version_1 and sys.version_info[0] == 3:
2213 if table_name == "samples_view":
2214 self.SQLTableDataPrep = self.samples_view_DataPrep
2215 if table_name == "samples":
2216 self.SQLTableDataPrep = self.samples_DataPrep
2217 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2218
2219 def samples_view_DataPrep(self, query, count):
2220 data = []
2221 data.append(query.value(0))
2222 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2223 data.append("{:>19}".format(query.value(1)))
2224 for i in xrange(2, count):
2225 data.append(query.value(i))
2226 return data
2227
2228 def samples_DataPrep(self, query, count):
2229 data = []
2230 for i in xrange(9):
2231 data.append(query.value(i))
2232 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2233 data.append("{:>19}".format(query.value(9)))
2234 for i in xrange(10, count):
2235 data.append(query.value(i))
2236 return data
2237
2238# Base class for custom ResizeColumnsToContents
2239
2240class ResizeColumnsToContentsBase(QObject):
2241
2242 def __init__(self, parent=None):
2243 super(ResizeColumnsToContentsBase, self).__init__(parent)
2244
2245 def ResizeColumnToContents(self, column, n):
2246 # Using the view's resizeColumnToContents() here is extrememly slow
2247 # so implement a crude alternative
2248 font = self.view.font()
2249 metrics = QFontMetrics(font)
2250 max = 0
2251 for row in xrange(n):
2252 val = self.data_model.child_items[row].data[column]
2253 len = metrics.width(str(val) + "MM")
2254 max = len if len > max else max
2255 val = self.data_model.columnHeader(column)
2256 len = metrics.width(str(val) + "MM")
2257 max = len if len > max else max
2258 self.view.setColumnWidth(column, max)
2259
2260 def ResizeColumnsToContents(self):
2261 n = min(self.data_model.child_count, 100)
2262 if n < 1:
2263 # No data yet, so connect a signal to notify when there is
2264 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2265 return
2266 columns = self.data_model.columnCount()
2267 for i in xrange(columns):
2268 self.ResizeColumnToContents(i, n)
2269
2270 def UpdateColumnWidths(self, *x):
2271 # This only needs to be done once, so disconnect the signal now
2272 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2273 self.ResizeColumnsToContents()
2274
2275# Convert value to CSV
2276
2277def ToCSValue(val):
2278 if '"' in val:
2279 val = val.replace('"', '""')
2280 if "," in val or '"' in val:
2281 val = '"' + val + '"'
2282 return val
2283
2284# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2285
2286glb_max_cols = 1000
2287
2288def RowColumnKey(a):
2289 return a.row() * glb_max_cols + a.column()
2290
2291# Copy selected table cells to clipboard
2292
2293def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2294 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2295 idx_cnt = len(indexes)
2296 if not idx_cnt:
2297 return
2298 if idx_cnt == 1:
2299 with_hdr=False
2300 min_row = indexes[0].row()
2301 max_row = indexes[0].row()
2302 min_col = indexes[0].column()
2303 max_col = indexes[0].column()
2304 for i in indexes:
2305 min_row = min(min_row, i.row())
2306 max_row = max(max_row, i.row())
2307 min_col = min(min_col, i.column())
2308 max_col = max(max_col, i.column())
2309 if max_col > glb_max_cols:
2310 raise RuntimeError("glb_max_cols is too low")
2311 max_width = [0] * (1 + max_col - min_col)
2312 for i in indexes:
2313 c = i.column() - min_col
2314 max_width[c] = max(max_width[c], len(str(i.data())))
2315 text = ""
2316 pad = ""
2317 sep = ""
2318 if with_hdr:
2319 model = indexes[0].model()
2320 for col in range(min_col, max_col + 1):
2321 val = model.headerData(col, Qt.Horizontal)
2322 if as_csv:
2323 text += sep + ToCSValue(val)
2324 sep = ","
2325 else:
2326 c = col - min_col
2327 max_width[c] = max(max_width[c], len(val))
2328 width = max_width[c]
2329 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2330 if align & Qt.AlignRight:
2331 val = val.rjust(width)
2332 text += pad + sep + val
2333 pad = " " * (width - len(val))
2334 sep = " "
2335 text += "\n"
2336 pad = ""
2337 sep = ""
2338 last_row = min_row
2339 for i in indexes:
2340 if i.row() > last_row:
2341 last_row = i.row()
2342 text += "\n"
2343 pad = ""
2344 sep = ""
2345 if as_csv:
2346 text += sep + ToCSValue(str(i.data()))
2347 sep = ","
2348 else:
2349 width = max_width[i.column() - min_col]
2350 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2351 val = str(i.data()).rjust(width)
2352 else:
2353 val = str(i.data())
2354 text += pad + sep + val
2355 pad = " " * (width - len(val))
2356 sep = " "
2357 QApplication.clipboard().setText(text)
2358
2359def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2360 indexes = view.selectedIndexes()
2361 if not len(indexes):
2362 return
2363
2364 selection = view.selectionModel()
2365
2366 first = None
2367 for i in indexes:
2368 above = view.indexAbove(i)
2369 if not selection.isSelected(above):
2370 first = i
2371 break
2372
2373 if first is None:
2374 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2375
2376 model = first.model()
2377 row_cnt = 0
2378 col_cnt = model.columnCount(first)
2379 max_width = [0] * col_cnt
2380
2381 indent_sz = 2
2382 indent_str = " " * indent_sz
2383
2384 expanded_mark_sz = 2
2385 if sys.version_info[0] == 3:
2386 expanded_mark = "\u25BC "
2387 not_expanded_mark = "\u25B6 "
2388 else:
2389 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2390 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2391 leaf_mark = " "
2392
2393 if not as_csv:
2394 pos = first
2395 while True:
2396 row_cnt += 1
2397 row = pos.row()
2398 for c in range(col_cnt):
2399 i = pos.sibling(row, c)
2400 if c:
2401 n = len(str(i.data()))
2402 else:
2403 n = len(str(i.data()).strip())
2404 n += (i.internalPointer().level - 1) * indent_sz
2405 n += expanded_mark_sz
2406 max_width[c] = max(max_width[c], n)
2407 pos = view.indexBelow(pos)
2408 if not selection.isSelected(pos):
2409 break
2410
2411 text = ""
2412 pad = ""
2413 sep = ""
2414 if with_hdr:
2415 for c in range(col_cnt):
2416 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2417 if as_csv:
2418 text += sep + ToCSValue(val)
2419 sep = ","
2420 else:
2421 max_width[c] = max(max_width[c], len(val))
2422 width = max_width[c]
2423 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2424 if align & Qt.AlignRight:
2425 val = val.rjust(width)
2426 text += pad + sep + val
2427 pad = " " * (width - len(val))
2428 sep = " "
2429 text += "\n"
2430 pad = ""
2431 sep = ""
2432
2433 pos = first
2434 while True:
2435 row = pos.row()
2436 for c in range(col_cnt):
2437 i = pos.sibling(row, c)
2438 val = str(i.data())
2439 if not c:
2440 if model.hasChildren(i):
2441 if view.isExpanded(i):
2442 mark = expanded_mark
2443 else:
2444 mark = not_expanded_mark
2445 else:
2446 mark = leaf_mark
2447 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2448 if as_csv:
2449 text += sep + ToCSValue(val)
2450 sep = ","
2451 else:
2452 width = max_width[c]
2453 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2454 val = val.rjust(width)
2455 text += pad + sep + val
2456 pad = " " * (width - len(val))
2457 sep = " "
2458 pos = view.indexBelow(pos)
2459 if not selection.isSelected(pos):
2460 break
2461 text = text.rstrip() + "\n"
2462 pad = ""
2463 sep = ""
2464
2465 QApplication.clipboard().setText(text)
2466
2467def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2468 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2469
2470def CopyCellsToClipboardHdr(view):
2471 CopyCellsToClipboard(view, False, True)
2472
2473def CopyCellsToClipboardCSV(view):
2474 CopyCellsToClipboard(view, True, True)
2475
2476# Context menu
2477
2478class ContextMenu(object):
2479
2480 def __init__(self, view):
2481 self.view = view
2482 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2483 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2484
2485 def ShowContextMenu(self, pos):
2486 menu = QMenu(self.view)
2487 self.AddActions(menu)
2488 menu.exec_(self.view.mapToGlobal(pos))
2489
2490 def AddCopy(self, menu):
2491 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2492 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2493
2494 def AddActions(self, menu):
2495 self.AddCopy(menu)
2496
2497class TreeContextMenu(ContextMenu):
2498
2499 def __init__(self, view):
2500 super(TreeContextMenu, self).__init__(view)
2501
2502 def AddActions(self, menu):
2503 i = self.view.currentIndex()
2504 text = str(i.data()).strip()
2505 if len(text):
2506 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2507 self.AddCopy(menu)
2508
2509# Table window
2510
2511class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2512
2513 def __init__(self, glb, table_name, parent=None):
2514 super(TableWindow, self).__init__(parent)
2515
2516 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2517
2518 self.model = QSortFilterProxyModel()
2519 self.model.setSourceModel(self.data_model)
2520
2521 self.view = QTableView()
2522 self.view.setModel(self.model)
2523 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2524 self.view.verticalHeader().setVisible(False)
2525 self.view.sortByColumn(-1, Qt.AscendingOrder)
2526 self.view.setSortingEnabled(True)
2527 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2528 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2529
2530 self.ResizeColumnsToContents()
2531
2532 self.context_menu = ContextMenu(self.view)
2533
2534 self.find_bar = FindBar(self, self, True)
2535
2536 self.finder = ChildDataItemFinder(self.data_model)
2537
2538 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2539
2540 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2541
2542 self.setWidget(self.vbox.Widget())
2543
2544 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2545
2546 def Find(self, value, direction, pattern, context):
2547 self.view.setFocus()
2548 self.find_bar.Busy()
2549 self.finder.Find(value, direction, pattern, context, self.FindDone)
2550
2551 def FindDone(self, row):
2552 self.find_bar.Idle()
2553 if row >= 0:
2554 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2555 else:
2556 self.find_bar.NotFound()
2557
2558# Table list
2559
2560def GetTableList(glb):
2561 tables = []
2562 query = QSqlQuery(glb.db)
2563 if glb.dbref.is_sqlite3:
2564 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2565 else:
2566 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2567 while query.next():
2568 tables.append(query.value(0))
2569 if glb.dbref.is_sqlite3:
2570 tables.append("sqlite_master")
2571 else:
2572 tables.append("information_schema.tables")
2573 tables.append("information_schema.views")
2574 tables.append("information_schema.columns")
2575 return tables
2576
2577# Top Calls data model
2578
2579class TopCallsModel(SQLTableModel):
2580
2581 def __init__(self, glb, report_vars, parent=None):
2582 text = ""
2583 if not glb.dbref.is_sqlite3:
2584 text = "::text"
2585 limit = ""
2586 if len(report_vars.limit):
2587 limit = " LIMIT " + report_vars.limit
2588 sql = ("SELECT comm, pid, tid, name,"
2589 " CASE"
2590 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2591 " ELSE short_name"
2592 " END AS dso,"
2593 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2594 " CASE"
2595 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2596 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2597 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2598 " ELSE ''" + text +
2599 " END AS flags"
2600 " FROM calls"
2601 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2602 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2603 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2604 " INNER JOIN comms ON calls.comm_id = comms.id"
2605 " INNER JOIN threads ON calls.thread_id = threads.id" +
2606 report_vars.where_clause +
2607 " ORDER BY elapsed_time DESC" +
2608 limit
2609 )
2610 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2611 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2612 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2613
2614 def columnAlignment(self, column):
2615 return self.alignment[column]
2616
2617# Top Calls report creation dialog
2618
2619class TopCallsDialog(ReportDialogBase):
2620
2621 def __init__(self, glb, parent=None):
2622 title = "Top Calls by Elapsed Time"
2623 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2624 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2625 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2626 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2627 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2628 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2629 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2630 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2631 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2632
2633# Top Calls window
2634
2635class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2636
2637 def __init__(self, glb, report_vars, parent=None):
2638 super(TopCallsWindow, self).__init__(parent)
2639
2640 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2641 self.model = self.data_model
2642
2643 self.view = QTableView()
2644 self.view.setModel(self.model)
2645 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2646 self.view.verticalHeader().setVisible(False)
2647 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2648 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2649
2650 self.context_menu = ContextMenu(self.view)
2651
2652 self.ResizeColumnsToContents()
2653
2654 self.find_bar = FindBar(self, self, True)
2655
2656 self.finder = ChildDataItemFinder(self.model)
2657
2658 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2659
2660 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2661
2662 self.setWidget(self.vbox.Widget())
2663
2664 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2665
2666 def Find(self, value, direction, pattern, context):
2667 self.view.setFocus()
2668 self.find_bar.Busy()
2669 self.finder.Find(value, direction, pattern, context, self.FindDone)
2670
2671 def FindDone(self, row):
2672 self.find_bar.Idle()
2673 if row >= 0:
2674 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2675 else:
2676 self.find_bar.NotFound()
2677
2678# Action Definition
2679
2680def CreateAction(label, tip, callback, parent=None, shortcut=None):
2681 action = QAction(label, parent)
2682 if shortcut != None:
2683 action.setShortcuts(shortcut)
2684 action.setStatusTip(tip)
2685 action.triggered.connect(callback)
2686 return action
2687
2688# Typical application actions
2689
2690def CreateExitAction(app, parent=None):
2691 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2692
2693# Typical MDI actions
2694
2695def CreateCloseActiveWindowAction(mdi_area):
2696 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2697
2698def CreateCloseAllWindowsAction(mdi_area):
2699 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2700
2701def CreateTileWindowsAction(mdi_area):
2702 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2703
2704def CreateCascadeWindowsAction(mdi_area):
2705 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2706
2707def CreateNextWindowAction(mdi_area):
2708 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2709
2710def CreatePreviousWindowAction(mdi_area):
2711 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2712
2713# Typical MDI window menu
2714
2715class WindowMenu():
2716
2717 def __init__(self, mdi_area, menu):
2718 self.mdi_area = mdi_area
2719 self.window_menu = menu.addMenu("&Windows")
2720 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2721 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2722 self.tile_windows = CreateTileWindowsAction(mdi_area)
2723 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2724 self.next_window = CreateNextWindowAction(mdi_area)
2725 self.previous_window = CreatePreviousWindowAction(mdi_area)
2726 self.window_menu.aboutToShow.connect(self.Update)
2727
2728 def Update(self):
2729 self.window_menu.clear()
2730 sub_window_count = len(self.mdi_area.subWindowList())
2731 have_sub_windows = sub_window_count != 0
2732 self.close_active_window.setEnabled(have_sub_windows)
2733 self.close_all_windows.setEnabled(have_sub_windows)
2734 self.tile_windows.setEnabled(have_sub_windows)
2735 self.cascade_windows.setEnabled(have_sub_windows)
2736 self.next_window.setEnabled(have_sub_windows)
2737 self.previous_window.setEnabled(have_sub_windows)
2738 self.window_menu.addAction(self.close_active_window)
2739 self.window_menu.addAction(self.close_all_windows)
2740 self.window_menu.addSeparator()
2741 self.window_menu.addAction(self.tile_windows)
2742 self.window_menu.addAction(self.cascade_windows)
2743 self.window_menu.addSeparator()
2744 self.window_menu.addAction(self.next_window)
2745 self.window_menu.addAction(self.previous_window)
2746 if sub_window_count == 0:
2747 return
2748 self.window_menu.addSeparator()
2749 nr = 1
2750 for sub_window in self.mdi_area.subWindowList():
2751 label = str(nr) + " " + sub_window.name
2752 if nr < 10:
2753 label = "&" + label
2754 action = self.window_menu.addAction(label)
2755 action.setCheckable(True)
2756 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2757 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2758 self.window_menu.addAction(action)
2759 nr += 1
2760
2761 def setActiveSubWindow(self, nr):
2762 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2763
2764# Help text
2765
2766glb_help_text = """
2767<h1>Contents</h1>
2768<style>
2769p.c1 {
2770 text-indent: 40px;
2771}
2772p.c2 {
2773 text-indent: 80px;
2774}
2775}
2776</style>
2777<p class=c1><a href=#reports>1. Reports</a></p>
2778<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2779<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2780<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2781<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2782<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2783<p class=c1><a href=#tables>2. Tables</a></p>
2784<h1 id=reports>1. Reports</h1>
2785<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2786The result is a GUI window with a tree representing a context-sensitive
2787call-graph. Expanding a couple of levels of the tree and adjusting column
2788widths to suit will display something like:
2789<pre>
2790 Call Graph: pt_example
2791Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2792v- ls
2793 v- 2638:2638
2794 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2795 |- unknown unknown 1 13198 0.1 1 0.0
2796 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2797 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2798 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2799 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2800 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2801 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2802 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2803 v- main ls 1 8182043 99.6 180254 99.9
2804</pre>
2805<h3>Points to note:</h3>
2806<ul>
2807<li>The top level is a command name (comm)</li>
2808<li>The next level is a thread (pid:tid)</li>
2809<li>Subsequent levels are functions</li>
2810<li>'Count' is the number of calls</li>
2811<li>'Time' is the elapsed time until the function returns</li>
2812<li>Percentages are relative to the level above</li>
2813<li>'Branch Count' is the total number of branches for that function and all functions that it calls
2814</ul>
2815<h3>Find</h3>
2816Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2817The pattern matching symbols are ? for any character and * for zero or more characters.
2818<h2 id=calltree>1.2 Call Tree</h2>
2819The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2820Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2821<h2 id=allbranches>1.3 All branches</h2>
2822The All branches report displays all branches in chronological order.
2823Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2824<h3>Disassembly</h3>
2825Open a branch to display disassembly. This only works if:
2826<ol>
2827<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2828<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2829The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2830One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2831or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2832</ol>
2833<h4 id=xed>Intel XED Setup</h4>
2834To use Intel XED, libxed.so must be present. To build and install libxed.so:
2835<pre>
2836git clone https://github.com/intelxed/mbuild.git mbuild
2837git clone https://github.com/intelxed/xed
2838cd xed
2839./mfile.py --share
2840sudo ./mfile.py --prefix=/usr/local install
2841sudo ldconfig
2842</pre>
2843<h3>Find</h3>
2844Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2845Refer to Python documentation for the regular expression syntax.
2846All columns are searched, but only currently fetched rows are searched.
2847<h2 id=selectedbranches>1.4 Selected branches</h2>
2848This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2849by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2850<h3>1.4.1 Time ranges</h3>
2851The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2852ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2853<pre>
2854 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2855 100us-200us From 100us to 200us
2856 10ms- From 10ms to the end
2857 -100ns The first 100ns
2858 -10ms- The last 10ms
2859</pre>
2860N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2861<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2862The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2863The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2864If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2865<h1 id=tables>2. Tables</h1>
2866The Tables menu shows all tables and views in the database. Most tables have an associated view
2867which displays the information in a more friendly way. Not all data for large tables is fetched
2868immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2869but that can be slow for large tables.
2870<p>There are also tables of database meta-information.
2871For SQLite3 databases, the sqlite_master table is included.
2872For PostgreSQL databases, information_schema.tables/views/columns are included.
2873<h3>Find</h3>
2874Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2875Refer to Python documentation for the regular expression syntax.
2876All columns are searched, but only currently fetched rows are searched.
2877<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2878will go to the next/previous result in id order, instead of display order.
2879"""
2880
2881# Help window
2882
2883class HelpWindow(QMdiSubWindow):
2884
2885 def __init__(self, glb, parent=None):
2886 super(HelpWindow, self).__init__(parent)
2887
2888 self.text = QTextBrowser()
2889 self.text.setHtml(glb_help_text)
2890 self.text.setReadOnly(True)
2891 self.text.setOpenExternalLinks(True)
2892
2893 self.setWidget(self.text)
2894
2895 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2896
2897# Main window that only displays the help text
2898
2899class HelpOnlyWindow(QMainWindow):
2900
2901 def __init__(self, parent=None):
2902 super(HelpOnlyWindow, self).__init__(parent)
2903
2904 self.setMinimumSize(200, 100)
2905 self.resize(800, 600)
2906 self.setWindowTitle("Exported SQL Viewer Help")
2907 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2908
2909 self.text = QTextBrowser()
2910 self.text.setHtml(glb_help_text)
2911 self.text.setReadOnly(True)
2912 self.text.setOpenExternalLinks(True)
2913
2914 self.setCentralWidget(self.text)
2915
2916# PostqreSQL server version
2917
2918def PostqreSQLServerVersion(db):
2919 query = QSqlQuery(db)
2920 QueryExec(query, "SELECT VERSION()")
2921 if query.next():
2922 v_str = query.value(0)
2923 v_list = v_str.strip().split(" ")
2924 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
2925 return v_list[1]
2926 return v_str
2927 return "Unknown"
2928
2929# SQLite version
2930
2931def SQLiteVersion(db):
2932 query = QSqlQuery(db)
2933 QueryExec(query, "SELECT sqlite_version()")
2934 if query.next():
2935 return query.value(0)
2936 return "Unknown"
2937
2938# About dialog
2939
2940class AboutDialog(QDialog):
2941
2942 def __init__(self, glb, parent=None):
2943 super(AboutDialog, self).__init__(parent)
2944
2945 self.setWindowTitle("About Exported SQL Viewer")
2946 self.setMinimumWidth(300)
2947
2948 pyside_version = "1" if pyside_version_1 else "2"
2949
2950 text = "<pre>"
2951 text += "Python version: " + sys.version.split(" ")[0] + "\n"
2952 text += "PySide version: " + pyside_version + "\n"
2953 text += "Qt version: " + qVersion() + "\n"
2954 if glb.dbref.is_sqlite3:
2955 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
2956 else:
2957 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
2958 text += "</pre>"
2959
2960 self.text = QTextBrowser()
2961 self.text.setHtml(text)
2962 self.text.setReadOnly(True)
2963 self.text.setOpenExternalLinks(True)
2964
2965 self.vbox = QVBoxLayout()
2966 self.vbox.addWidget(self.text)
2967
2968 self.setLayout(self.vbox);
2969
2970# Font resize
2971
2972def ResizeFont(widget, diff):
2973 font = widget.font()
2974 sz = font.pointSize()
2975 font.setPointSize(sz + diff)
2976 widget.setFont(font)
2977
2978def ShrinkFont(widget):
2979 ResizeFont(widget, -1)
2980
2981def EnlargeFont(widget):
2982 ResizeFont(widget, 1)
2983
2984# Unique name for sub-windows
2985
2986def NumberedWindowName(name, nr):
2987 if nr > 1:
2988 name += " <" + str(nr) + ">"
2989 return name
2990
2991def UniqueSubWindowName(mdi_area, name):
2992 nr = 1
2993 while True:
2994 unique_name = NumberedWindowName(name, nr)
2995 ok = True
2996 for sub_window in mdi_area.subWindowList():
2997 if sub_window.name == unique_name:
2998 ok = False
2999 break
3000 if ok:
3001 return unique_name
3002 nr += 1
3003
3004# Add a sub-window
3005
3006def AddSubWindow(mdi_area, sub_window, name):
3007 unique_name = UniqueSubWindowName(mdi_area, name)
3008 sub_window.setMinimumSize(200, 100)
3009 sub_window.resize(800, 600)
3010 sub_window.setWindowTitle(unique_name)
3011 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3012 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3013 sub_window.name = unique_name
3014 mdi_area.addSubWindow(sub_window)
3015 sub_window.show()
3016
3017# Main window
3018
3019class MainWindow(QMainWindow):
3020
3021 def __init__(self, glb, parent=None):
3022 super(MainWindow, self).__init__(parent)
3023
3024 self.glb = glb
3025
3026 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3027 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3028 self.setMinimumSize(200, 100)
3029
3030 self.mdi_area = QMdiArea()
3031 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3032 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3033
3034 self.setCentralWidget(self.mdi_area)
3035
3036 menu = self.menuBar()
3037
3038 file_menu = menu.addMenu("&File")
3039 file_menu.addAction(CreateExitAction(glb.app, self))
3040
3041 edit_menu = menu.addMenu("&Edit")
3042 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3043 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3044 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3045 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3046 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3047 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3048
3049 reports_menu = menu.addMenu("&Reports")
3050 if IsSelectable(glb.db, "calls"):
3051 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3052
3053 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3054 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3055
3056 self.EventMenu(GetEventList(glb.db), reports_menu)
3057
3058 if IsSelectable(glb.db, "calls"):
3059 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3060
3061 self.TableMenu(GetTableList(glb), menu)
3062
3063 self.window_menu = WindowMenu(self.mdi_area, menu)
3064
3065 help_menu = menu.addMenu("&Help")
3066 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3067 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3068
3069 def Try(self, fn):
3070 win = self.mdi_area.activeSubWindow()
3071 if win:
3072 try:
3073 fn(win.view)
3074 except:
3075 pass
3076
3077 def CopyToClipboard(self):
3078 self.Try(CopyCellsToClipboardHdr)
3079
3080 def CopyToClipboardCSV(self):
3081 self.Try(CopyCellsToClipboardCSV)
3082
3083 def Find(self):
3084 win = self.mdi_area.activeSubWindow()
3085 if win:
3086 try:
3087 win.find_bar.Activate()
3088 except:
3089 pass
3090
3091 def FetchMoreRecords(self):
3092 win = self.mdi_area.activeSubWindow()
3093 if win:
3094 try:
3095 win.fetch_bar.Activate()
3096 except:
3097 pass
3098
3099 def ShrinkFont(self):
3100 self.Try(ShrinkFont)
3101
3102 def EnlargeFont(self):
3103 self.Try(EnlargeFont)
3104
3105 def EventMenu(self, events, reports_menu):
3106 branches_events = 0
3107 for event in events:
3108 event = event.split(":")[0]
3109 if event == "branches":
3110 branches_events += 1
3111 dbid = 0
3112 for event in events:
3113 dbid += 1
3114 event = event.split(":")[0]
3115 if event == "branches":
3116 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3117 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
3118 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3119 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
3120
3121 def TableMenu(self, tables, menu):
3122 table_menu = menu.addMenu("&Tables")
3123 for table in tables:
3124 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
3125
3126 def NewCallGraph(self):
3127 CallGraphWindow(self.glb, self)
3128
3129 def NewCallTree(self):
3130 CallTreeWindow(self.glb, self)
3131
3132 def NewTopCalls(self):
3133 dialog = TopCallsDialog(self.glb, self)
3134 ret = dialog.exec_()
3135 if ret:
3136 TopCallsWindow(self.glb, dialog.report_vars, self)
3137
3138 def NewBranchView(self, event_id):
3139 BranchWindow(self.glb, event_id, ReportVars(), self)
3140
3141 def NewSelectedBranchView(self, event_id):
3142 dialog = SelectedBranchDialog(self.glb, self)
3143 ret = dialog.exec_()
3144 if ret:
3145 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3146
3147 def NewTableView(self, table_name):
3148 TableWindow(self.glb, table_name, self)
3149
3150 def Help(self):
3151 HelpWindow(self.glb, self)
3152
3153 def About(self):
3154 dialog = AboutDialog(self.glb, self)
3155 dialog.exec_()
3156
3157# XED Disassembler
3158
3159class xed_state_t(Structure):
3160
3161 _fields_ = [
3162 ("mode", c_int),
3163 ("width", c_int)
3164 ]
3165
3166class XEDInstruction():
3167
3168 def __init__(self, libxed):
3169 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3170 xedd_t = c_byte * 512
3171 self.xedd = xedd_t()
3172 self.xedp = addressof(self.xedd)
3173 libxed.xed_decoded_inst_zero(self.xedp)
3174 self.state = xed_state_t()
3175 self.statep = addressof(self.state)
3176 # Buffer for disassembled instruction text
3177 self.buffer = create_string_buffer(256)
3178 self.bufferp = addressof(self.buffer)
3179
3180class LibXED():
3181
3182 def __init__(self):
3183 try:
3184 self.libxed = CDLL("libxed.so")
3185 except:
3186 self.libxed = None
3187 if not self.libxed:
3188 self.libxed = CDLL("/usr/local/lib/libxed.so")
3189
3190 self.xed_tables_init = self.libxed.xed_tables_init
3191 self.xed_tables_init.restype = None
3192 self.xed_tables_init.argtypes = []
3193
3194 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3195 self.xed_decoded_inst_zero.restype = None
3196 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3197
3198 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3199 self.xed_operand_values_set_mode.restype = None
3200 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3201
3202 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3203 self.xed_decoded_inst_zero_keep_mode.restype = None
3204 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3205
3206 self.xed_decode = self.libxed.xed_decode
3207 self.xed_decode.restype = c_int
3208 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3209
3210 self.xed_format_context = self.libxed.xed_format_context
3211 self.xed_format_context.restype = c_uint
3212 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3213
3214 self.xed_tables_init()
3215
3216 def Instruction(self):
3217 return XEDInstruction(self)
3218
3219 def SetMode(self, inst, mode):
3220 if mode:
3221 inst.state.mode = 4 # 32-bit
3222 inst.state.width = 4 # 4 bytes
3223 else:
3224 inst.state.mode = 1 # 64-bit
3225 inst.state.width = 8 # 8 bytes
3226 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3227
3228 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3229 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3230 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3231 if err:
3232 return 0, ""
3233 # Use AT&T mode (2), alternative is Intel (3)
3234 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3235 if not ok:
3236 return 0, ""
3237 if sys.version_info[0] == 2:
3238 result = inst.buffer.value
3239 else:
3240 result = inst.buffer.value.decode()
3241 # Return instruction length and the disassembled instruction text
3242 # For now, assume the length is in byte 166
3243 return inst.xedd[166], result
3244
3245def TryOpen(file_name):
3246 try:
3247 return open(file_name, "rb")
3248 except:
3249 return None
3250
3251def Is64Bit(f):
3252 result = sizeof(c_void_p)
3253 # ELF support only
3254 pos = f.tell()
3255 f.seek(0)
3256 header = f.read(7)
3257 f.seek(pos)
3258 magic = header[0:4]
3259 if sys.version_info[0] == 2:
3260 eclass = ord(header[4])
3261 encoding = ord(header[5])
3262 version = ord(header[6])
3263 else:
3264 eclass = header[4]
3265 encoding = header[5]
3266 version = header[6]
3267 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3268 result = True if eclass == 2 else False
3269 return result
3270
3271# Global data
3272
3273class Glb():
3274
3275 def __init__(self, dbref, db, dbname):
3276 self.dbref = dbref
3277 self.db = db
3278 self.dbname = dbname
3279 self.home_dir = os.path.expanduser("~")
3280 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3281 if self.buildid_dir:
3282 self.buildid_dir += "/.build-id/"
3283 else:
3284 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3285 self.app = None
3286 self.mainwindow = None
3287 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3288 try:
3289 self.disassembler = LibXED()
3290 self.have_disassembler = True
3291 except:
3292 self.have_disassembler = False
3293
3294 def FileFromBuildId(self, build_id):
3295 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3296 return TryOpen(file_name)
3297
3298 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3299 # Assume current machine i.e. no support for virtualization
3300 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3301 file_name = os.getenv("PERF_KCORE")
3302 f = TryOpen(file_name) if file_name else None
3303 if f:
3304 return f
3305 # For now, no special handling if long_name is /proc/kcore
3306 f = TryOpen(long_name)
3307 if f:
3308 return f
3309 f = self.FileFromBuildId(build_id)
3310 if f:
3311 return f
3312 return None
3313
3314 def AddInstanceToShutdownOnExit(self, instance):
3315 self.instances_to_shutdown_on_exit.add(instance)
3316
3317 # Shutdown any background processes or threads
3318 def ShutdownInstances(self):
3319 for x in self.instances_to_shutdown_on_exit:
3320 try:
3321 x.Shutdown()
3322 except:
3323 pass
3324
3325# Database reference
3326
3327class DBRef():
3328
3329 def __init__(self, is_sqlite3, dbname):
3330 self.is_sqlite3 = is_sqlite3
3331 self.dbname = dbname
3332
3333 def Open(self, connection_name):
3334 dbname = self.dbname
3335 if self.is_sqlite3:
3336 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3337 else:
3338 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3339 opts = dbname.split()
3340 for opt in opts:
3341 if "=" in opt:
3342 opt = opt.split("=")
3343 if opt[0] == "hostname":
3344 db.setHostName(opt[1])
3345 elif opt[0] == "port":
3346 db.setPort(int(opt[1]))
3347 elif opt[0] == "username":
3348 db.setUserName(opt[1])
3349 elif opt[0] == "password":
3350 db.setPassword(opt[1])
3351 elif opt[0] == "dbname":
3352 dbname = opt[1]
3353 else:
3354 dbname = opt
3355
3356 db.setDatabaseName(dbname)
3357 if not db.open():
3358 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3359 return db, dbname
3360
3361# Main
3362
3363def Main():
3364 if (len(sys.argv) < 2):
3365 printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
3366 raise Exception("Too few arguments")
3367
3368 dbname = sys.argv[1]
3369 if dbname == "--help-only":
3370 app = QApplication(sys.argv)
3371 mainwindow = HelpOnlyWindow()
3372 mainwindow.show()
3373 err = app.exec_()
3374 sys.exit(err)
3375
3376 is_sqlite3 = False
3377 try:
3378 f = open(dbname, "rb")
3379 if f.read(15) == b'SQLite format 3':
3380 is_sqlite3 = True
3381 f.close()
3382 except:
3383 pass
3384
3385 dbref = DBRef(is_sqlite3, dbname)
3386 db, dbname = dbref.Open("main")
3387 glb = Glb(dbref, db, dbname)
3388 app = QApplication(sys.argv)
3389 glb.app = app
3390 mainwindow = MainWindow(glb)
3391 glb.mainwindow = mainwindow
3392 mainwindow.show()
3393 err = app.exec_()
3394 glb.ShutdownInstances()
3395 db.close()
3396 sys.exit(err)
3397
3398if __name__ == "__main__":
3399 Main()