exported-sql-viewer.py 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615
  1. #!/usr/bin/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. # To use this script you will need to have exported data using either the
  6. # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
  7. # scripts for details.
  8. #
  9. # Following on from the example in the export scripts, a
  10. # call-graph can be displayed for the pt_example database like this:
  11. #
  12. # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
  13. #
  14. # Note that for PostgreSQL, this script supports connecting to remote databases
  15. # by setting hostname, port, username, password, and dbname e.g.
  16. #
  17. # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
  18. #
  19. # The result is a GUI window with a tree representing a context-sensitive
  20. # call-graph. Expanding a couple of levels of the tree and adjusting column
  21. # widths to suit will display something like:
  22. #
  23. # Call Graph: pt_example
  24. # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
  25. # v- ls
  26. # v- 2638:2638
  27. # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
  28. # |- unknown unknown 1 13198 0.1 1 0.0
  29. # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
  30. # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
  31. # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
  32. # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
  33. # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
  34. # >- __libc_csu_init ls 1 10354 0.1 10 0.0
  35. # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
  36. # v- main ls 1 8182043 99.6 180254 99.9
  37. #
  38. # Points to note:
  39. # The top level is a command name (comm)
  40. # The next level is a thread (pid:tid)
  41. # Subsequent levels are functions
  42. # 'Count' is the number of calls
  43. # 'Time' is the elapsed time until the function returns
  44. # Percentages are relative to the level above
  45. # 'Branch Count' is the total number of branches for that function and all
  46. # functions that it calls
  47. # There is also a "All branches" report, which displays branches and
  48. # possibly disassembly. However, presently, the only supported disassembler is
  49. # Intel XED, and additionally the object code must be present in perf build ID
  50. # cache. To use Intel XED, libxed.so must be present. To build and install
  51. # libxed.so:
  52. # git clone https://github.com/intelxed/mbuild.git mbuild
  53. # git clone https://github.com/intelxed/xed
  54. # cd xed
  55. # ./mfile.py --share
  56. # sudo ./mfile.py --prefix=/usr/local install
  57. # sudo ldconfig
  58. #
  59. # Example report:
  60. #
  61. # Time CPU Command PID TID Branch Type In Tx Branch
  62. # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
  63. # 7fab593ea260 48 89 e7 mov %rsp, %rdi
  64. # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  65. # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
  66. # 7fab593ea260 48 89 e7 mov %rsp, %rdi
  67. # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
  68. # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
  69. # 7fab593ea930 55 pushq %rbp
  70. # 7fab593ea931 48 89 e5 mov %rsp, %rbp
  71. # 7fab593ea934 41 57 pushq %r15
  72. # 7fab593ea936 41 56 pushq %r14
  73. # 7fab593ea938 41 55 pushq %r13
  74. # 7fab593ea93a 41 54 pushq %r12
  75. # 7fab593ea93c 53 pushq %rbx
  76. # 7fab593ea93d 48 89 fb mov %rdi, %rbx
  77. # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
  78. # 7fab593ea944 0f 31 rdtsc
  79. # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
  80. # 7fab593ea94a 89 c0 mov %eax, %eax
  81. # 7fab593ea94c 48 09 c2 or %rax, %rdx
  82. # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
  83. # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  84. # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
  85. # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
  86. # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
  87. # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  88. import sys
  89. import weakref
  90. import threading
  91. import string
  92. import cPickle
  93. import re
  94. import os
  95. from PySide.QtCore import *
  96. from PySide.QtGui import *
  97. from PySide.QtSql import *
  98. from decimal import *
  99. from ctypes import *
  100. from multiprocessing import Process, Array, Value, Event
  101. # Data formatting helpers
  102. def tohex(ip):
  103. if ip < 0:
  104. ip += 1 << 64
  105. return "%x" % ip
  106. def offstr(offset):
  107. if offset:
  108. return "+0x%x" % offset
  109. return ""
  110. def dsoname(name):
  111. if name == "[kernel.kallsyms]":
  112. return "[kernel]"
  113. return name
  114. def findnth(s, sub, n, offs=0):
  115. pos = s.find(sub)
  116. if pos < 0:
  117. return pos
  118. if n <= 1:
  119. return offs + pos
  120. return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
  121. # Percent to one decimal place
  122. def PercentToOneDP(n, d):
  123. if not d:
  124. return "0.0"
  125. x = (n * Decimal(100)) / d
  126. return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
  127. # Helper for queries that must not fail
  128. def QueryExec(query, stmt):
  129. ret = query.exec_(stmt)
  130. if not ret:
  131. raise Exception("Query failed: " + query.lastError().text())
  132. # Background thread
  133. class Thread(QThread):
  134. done = Signal(object)
  135. def __init__(self, task, param=None, parent=None):
  136. super(Thread, self).__init__(parent)
  137. self.task = task
  138. self.param = param
  139. def run(self):
  140. while True:
  141. if self.param is None:
  142. done, result = self.task()
  143. else:
  144. done, result = self.task(self.param)
  145. self.done.emit(result)
  146. if done:
  147. break
  148. # Tree data model
  149. class TreeModel(QAbstractItemModel):
  150. def __init__(self, root, parent=None):
  151. super(TreeModel, self).__init__(parent)
  152. self.root = root
  153. self.last_row_read = 0
  154. def Item(self, parent):
  155. if parent.isValid():
  156. return parent.internalPointer()
  157. else:
  158. return self.root
  159. def rowCount(self, parent):
  160. result = self.Item(parent).childCount()
  161. if result < 0:
  162. result = 0
  163. self.dataChanged.emit(parent, parent)
  164. return result
  165. def hasChildren(self, parent):
  166. return self.Item(parent).hasChildren()
  167. def headerData(self, section, orientation, role):
  168. if role == Qt.TextAlignmentRole:
  169. return self.columnAlignment(section)
  170. if role != Qt.DisplayRole:
  171. return None
  172. if orientation != Qt.Horizontal:
  173. return None
  174. return self.columnHeader(section)
  175. def parent(self, child):
  176. child_item = child.internalPointer()
  177. if child_item is self.root:
  178. return QModelIndex()
  179. parent_item = child_item.getParentItem()
  180. return self.createIndex(parent_item.getRow(), 0, parent_item)
  181. def index(self, row, column, parent):
  182. child_item = self.Item(parent).getChildItem(row)
  183. return self.createIndex(row, column, child_item)
  184. def DisplayData(self, item, index):
  185. return item.getData(index.column())
  186. def FetchIfNeeded(self, row):
  187. if row > self.last_row_read:
  188. self.last_row_read = row
  189. if row + 10 >= self.root.child_count:
  190. self.fetcher.Fetch(glb_chunk_sz)
  191. def columnAlignment(self, column):
  192. return Qt.AlignLeft
  193. def columnFont(self, column):
  194. return None
  195. def data(self, index, role):
  196. if role == Qt.TextAlignmentRole:
  197. return self.columnAlignment(index.column())
  198. if role == Qt.FontRole:
  199. return self.columnFont(index.column())
  200. if role != Qt.DisplayRole:
  201. return None
  202. item = index.internalPointer()
  203. return self.DisplayData(item, index)
  204. # Table data model
  205. class TableModel(QAbstractTableModel):
  206. def __init__(self, parent=None):
  207. super(TableModel, self).__init__(parent)
  208. self.child_count = 0
  209. self.child_items = []
  210. self.last_row_read = 0
  211. def Item(self, parent):
  212. if parent.isValid():
  213. return parent.internalPointer()
  214. else:
  215. return self
  216. def rowCount(self, parent):
  217. return self.child_count
  218. def headerData(self, section, orientation, role):
  219. if role == Qt.TextAlignmentRole:
  220. return self.columnAlignment(section)
  221. if role != Qt.DisplayRole:
  222. return None
  223. if orientation != Qt.Horizontal:
  224. return None
  225. return self.columnHeader(section)
  226. def index(self, row, column, parent):
  227. return self.createIndex(row, column, self.child_items[row])
  228. def DisplayData(self, item, index):
  229. return item.getData(index.column())
  230. def FetchIfNeeded(self, row):
  231. if row > self.last_row_read:
  232. self.last_row_read = row
  233. if row + 10 >= self.child_count:
  234. self.fetcher.Fetch(glb_chunk_sz)
  235. def columnAlignment(self, column):
  236. return Qt.AlignLeft
  237. def columnFont(self, column):
  238. return None
  239. def data(self, index, role):
  240. if role == Qt.TextAlignmentRole:
  241. return self.columnAlignment(index.column())
  242. if role == Qt.FontRole:
  243. return self.columnFont(index.column())
  244. if role != Qt.DisplayRole:
  245. return None
  246. item = index.internalPointer()
  247. return self.DisplayData(item, index)
  248. # Model cache
  249. model_cache = weakref.WeakValueDictionary()
  250. model_cache_lock = threading.Lock()
  251. def LookupCreateModel(model_name, create_fn):
  252. model_cache_lock.acquire()
  253. try:
  254. model = model_cache[model_name]
  255. except:
  256. model = None
  257. if model is None:
  258. model = create_fn()
  259. model_cache[model_name] = model
  260. model_cache_lock.release()
  261. return model
  262. # Find bar
  263. class FindBar():
  264. def __init__(self, parent, finder, is_reg_expr=False):
  265. self.finder = finder
  266. self.context = []
  267. self.last_value = None
  268. self.last_pattern = None
  269. label = QLabel("Find:")
  270. label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  271. self.textbox = QComboBox()
  272. self.textbox.setEditable(True)
  273. self.textbox.currentIndexChanged.connect(self.ValueChanged)
  274. self.progress = QProgressBar()
  275. self.progress.setRange(0, 0)
  276. self.progress.hide()
  277. if is_reg_expr:
  278. self.pattern = QCheckBox("Regular Expression")
  279. else:
  280. self.pattern = QCheckBox("Pattern")
  281. self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  282. self.next_button = QToolButton()
  283. self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
  284. self.next_button.released.connect(lambda: self.NextPrev(1))
  285. self.prev_button = QToolButton()
  286. self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
  287. self.prev_button.released.connect(lambda: self.NextPrev(-1))
  288. self.close_button = QToolButton()
  289. self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
  290. self.close_button.released.connect(self.Deactivate)
  291. self.hbox = QHBoxLayout()
  292. self.hbox.setContentsMargins(0, 0, 0, 0)
  293. self.hbox.addWidget(label)
  294. self.hbox.addWidget(self.textbox)
  295. self.hbox.addWidget(self.progress)
  296. self.hbox.addWidget(self.pattern)
  297. self.hbox.addWidget(self.next_button)
  298. self.hbox.addWidget(self.prev_button)
  299. self.hbox.addWidget(self.close_button)
  300. self.bar = QWidget()
  301. self.bar.setLayout(self.hbox);
  302. self.bar.hide()
  303. def Widget(self):
  304. return self.bar
  305. def Activate(self):
  306. self.bar.show()
  307. self.textbox.setFocus()
  308. def Deactivate(self):
  309. self.bar.hide()
  310. def Busy(self):
  311. self.textbox.setEnabled(False)
  312. self.pattern.hide()
  313. self.next_button.hide()
  314. self.prev_button.hide()
  315. self.progress.show()
  316. def Idle(self):
  317. self.textbox.setEnabled(True)
  318. self.progress.hide()
  319. self.pattern.show()
  320. self.next_button.show()
  321. self.prev_button.show()
  322. def Find(self, direction):
  323. value = self.textbox.currentText()
  324. pattern = self.pattern.isChecked()
  325. self.last_value = value
  326. self.last_pattern = pattern
  327. self.finder.Find(value, direction, pattern, self.context)
  328. def ValueChanged(self):
  329. value = self.textbox.currentText()
  330. pattern = self.pattern.isChecked()
  331. index = self.textbox.currentIndex()
  332. data = self.textbox.itemData(index)
  333. # Store the pattern in the combo box to keep it with the text value
  334. if data == None:
  335. self.textbox.setItemData(index, pattern)
  336. else:
  337. self.pattern.setChecked(data)
  338. self.Find(0)
  339. def NextPrev(self, direction):
  340. value = self.textbox.currentText()
  341. pattern = self.pattern.isChecked()
  342. if value != self.last_value:
  343. index = self.textbox.findText(value)
  344. # Allow for a button press before the value has been added to the combo box
  345. if index < 0:
  346. index = self.textbox.count()
  347. self.textbox.addItem(value, pattern)
  348. self.textbox.setCurrentIndex(index)
  349. return
  350. else:
  351. self.textbox.setItemData(index, pattern)
  352. elif pattern != self.last_pattern:
  353. # Keep the pattern recorded in the combo box up to date
  354. index = self.textbox.currentIndex()
  355. self.textbox.setItemData(index, pattern)
  356. self.Find(direction)
  357. def NotFound(self):
  358. QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
  359. # Context-sensitive call graph data model item base
  360. class CallGraphLevelItemBase(object):
  361. def __init__(self, glb, row, parent_item):
  362. self.glb = glb
  363. self.row = row
  364. self.parent_item = parent_item
  365. self.query_done = False;
  366. self.child_count = 0
  367. self.child_items = []
  368. def getChildItem(self, row):
  369. return self.child_items[row]
  370. def getParentItem(self):
  371. return self.parent_item
  372. def getRow(self):
  373. return self.row
  374. def childCount(self):
  375. if not self.query_done:
  376. self.Select()
  377. if not self.child_count:
  378. return -1
  379. return self.child_count
  380. def hasChildren(self):
  381. if not self.query_done:
  382. return True
  383. return self.child_count > 0
  384. def getData(self, column):
  385. return self.data[column]
  386. # Context-sensitive call graph data model level 2+ item base
  387. class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
  388. def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
  389. super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
  390. self.comm_id = comm_id
  391. self.thread_id = thread_id
  392. self.call_path_id = call_path_id
  393. self.branch_count = branch_count
  394. self.time = time
  395. def Select(self):
  396. self.query_done = True;
  397. query = QSqlQuery(self.glb.db)
  398. QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
  399. " FROM calls"
  400. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  401. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  402. " INNER JOIN dsos ON symbols.dso_id = dsos.id"
  403. " WHERE parent_call_path_id = " + str(self.call_path_id) +
  404. " AND comm_id = " + str(self.comm_id) +
  405. " AND thread_id = " + str(self.thread_id) +
  406. " GROUP BY call_path_id, name, short_name"
  407. " ORDER BY call_path_id")
  408. while query.next():
  409. 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)
  410. self.child_items.append(child_item)
  411. self.child_count += 1
  412. # Context-sensitive call graph data model level three item
  413. class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
  414. def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
  415. super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
  416. dso = dsoname(dso)
  417. self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
  418. self.dbid = call_path_id
  419. # Context-sensitive call graph data model level two item
  420. class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
  421. def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
  422. super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
  423. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
  424. self.dbid = thread_id
  425. def Select(self):
  426. super(CallGraphLevelTwoItem, self).Select()
  427. for child_item in self.child_items:
  428. self.time += child_item.time
  429. self.branch_count += child_item.branch_count
  430. for child_item in self.child_items:
  431. child_item.data[4] = PercentToOneDP(child_item.time, self.time)
  432. child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
  433. # Context-sensitive call graph data model level one item
  434. class CallGraphLevelOneItem(CallGraphLevelItemBase):
  435. def __init__(self, glb, row, comm_id, comm, parent_item):
  436. super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
  437. self.data = [comm, "", "", "", "", "", ""]
  438. self.dbid = comm_id
  439. def Select(self):
  440. self.query_done = True;
  441. query = QSqlQuery(self.glb.db)
  442. QueryExec(query, "SELECT thread_id, pid, tid"
  443. " FROM comm_threads"
  444. " INNER JOIN threads ON thread_id = threads.id"
  445. " WHERE comm_id = " + str(self.dbid))
  446. while query.next():
  447. child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
  448. self.child_items.append(child_item)
  449. self.child_count += 1
  450. # Context-sensitive call graph data model root item
  451. class CallGraphRootItem(CallGraphLevelItemBase):
  452. def __init__(self, glb):
  453. super(CallGraphRootItem, self).__init__(glb, 0, None)
  454. self.dbid = 0
  455. self.query_done = True;
  456. query = QSqlQuery(glb.db)
  457. QueryExec(query, "SELECT id, comm FROM comms")
  458. while query.next():
  459. if not query.value(0):
  460. continue
  461. child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
  462. self.child_items.append(child_item)
  463. self.child_count += 1
  464. # Context-sensitive call graph data model
  465. class CallGraphModel(TreeModel):
  466. def __init__(self, glb, parent=None):
  467. super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
  468. self.glb = glb
  469. def columnCount(self, parent=None):
  470. return 7
  471. def columnHeader(self, column):
  472. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  473. return headers[column]
  474. def columnAlignment(self, column):
  475. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  476. return alignment[column]
  477. def FindSelect(self, value, pattern, query):
  478. if pattern:
  479. # postgresql and sqlite pattern patching differences:
  480. # postgresql LIKE is case sensitive but sqlite LIKE is not
  481. # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
  482. # postgresql supports ILIKE which is case insensitive
  483. # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
  484. if not self.glb.dbref.is_sqlite3:
  485. # Escape % and _
  486. s = value.replace("%", "\%")
  487. s = s.replace("_", "\_")
  488. # Translate * and ? into SQL LIKE pattern characters % and _
  489. trans = string.maketrans("*?", "%_")
  490. match = " LIKE '" + str(s).translate(trans) + "'"
  491. else:
  492. match = " GLOB '" + str(value) + "'"
  493. else:
  494. match = " = '" + str(value) + "'"
  495. QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
  496. " FROM calls"
  497. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  498. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  499. " WHERE symbols.name" + match +
  500. " GROUP BY comm_id, thread_id, call_path_id"
  501. " ORDER BY comm_id, thread_id, call_path_id")
  502. def FindPath(self, query):
  503. # Turn the query result into a list of ids that the tree view can walk
  504. # to open the tree at the right place.
  505. ids = []
  506. parent_id = query.value(0)
  507. while parent_id:
  508. ids.insert(0, parent_id)
  509. q2 = QSqlQuery(self.glb.db)
  510. QueryExec(q2, "SELECT parent_id"
  511. " FROM call_paths"
  512. " WHERE id = " + str(parent_id))
  513. if not q2.next():
  514. break
  515. parent_id = q2.value(0)
  516. # The call path root is not used
  517. if ids[0] == 1:
  518. del ids[0]
  519. ids.insert(0, query.value(2))
  520. ids.insert(0, query.value(1))
  521. return ids
  522. def Found(self, query, found):
  523. if found:
  524. return self.FindPath(query)
  525. return []
  526. def FindValue(self, value, pattern, query, last_value, last_pattern):
  527. if last_value == value and pattern == last_pattern:
  528. found = query.first()
  529. else:
  530. self.FindSelect(value, pattern, query)
  531. found = query.next()
  532. return self.Found(query, found)
  533. def FindNext(self, query):
  534. found = query.next()
  535. if not found:
  536. found = query.first()
  537. return self.Found(query, found)
  538. def FindPrev(self, query):
  539. found = query.previous()
  540. if not found:
  541. found = query.last()
  542. return self.Found(query, found)
  543. def FindThread(self, c):
  544. if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
  545. ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
  546. elif c.direction > 0:
  547. ids = self.FindNext(c.query)
  548. else:
  549. ids = self.FindPrev(c.query)
  550. return (True, ids)
  551. def Find(self, value, direction, pattern, context, callback):
  552. class Context():
  553. def __init__(self, *x):
  554. self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
  555. def Update(self, *x):
  556. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
  557. if len(context):
  558. context[0].Update(value, direction, pattern)
  559. else:
  560. context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
  561. # Use a thread so the UI is not blocked during the SELECT
  562. thread = Thread(self.FindThread, context[0])
  563. thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
  564. thread.start()
  565. def FindDone(self, thread, callback, ids):
  566. callback(ids)
  567. # Vertical widget layout
  568. class VBox():
  569. def __init__(self, w1, w2, w3=None):
  570. self.vbox = QWidget()
  571. self.vbox.setLayout(QVBoxLayout());
  572. self.vbox.layout().setContentsMargins(0, 0, 0, 0)
  573. self.vbox.layout().addWidget(w1)
  574. self.vbox.layout().addWidget(w2)
  575. if w3:
  576. self.vbox.layout().addWidget(w3)
  577. def Widget(self):
  578. return self.vbox
  579. # Context-sensitive call graph window
  580. class CallGraphWindow(QMdiSubWindow):
  581. def __init__(self, glb, parent=None):
  582. super(CallGraphWindow, self).__init__(parent)
  583. self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
  584. self.view = QTreeView()
  585. self.view.setModel(self.model)
  586. for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
  587. self.view.setColumnWidth(c, w)
  588. self.find_bar = FindBar(self, self)
  589. self.vbox = VBox(self.view, self.find_bar.Widget())
  590. self.setWidget(self.vbox.Widget())
  591. AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
  592. def DisplayFound(self, ids):
  593. if not len(ids):
  594. return False
  595. parent = QModelIndex()
  596. for dbid in ids:
  597. found = False
  598. n = self.model.rowCount(parent)
  599. for row in xrange(n):
  600. child = self.model.index(row, 0, parent)
  601. if child.internalPointer().dbid == dbid:
  602. found = True
  603. self.view.setCurrentIndex(child)
  604. parent = child
  605. break
  606. if not found:
  607. break
  608. return found
  609. def Find(self, value, direction, pattern, context):
  610. self.view.setFocus()
  611. self.find_bar.Busy()
  612. self.model.Find(value, direction, pattern, context, self.FindDone)
  613. def FindDone(self, ids):
  614. found = True
  615. if not self.DisplayFound(ids):
  616. found = False
  617. self.find_bar.Idle()
  618. if not found:
  619. self.find_bar.NotFound()
  620. # Child data item finder
  621. class ChildDataItemFinder():
  622. def __init__(self, root):
  623. self.root = root
  624. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
  625. self.rows = []
  626. self.pos = 0
  627. def FindSelect(self):
  628. self.rows = []
  629. if self.pattern:
  630. pattern = re.compile(self.value)
  631. for child in self.root.child_items:
  632. for column_data in child.data:
  633. if re.search(pattern, str(column_data)) is not None:
  634. self.rows.append(child.row)
  635. break
  636. else:
  637. for child in self.root.child_items:
  638. for column_data in child.data:
  639. if self.value in str(column_data):
  640. self.rows.append(child.row)
  641. break
  642. def FindValue(self):
  643. self.pos = 0
  644. if self.last_value != self.value or self.pattern != self.last_pattern:
  645. self.FindSelect()
  646. if not len(self.rows):
  647. return -1
  648. return self.rows[self.pos]
  649. def FindThread(self):
  650. if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
  651. row = self.FindValue()
  652. elif len(self.rows):
  653. if self.direction > 0:
  654. self.pos += 1
  655. if self.pos >= len(self.rows):
  656. self.pos = 0
  657. else:
  658. self.pos -= 1
  659. if self.pos < 0:
  660. self.pos = len(self.rows) - 1
  661. row = self.rows[self.pos]
  662. else:
  663. row = -1
  664. return (True, row)
  665. def Find(self, value, direction, pattern, context, callback):
  666. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
  667. # Use a thread so the UI is not blocked
  668. thread = Thread(self.FindThread)
  669. thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
  670. thread.start()
  671. def FindDone(self, thread, callback, row):
  672. callback(row)
  673. # Number of database records to fetch in one go
  674. glb_chunk_sz = 10000
  675. # size of pickled integer big enough for record size
  676. glb_nsz = 8
  677. # Background process for SQL data fetcher
  678. class SQLFetcherProcess():
  679. def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
  680. # Need a unique connection name
  681. conn_name = "SQLFetcher" + str(os.getpid())
  682. self.db, dbname = dbref.Open(conn_name)
  683. self.sql = sql
  684. self.buffer = buffer
  685. self.head = head
  686. self.tail = tail
  687. self.fetch_count = fetch_count
  688. self.fetching_done = fetching_done
  689. self.process_target = process_target
  690. self.wait_event = wait_event
  691. self.fetched_event = fetched_event
  692. self.prep = prep
  693. self.query = QSqlQuery(self.db)
  694. self.query_limit = 0 if "$$last_id$$" in sql else 2
  695. self.last_id = -1
  696. self.fetched = 0
  697. self.more = True
  698. self.local_head = self.head.value
  699. self.local_tail = self.tail.value
  700. def Select(self):
  701. if self.query_limit:
  702. if self.query_limit == 1:
  703. return
  704. self.query_limit -= 1
  705. stmt = self.sql.replace("$$last_id$$", str(self.last_id))
  706. QueryExec(self.query, stmt)
  707. def Next(self):
  708. if not self.query.next():
  709. self.Select()
  710. if not self.query.next():
  711. return None
  712. self.last_id = self.query.value(0)
  713. return self.prep(self.query)
  714. def WaitForTarget(self):
  715. while True:
  716. self.wait_event.clear()
  717. target = self.process_target.value
  718. if target > self.fetched or target < 0:
  719. break
  720. self.wait_event.wait()
  721. return target
  722. def HasSpace(self, sz):
  723. if self.local_tail <= self.local_head:
  724. space = len(self.buffer) - self.local_head
  725. if space > sz:
  726. return True
  727. if space >= glb_nsz:
  728. # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
  729. nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
  730. self.buffer[self.local_head : self.local_head + len(nd)] = nd
  731. self.local_head = 0
  732. if self.local_tail - self.local_head > sz:
  733. return True
  734. return False
  735. def WaitForSpace(self, sz):
  736. if self.HasSpace(sz):
  737. return
  738. while True:
  739. self.wait_event.clear()
  740. self.local_tail = self.tail.value
  741. if self.HasSpace(sz):
  742. return
  743. self.wait_event.wait()
  744. def AddToBuffer(self, obj):
  745. d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
  746. n = len(d)
  747. nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
  748. sz = n + glb_nsz
  749. self.WaitForSpace(sz)
  750. pos = self.local_head
  751. self.buffer[pos : pos + len(nd)] = nd
  752. self.buffer[pos + glb_nsz : pos + sz] = d
  753. self.local_head += sz
  754. def FetchBatch(self, batch_size):
  755. fetched = 0
  756. while batch_size > fetched:
  757. obj = self.Next()
  758. if obj is None:
  759. self.more = False
  760. break
  761. self.AddToBuffer(obj)
  762. fetched += 1
  763. if fetched:
  764. self.fetched += fetched
  765. with self.fetch_count.get_lock():
  766. self.fetch_count.value += fetched
  767. self.head.value = self.local_head
  768. self.fetched_event.set()
  769. def Run(self):
  770. while self.more:
  771. target = self.WaitForTarget()
  772. if target < 0:
  773. break
  774. batch_size = min(glb_chunk_sz, target - self.fetched)
  775. self.FetchBatch(batch_size)
  776. self.fetching_done.value = True
  777. self.fetched_event.set()
  778. def SQLFetcherFn(*x):
  779. process = SQLFetcherProcess(*x)
  780. process.Run()
  781. # SQL data fetcher
  782. class SQLFetcher(QObject):
  783. done = Signal(object)
  784. def __init__(self, glb, sql, prep, process_data, parent=None):
  785. super(SQLFetcher, self).__init__(parent)
  786. self.process_data = process_data
  787. self.more = True
  788. self.target = 0
  789. self.last_target = 0
  790. self.fetched = 0
  791. self.buffer_size = 16 * 1024 * 1024
  792. self.buffer = Array(c_char, self.buffer_size, lock=False)
  793. self.head = Value(c_longlong)
  794. self.tail = Value(c_longlong)
  795. self.local_tail = 0
  796. self.fetch_count = Value(c_longlong)
  797. self.fetching_done = Value(c_bool)
  798. self.last_count = 0
  799. self.process_target = Value(c_longlong)
  800. self.wait_event = Event()
  801. self.fetched_event = Event()
  802. glb.AddInstanceToShutdownOnExit(self)
  803. 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))
  804. self.process.start()
  805. self.thread = Thread(self.Thread)
  806. self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
  807. self.thread.start()
  808. def Shutdown(self):
  809. # Tell the thread and process to exit
  810. self.process_target.value = -1
  811. self.wait_event.set()
  812. self.more = False
  813. self.fetching_done.value = True
  814. self.fetched_event.set()
  815. def Thread(self):
  816. if not self.more:
  817. return True, 0
  818. while True:
  819. self.fetched_event.clear()
  820. fetch_count = self.fetch_count.value
  821. if fetch_count != self.last_count:
  822. break
  823. if self.fetching_done.value:
  824. self.more = False
  825. return True, 0
  826. self.fetched_event.wait()
  827. count = fetch_count - self.last_count
  828. self.last_count = fetch_count
  829. self.fetched += count
  830. return False, count
  831. def Fetch(self, nr):
  832. if not self.more:
  833. # -1 inidcates there are no more
  834. return -1
  835. result = self.fetched
  836. extra = result + nr - self.target
  837. if extra > 0:
  838. self.target += extra
  839. # process_target < 0 indicates shutting down
  840. if self.process_target.value >= 0:
  841. self.process_target.value = self.target
  842. self.wait_event.set()
  843. return result
  844. def RemoveFromBuffer(self):
  845. pos = self.local_tail
  846. if len(self.buffer) - pos < glb_nsz:
  847. pos = 0
  848. n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
  849. if n == 0:
  850. pos = 0
  851. n = cPickle.loads(self.buffer[0 : glb_nsz])
  852. pos += glb_nsz
  853. obj = cPickle.loads(self.buffer[pos : pos + n])
  854. self.local_tail = pos + n
  855. return obj
  856. def ProcessData(self, count):
  857. for i in xrange(count):
  858. obj = self.RemoveFromBuffer()
  859. self.process_data(obj)
  860. self.tail.value = self.local_tail
  861. self.wait_event.set()
  862. self.done.emit(count)
  863. # Fetch more records bar
  864. class FetchMoreRecordsBar():
  865. def __init__(self, model, parent):
  866. self.model = model
  867. self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
  868. self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  869. self.fetch_count = QSpinBox()
  870. self.fetch_count.setRange(1, 1000000)
  871. self.fetch_count.setValue(10)
  872. self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  873. self.fetch = QPushButton("Go!")
  874. self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  875. self.fetch.released.connect(self.FetchMoreRecords)
  876. self.progress = QProgressBar()
  877. self.progress.setRange(0, 100)
  878. self.progress.hide()
  879. self.done_label = QLabel("All records fetched")
  880. self.done_label.hide()
  881. self.spacer = QLabel("")
  882. self.close_button = QToolButton()
  883. self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
  884. self.close_button.released.connect(self.Deactivate)
  885. self.hbox = QHBoxLayout()
  886. self.hbox.setContentsMargins(0, 0, 0, 0)
  887. self.hbox.addWidget(self.label)
  888. self.hbox.addWidget(self.fetch_count)
  889. self.hbox.addWidget(self.fetch)
  890. self.hbox.addWidget(self.spacer)
  891. self.hbox.addWidget(self.progress)
  892. self.hbox.addWidget(self.done_label)
  893. self.hbox.addWidget(self.close_button)
  894. self.bar = QWidget()
  895. self.bar.setLayout(self.hbox);
  896. self.bar.show()
  897. self.in_progress = False
  898. self.model.progress.connect(self.Progress)
  899. self.done = False
  900. if not model.HasMoreRecords():
  901. self.Done()
  902. def Widget(self):
  903. return self.bar
  904. def Activate(self):
  905. self.bar.show()
  906. self.fetch.setFocus()
  907. def Deactivate(self):
  908. self.bar.hide()
  909. def Enable(self, enable):
  910. self.fetch.setEnabled(enable)
  911. self.fetch_count.setEnabled(enable)
  912. def Busy(self):
  913. self.Enable(False)
  914. self.fetch.hide()
  915. self.spacer.hide()
  916. self.progress.show()
  917. def Idle(self):
  918. self.in_progress = False
  919. self.Enable(True)
  920. self.progress.hide()
  921. self.fetch.show()
  922. self.spacer.show()
  923. def Target(self):
  924. return self.fetch_count.value() * glb_chunk_sz
  925. def Done(self):
  926. self.done = True
  927. self.Idle()
  928. self.label.hide()
  929. self.fetch_count.hide()
  930. self.fetch.hide()
  931. self.spacer.hide()
  932. self.done_label.show()
  933. def Progress(self, count):
  934. if self.in_progress:
  935. if count:
  936. percent = ((count - self.start) * 100) / self.Target()
  937. if percent >= 100:
  938. self.Idle()
  939. else:
  940. self.progress.setValue(percent)
  941. if not count:
  942. # Count value of zero means no more records
  943. self.Done()
  944. def FetchMoreRecords(self):
  945. if self.done:
  946. return
  947. self.progress.setValue(0)
  948. self.Busy()
  949. self.in_progress = True
  950. self.start = self.model.FetchMoreRecords(self.Target())
  951. # Brance data model level two item
  952. class BranchLevelTwoItem():
  953. def __init__(self, row, text, parent_item):
  954. self.row = row
  955. self.parent_item = parent_item
  956. self.data = [""] * 8
  957. self.data[7] = text
  958. self.level = 2
  959. def getParentItem(self):
  960. return self.parent_item
  961. def getRow(self):
  962. return self.row
  963. def childCount(self):
  964. return 0
  965. def hasChildren(self):
  966. return False
  967. def getData(self, column):
  968. return self.data[column]
  969. # Brance data model level one item
  970. class BranchLevelOneItem():
  971. def __init__(self, glb, row, data, parent_item):
  972. self.glb = glb
  973. self.row = row
  974. self.parent_item = parent_item
  975. self.child_count = 0
  976. self.child_items = []
  977. self.data = data[1:]
  978. self.dbid = data[0]
  979. self.level = 1
  980. self.query_done = False
  981. def getChildItem(self, row):
  982. return self.child_items[row]
  983. def getParentItem(self):
  984. return self.parent_item
  985. def getRow(self):
  986. return self.row
  987. def Select(self):
  988. self.query_done = True
  989. if not self.glb.have_disassembler:
  990. return
  991. query = QSqlQuery(self.glb.db)
  992. QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
  993. " FROM samples"
  994. " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
  995. " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
  996. " WHERE samples.id = " + str(self.dbid))
  997. if not query.next():
  998. return
  999. cpu = query.value(0)
  1000. dso = query.value(1)
  1001. sym = query.value(2)
  1002. if dso == 0 or sym == 0:
  1003. return
  1004. off = query.value(3)
  1005. short_name = query.value(4)
  1006. long_name = query.value(5)
  1007. build_id = query.value(6)
  1008. sym_start = query.value(7)
  1009. ip = query.value(8)
  1010. QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
  1011. " FROM samples"
  1012. " INNER JOIN symbols ON samples.symbol_id = symbols.id"
  1013. " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
  1014. " ORDER BY samples.id"
  1015. " LIMIT 1")
  1016. if not query.next():
  1017. return
  1018. if query.value(0) != dso:
  1019. # Cannot disassemble from one dso to another
  1020. return
  1021. bsym = query.value(1)
  1022. boff = query.value(2)
  1023. bsym_start = query.value(3)
  1024. if bsym == 0:
  1025. return
  1026. tot = bsym_start + boff + 1 - sym_start - off
  1027. if tot <= 0 or tot > 16384:
  1028. return
  1029. inst = self.glb.disassembler.Instruction()
  1030. f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
  1031. if not f:
  1032. return
  1033. mode = 0 if Is64Bit(f) else 1
  1034. self.glb.disassembler.SetMode(inst, mode)
  1035. buf_sz = tot + 16
  1036. buf = create_string_buffer(tot + 16)
  1037. f.seek(sym_start + off)
  1038. buf.value = f.read(buf_sz)
  1039. buf_ptr = addressof(buf)
  1040. i = 0
  1041. while tot > 0:
  1042. cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
  1043. if cnt:
  1044. byte_str = tohex(ip).rjust(16)
  1045. for k in xrange(cnt):
  1046. byte_str += " %02x" % ord(buf[i])
  1047. i += 1
  1048. while k < 15:
  1049. byte_str += " "
  1050. k += 1
  1051. self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
  1052. self.child_count += 1
  1053. else:
  1054. return
  1055. buf_ptr += cnt
  1056. tot -= cnt
  1057. buf_sz -= cnt
  1058. ip += cnt
  1059. def childCount(self):
  1060. if not self.query_done:
  1061. self.Select()
  1062. if not self.child_count:
  1063. return -1
  1064. return self.child_count
  1065. def hasChildren(self):
  1066. if not self.query_done:
  1067. return True
  1068. return self.child_count > 0
  1069. def getData(self, column):
  1070. return self.data[column]
  1071. # Brance data model root item
  1072. class BranchRootItem():
  1073. def __init__(self):
  1074. self.child_count = 0
  1075. self.child_items = []
  1076. self.level = 0
  1077. def getChildItem(self, row):
  1078. return self.child_items[row]
  1079. def getParentItem(self):
  1080. return None
  1081. def getRow(self):
  1082. return 0
  1083. def childCount(self):
  1084. return self.child_count
  1085. def hasChildren(self):
  1086. return self.child_count > 0
  1087. def getData(self, column):
  1088. return ""
  1089. # Branch data preparation
  1090. def BranchDataPrep(query):
  1091. data = []
  1092. for i in xrange(0, 8):
  1093. data.append(query.value(i))
  1094. data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
  1095. " (" + dsoname(query.value(11)) + ")" + " -> " +
  1096. tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
  1097. " (" + dsoname(query.value(15)) + ")")
  1098. return data
  1099. # Branch data model
  1100. class BranchModel(TreeModel):
  1101. progress = Signal(object)
  1102. def __init__(self, glb, event_id, where_clause, parent=None):
  1103. super(BranchModel, self).__init__(BranchRootItem(), parent)
  1104. self.glb = glb
  1105. self.event_id = event_id
  1106. self.more = True
  1107. self.populated = 0
  1108. sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
  1109. " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
  1110. " ip, symbols.name, sym_offset, dsos.short_name,"
  1111. " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
  1112. " FROM samples"
  1113. " INNER JOIN comms ON comm_id = comms.id"
  1114. " INNER JOIN threads ON thread_id = threads.id"
  1115. " INNER JOIN branch_types ON branch_type = branch_types.id"
  1116. " INNER JOIN symbols ON symbol_id = symbols.id"
  1117. " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
  1118. " INNER JOIN dsos ON samples.dso_id = dsos.id"
  1119. " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
  1120. " WHERE samples.id > $$last_id$$" + where_clause +
  1121. " AND evsel_id = " + str(self.event_id) +
  1122. " ORDER BY samples.id"
  1123. " LIMIT " + str(glb_chunk_sz))
  1124. self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
  1125. self.fetcher.done.connect(self.Update)
  1126. self.fetcher.Fetch(glb_chunk_sz)
  1127. def columnCount(self, parent=None):
  1128. return 8
  1129. def columnHeader(self, column):
  1130. return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
  1131. def columnFont(self, column):
  1132. if column != 7:
  1133. return None
  1134. return QFont("Monospace")
  1135. def DisplayData(self, item, index):
  1136. if item.level == 1:
  1137. self.FetchIfNeeded(item.row)
  1138. return item.getData(index.column())
  1139. def AddSample(self, data):
  1140. child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
  1141. self.root.child_items.append(child)
  1142. self.populated += 1
  1143. def Update(self, fetched):
  1144. if not fetched:
  1145. self.more = False
  1146. self.progress.emit(0)
  1147. child_count = self.root.child_count
  1148. count = self.populated - child_count
  1149. if count > 0:
  1150. parent = QModelIndex()
  1151. self.beginInsertRows(parent, child_count, child_count + count - 1)
  1152. self.insertRows(child_count, count, parent)
  1153. self.root.child_count += count
  1154. self.endInsertRows()
  1155. self.progress.emit(self.root.child_count)
  1156. def FetchMoreRecords(self, count):
  1157. current = self.root.child_count
  1158. if self.more:
  1159. self.fetcher.Fetch(count)
  1160. else:
  1161. self.progress.emit(0)
  1162. return current
  1163. def HasMoreRecords(self):
  1164. return self.more
  1165. # Branch window
  1166. class BranchWindow(QMdiSubWindow):
  1167. def __init__(self, glb, event_id, name, where_clause, parent=None):
  1168. super(BranchWindow, self).__init__(parent)
  1169. model_name = "Branch Events " + str(event_id)
  1170. if len(where_clause):
  1171. model_name = where_clause + " " + model_name
  1172. self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause))
  1173. self.view = QTreeView()
  1174. self.view.setUniformRowHeights(True)
  1175. self.view.setModel(self.model)
  1176. self.ResizeColumnsToContents()
  1177. self.find_bar = FindBar(self, self, True)
  1178. self.finder = ChildDataItemFinder(self.model.root)
  1179. self.fetch_bar = FetchMoreRecordsBar(self.model, self)
  1180. self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
  1181. self.setWidget(self.vbox.Widget())
  1182. AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events")
  1183. def ResizeColumnToContents(self, column, n):
  1184. # Using the view's resizeColumnToContents() here is extrememly slow
  1185. # so implement a crude alternative
  1186. mm = "MM" if column else "MMMM"
  1187. font = self.view.font()
  1188. metrics = QFontMetrics(font)
  1189. max = 0
  1190. for row in xrange(n):
  1191. val = self.model.root.child_items[row].data[column]
  1192. len = metrics.width(str(val) + mm)
  1193. max = len if len > max else max
  1194. val = self.model.columnHeader(column)
  1195. len = metrics.width(str(val) + mm)
  1196. max = len if len > max else max
  1197. self.view.setColumnWidth(column, max)
  1198. def ResizeColumnsToContents(self):
  1199. n = min(self.model.root.child_count, 100)
  1200. if n < 1:
  1201. # No data yet, so connect a signal to notify when there is
  1202. self.model.rowsInserted.connect(self.UpdateColumnWidths)
  1203. return
  1204. columns = self.model.columnCount()
  1205. for i in xrange(columns):
  1206. self.ResizeColumnToContents(i, n)
  1207. def UpdateColumnWidths(self, *x):
  1208. # This only needs to be done once, so disconnect the signal now
  1209. self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
  1210. self.ResizeColumnsToContents()
  1211. def Find(self, value, direction, pattern, context):
  1212. self.view.setFocus()
  1213. self.find_bar.Busy()
  1214. self.finder.Find(value, direction, pattern, context, self.FindDone)
  1215. def FindDone(self, row):
  1216. self.find_bar.Idle()
  1217. if row >= 0:
  1218. self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
  1219. else:
  1220. self.find_bar.NotFound()
  1221. # Dialog data item converted and validated using a SQL table
  1222. class SQLTableDialogDataItem():
  1223. def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
  1224. self.glb = glb
  1225. self.label = label
  1226. self.placeholder_text = placeholder_text
  1227. self.table_name = table_name
  1228. self.match_column = match_column
  1229. self.column_name1 = column_name1
  1230. self.column_name2 = column_name2
  1231. self.parent = parent
  1232. self.value = ""
  1233. self.widget = QLineEdit()
  1234. self.widget.editingFinished.connect(self.Validate)
  1235. self.widget.textChanged.connect(self.Invalidate)
  1236. self.red = False
  1237. self.error = ""
  1238. self.validated = True
  1239. self.last_id = 0
  1240. self.first_time = 0
  1241. self.last_time = 2 ** 64
  1242. if self.table_name == "<timeranges>":
  1243. query = QSqlQuery(self.glb.db)
  1244. QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
  1245. if query.next():
  1246. self.last_id = int(query.value(0))
  1247. self.last_time = int(query.value(1))
  1248. QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
  1249. if query.next():
  1250. self.first_time = int(query.value(0))
  1251. if placeholder_text:
  1252. placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
  1253. if placeholder_text:
  1254. self.widget.setPlaceholderText(placeholder_text)
  1255. def ValueToIds(self, value):
  1256. ids = []
  1257. query = QSqlQuery(self.glb.db)
  1258. stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
  1259. ret = query.exec_(stmt)
  1260. if ret:
  1261. while query.next():
  1262. ids.append(str(query.value(0)))
  1263. return ids
  1264. def IdBetween(self, query, lower_id, higher_id, order):
  1265. QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
  1266. if query.next():
  1267. return True, int(query.value(0))
  1268. else:
  1269. return False, 0
  1270. def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
  1271. query = QSqlQuery(self.glb.db)
  1272. while True:
  1273. next_id = int((lower_id + higher_id) / 2)
  1274. QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
  1275. if not query.next():
  1276. ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
  1277. if not ok:
  1278. ok, dbid = self.IdBetween(query, next_id, higher_id, "")
  1279. if not ok:
  1280. return str(higher_id)
  1281. next_id = dbid
  1282. QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
  1283. next_time = int(query.value(0))
  1284. if get_floor:
  1285. if target_time > next_time:
  1286. lower_id = next_id
  1287. else:
  1288. higher_id = next_id
  1289. if higher_id <= lower_id + 1:
  1290. return str(higher_id)
  1291. else:
  1292. if target_time >= next_time:
  1293. lower_id = next_id
  1294. else:
  1295. higher_id = next_id
  1296. if higher_id <= lower_id + 1:
  1297. return str(lower_id)
  1298. def ConvertRelativeTime(self, val):
  1299. print "val ", val
  1300. mult = 1
  1301. suffix = val[-2:]
  1302. if suffix == "ms":
  1303. mult = 1000000
  1304. elif suffix == "us":
  1305. mult = 1000
  1306. elif suffix == "ns":
  1307. mult = 1
  1308. else:
  1309. return val
  1310. val = val[:-2].strip()
  1311. if not self.IsNumber(val):
  1312. return val
  1313. val = int(val) * mult
  1314. if val >= 0:
  1315. val += self.first_time
  1316. else:
  1317. val += self.last_time
  1318. return str(val)
  1319. def ConvertTimeRange(self, vrange):
  1320. print "vrange ", vrange
  1321. if vrange[0] == "":
  1322. vrange[0] = str(self.first_time)
  1323. if vrange[1] == "":
  1324. vrange[1] = str(self.last_time)
  1325. vrange[0] = self.ConvertRelativeTime(vrange[0])
  1326. vrange[1] = self.ConvertRelativeTime(vrange[1])
  1327. print "vrange2 ", vrange
  1328. if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
  1329. return False
  1330. print "ok1"
  1331. beg_range = max(int(vrange[0]), self.first_time)
  1332. end_range = min(int(vrange[1]), self.last_time)
  1333. if beg_range > self.last_time or end_range < self.first_time:
  1334. return False
  1335. print "ok2"
  1336. vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
  1337. vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
  1338. print "vrange3 ", vrange
  1339. return True
  1340. def AddTimeRange(self, value, ranges):
  1341. print "value ", value
  1342. n = value.count("-")
  1343. if n == 1:
  1344. pass
  1345. elif n == 2:
  1346. if value.split("-")[1].strip() == "":
  1347. n = 1
  1348. elif n == 3:
  1349. n = 2
  1350. else:
  1351. return False
  1352. pos = findnth(value, "-", n)
  1353. vrange = [value[:pos].strip() ,value[pos+1:].strip()]
  1354. if self.ConvertTimeRange(vrange):
  1355. ranges.append(vrange)
  1356. return True
  1357. return False
  1358. def InvalidValue(self, value):
  1359. self.value = ""
  1360. palette = QPalette()
  1361. palette.setColor(QPalette.Text,Qt.red)
  1362. self.widget.setPalette(palette)
  1363. self.red = True
  1364. self.error = self.label + " invalid value '" + value + "'"
  1365. self.parent.ShowMessage(self.error)
  1366. def IsNumber(self, value):
  1367. try:
  1368. x = int(value)
  1369. except:
  1370. x = 0
  1371. return str(x) == value
  1372. def Invalidate(self):
  1373. self.validated = False
  1374. def Validate(self):
  1375. input_string = self.widget.text()
  1376. self.validated = True
  1377. if self.red:
  1378. palette = QPalette()
  1379. self.widget.setPalette(palette)
  1380. self.red = False
  1381. if not len(input_string.strip()):
  1382. self.error = ""
  1383. self.value = ""
  1384. return
  1385. if self.table_name == "<timeranges>":
  1386. ranges = []
  1387. for value in [x.strip() for x in input_string.split(",")]:
  1388. if not self.AddTimeRange(value, ranges):
  1389. return self.InvalidValue(value)
  1390. ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
  1391. self.value = " OR ".join(ranges)
  1392. elif self.table_name == "<ranges>":
  1393. singles = []
  1394. ranges = []
  1395. for value in [x.strip() for x in input_string.split(",")]:
  1396. if "-" in value:
  1397. vrange = value.split("-")
  1398. if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
  1399. return self.InvalidValue(value)
  1400. ranges.append(vrange)
  1401. else:
  1402. if not self.IsNumber(value):
  1403. return self.InvalidValue(value)
  1404. singles.append(value)
  1405. ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
  1406. if len(singles):
  1407. ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")")
  1408. self.value = " OR ".join(ranges)
  1409. elif self.table_name:
  1410. all_ids = []
  1411. for value in [x.strip() for x in input_string.split(",")]:
  1412. ids = self.ValueToIds(value)
  1413. if len(ids):
  1414. all_ids.extend(ids)
  1415. else:
  1416. return self.InvalidValue(value)
  1417. self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
  1418. if self.column_name2:
  1419. self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
  1420. else:
  1421. self.value = input_string.strip()
  1422. self.error = ""
  1423. self.parent.ClearMessage()
  1424. def IsValid(self):
  1425. if not self.validated:
  1426. self.Validate()
  1427. if len(self.error):
  1428. self.parent.ShowMessage(self.error)
  1429. return False
  1430. return True
  1431. # Selected branch report creation dialog
  1432. class SelectedBranchDialog(QDialog):
  1433. def __init__(self, glb, parent=None):
  1434. super(SelectedBranchDialog, self).__init__(parent)
  1435. self.glb = glb
  1436. self.name = ""
  1437. self.where_clause = ""
  1438. self.setWindowTitle("Selected Branches")
  1439. self.setMinimumWidth(600)
  1440. items = (
  1441. ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""),
  1442. ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""),
  1443. ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""),
  1444. ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""),
  1445. ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""),
  1446. ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""),
  1447. ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"),
  1448. ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"),
  1449. ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""),
  1450. )
  1451. self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items]
  1452. self.grid = QGridLayout()
  1453. for row in xrange(len(self.data_items)):
  1454. self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
  1455. self.grid.addWidget(self.data_items[row].widget, row, 1)
  1456. self.status = QLabel()
  1457. self.ok_button = QPushButton("Ok", self)
  1458. self.ok_button.setDefault(True)
  1459. self.ok_button.released.connect(self.Ok)
  1460. self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  1461. self.cancel_button = QPushButton("Cancel", self)
  1462. self.cancel_button.released.connect(self.reject)
  1463. self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  1464. self.hbox = QHBoxLayout()
  1465. #self.hbox.addStretch()
  1466. self.hbox.addWidget(self.status)
  1467. self.hbox.addWidget(self.ok_button)
  1468. self.hbox.addWidget(self.cancel_button)
  1469. self.vbox = QVBoxLayout()
  1470. self.vbox.addLayout(self.grid)
  1471. self.vbox.addLayout(self.hbox)
  1472. self.setLayout(self.vbox);
  1473. def Ok(self):
  1474. self.name = self.data_items[0].value
  1475. if not self.name:
  1476. self.ShowMessage("Report name is required")
  1477. return
  1478. for d in self.data_items:
  1479. if not d.IsValid():
  1480. return
  1481. for d in self.data_items[1:]:
  1482. if len(d.value):
  1483. if len(self.where_clause):
  1484. self.where_clause += " AND "
  1485. self.where_clause += d.value
  1486. if len(self.where_clause):
  1487. self.where_clause = " AND ( " + self.where_clause + " ) "
  1488. else:
  1489. self.ShowMessage("No selection")
  1490. return
  1491. self.accept()
  1492. def ShowMessage(self, msg):
  1493. self.status.setText("<font color=#FF0000>" + msg)
  1494. def ClearMessage(self):
  1495. self.status.setText("")
  1496. # Event list
  1497. def GetEventList(db):
  1498. events = []
  1499. query = QSqlQuery(db)
  1500. QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
  1501. while query.next():
  1502. events.append(query.value(0))
  1503. return events
  1504. # SQL data preparation
  1505. def SQLTableDataPrep(query, count):
  1506. data = []
  1507. for i in xrange(count):
  1508. data.append(query.value(i))
  1509. return data
  1510. # SQL table data model item
  1511. class SQLTableItem():
  1512. def __init__(self, row, data):
  1513. self.row = row
  1514. self.data = data
  1515. def getData(self, column):
  1516. return self.data[column]
  1517. # SQL table data model
  1518. class SQLTableModel(TableModel):
  1519. progress = Signal(object)
  1520. def __init__(self, glb, sql, column_count, parent=None):
  1521. super(SQLTableModel, self).__init__(parent)
  1522. self.glb = glb
  1523. self.more = True
  1524. self.populated = 0
  1525. self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
  1526. self.fetcher.done.connect(self.Update)
  1527. self.fetcher.Fetch(glb_chunk_sz)
  1528. def DisplayData(self, item, index):
  1529. self.FetchIfNeeded(item.row)
  1530. return item.getData(index.column())
  1531. def AddSample(self, data):
  1532. child = SQLTableItem(self.populated, data)
  1533. self.child_items.append(child)
  1534. self.populated += 1
  1535. def Update(self, fetched):
  1536. if not fetched:
  1537. self.more = False
  1538. self.progress.emit(0)
  1539. child_count = self.child_count
  1540. count = self.populated - child_count
  1541. if count > 0:
  1542. parent = QModelIndex()
  1543. self.beginInsertRows(parent, child_count, child_count + count - 1)
  1544. self.insertRows(child_count, count, parent)
  1545. self.child_count += count
  1546. self.endInsertRows()
  1547. self.progress.emit(self.child_count)
  1548. def FetchMoreRecords(self, count):
  1549. current = self.child_count
  1550. if self.more:
  1551. self.fetcher.Fetch(count)
  1552. else:
  1553. self.progress.emit(0)
  1554. return current
  1555. def HasMoreRecords(self):
  1556. return self.more
  1557. # SQL automatic table data model
  1558. class SQLAutoTableModel(SQLTableModel):
  1559. def __init__(self, glb, table_name, parent=None):
  1560. sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
  1561. if table_name == "comm_threads_view":
  1562. # For now, comm_threads_view has no id column
  1563. sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
  1564. self.column_headers = []
  1565. query = QSqlQuery(glb.db)
  1566. if glb.dbref.is_sqlite3:
  1567. QueryExec(query, "PRAGMA table_info(" + table_name + ")")
  1568. while query.next():
  1569. self.column_headers.append(query.value(1))
  1570. if table_name == "sqlite_master":
  1571. sql = "SELECT * FROM " + table_name
  1572. else:
  1573. if table_name[:19] == "information_schema.":
  1574. sql = "SELECT * FROM " + table_name
  1575. select_table_name = table_name[19:]
  1576. schema = "information_schema"
  1577. else:
  1578. select_table_name = table_name
  1579. schema = "public"
  1580. QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
  1581. while query.next():
  1582. self.column_headers.append(query.value(0))
  1583. super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
  1584. def columnCount(self, parent=None):
  1585. return len(self.column_headers)
  1586. def columnHeader(self, column):
  1587. return self.column_headers[column]
  1588. # Base class for custom ResizeColumnsToContents
  1589. class ResizeColumnsToContentsBase(QObject):
  1590. def __init__(self, parent=None):
  1591. super(ResizeColumnsToContentsBase, self).__init__(parent)
  1592. def ResizeColumnToContents(self, column, n):
  1593. # Using the view's resizeColumnToContents() here is extrememly slow
  1594. # so implement a crude alternative
  1595. font = self.view.font()
  1596. metrics = QFontMetrics(font)
  1597. max = 0
  1598. for row in xrange(n):
  1599. val = self.data_model.child_items[row].data[column]
  1600. len = metrics.width(str(val) + "MM")
  1601. max = len if len > max else max
  1602. val = self.data_model.columnHeader(column)
  1603. len = metrics.width(str(val) + "MM")
  1604. max = len if len > max else max
  1605. self.view.setColumnWidth(column, max)
  1606. def ResizeColumnsToContents(self):
  1607. n = min(self.data_model.child_count, 100)
  1608. if n < 1:
  1609. # No data yet, so connect a signal to notify when there is
  1610. self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
  1611. return
  1612. columns = self.data_model.columnCount()
  1613. for i in xrange(columns):
  1614. self.ResizeColumnToContents(i, n)
  1615. def UpdateColumnWidths(self, *x):
  1616. # This only needs to be done once, so disconnect the signal now
  1617. self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
  1618. self.ResizeColumnsToContents()
  1619. # Table window
  1620. class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
  1621. def __init__(self, glb, table_name, parent=None):
  1622. super(TableWindow, self).__init__(parent)
  1623. self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
  1624. self.model = QSortFilterProxyModel()
  1625. self.model.setSourceModel(self.data_model)
  1626. self.view = QTableView()
  1627. self.view.setModel(self.model)
  1628. self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
  1629. self.view.verticalHeader().setVisible(False)
  1630. self.view.sortByColumn(-1, Qt.AscendingOrder)
  1631. self.view.setSortingEnabled(True)
  1632. self.ResizeColumnsToContents()
  1633. self.find_bar = FindBar(self, self, True)
  1634. self.finder = ChildDataItemFinder(self.data_model)
  1635. self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
  1636. self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
  1637. self.setWidget(self.vbox.Widget())
  1638. AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
  1639. def Find(self, value, direction, pattern, context):
  1640. self.view.setFocus()
  1641. self.find_bar.Busy()
  1642. self.finder.Find(value, direction, pattern, context, self.FindDone)
  1643. def FindDone(self, row):
  1644. self.find_bar.Idle()
  1645. if row >= 0:
  1646. self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
  1647. else:
  1648. self.find_bar.NotFound()
  1649. # Table list
  1650. def GetTableList(glb):
  1651. tables = []
  1652. query = QSqlQuery(glb.db)
  1653. if glb.dbref.is_sqlite3:
  1654. QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
  1655. else:
  1656. QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
  1657. while query.next():
  1658. tables.append(query.value(0))
  1659. if glb.dbref.is_sqlite3:
  1660. tables.append("sqlite_master")
  1661. else:
  1662. tables.append("information_schema.tables")
  1663. tables.append("information_schema.views")
  1664. tables.append("information_schema.columns")
  1665. return tables
  1666. # Action Definition
  1667. def CreateAction(label, tip, callback, parent=None, shortcut=None):
  1668. action = QAction(label, parent)
  1669. if shortcut != None:
  1670. action.setShortcuts(shortcut)
  1671. action.setStatusTip(tip)
  1672. action.triggered.connect(callback)
  1673. return action
  1674. # Typical application actions
  1675. def CreateExitAction(app, parent=None):
  1676. return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
  1677. # Typical MDI actions
  1678. def CreateCloseActiveWindowAction(mdi_area):
  1679. return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
  1680. def CreateCloseAllWindowsAction(mdi_area):
  1681. return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
  1682. def CreateTileWindowsAction(mdi_area):
  1683. return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
  1684. def CreateCascadeWindowsAction(mdi_area):
  1685. return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
  1686. def CreateNextWindowAction(mdi_area):
  1687. return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
  1688. def CreatePreviousWindowAction(mdi_area):
  1689. return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
  1690. # Typical MDI window menu
  1691. class WindowMenu():
  1692. def __init__(self, mdi_area, menu):
  1693. self.mdi_area = mdi_area
  1694. self.window_menu = menu.addMenu("&Windows")
  1695. self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
  1696. self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
  1697. self.tile_windows = CreateTileWindowsAction(mdi_area)
  1698. self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
  1699. self.next_window = CreateNextWindowAction(mdi_area)
  1700. self.previous_window = CreatePreviousWindowAction(mdi_area)
  1701. self.window_menu.aboutToShow.connect(self.Update)
  1702. def Update(self):
  1703. self.window_menu.clear()
  1704. sub_window_count = len(self.mdi_area.subWindowList())
  1705. have_sub_windows = sub_window_count != 0
  1706. self.close_active_window.setEnabled(have_sub_windows)
  1707. self.close_all_windows.setEnabled(have_sub_windows)
  1708. self.tile_windows.setEnabled(have_sub_windows)
  1709. self.cascade_windows.setEnabled(have_sub_windows)
  1710. self.next_window.setEnabled(have_sub_windows)
  1711. self.previous_window.setEnabled(have_sub_windows)
  1712. self.window_menu.addAction(self.close_active_window)
  1713. self.window_menu.addAction(self.close_all_windows)
  1714. self.window_menu.addSeparator()
  1715. self.window_menu.addAction(self.tile_windows)
  1716. self.window_menu.addAction(self.cascade_windows)
  1717. self.window_menu.addSeparator()
  1718. self.window_menu.addAction(self.next_window)
  1719. self.window_menu.addAction(self.previous_window)
  1720. if sub_window_count == 0:
  1721. return
  1722. self.window_menu.addSeparator()
  1723. nr = 1
  1724. for sub_window in self.mdi_area.subWindowList():
  1725. label = str(nr) + " " + sub_window.name
  1726. if nr < 10:
  1727. label = "&" + label
  1728. action = self.window_menu.addAction(label)
  1729. action.setCheckable(True)
  1730. action.setChecked(sub_window == self.mdi_area.activeSubWindow())
  1731. action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
  1732. self.window_menu.addAction(action)
  1733. nr += 1
  1734. def setActiveSubWindow(self, nr):
  1735. self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
  1736. # Help text
  1737. glb_help_text = """
  1738. <h1>Contents</h1>
  1739. <style>
  1740. p.c1 {
  1741. text-indent: 40px;
  1742. }
  1743. p.c2 {
  1744. text-indent: 80px;
  1745. }
  1746. }
  1747. </style>
  1748. <p class=c1><a href=#reports>1. Reports</a></p>
  1749. <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
  1750. <p class=c2><a href=#allbranches>1.2 All branches</a></p>
  1751. <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
  1752. <p class=c1><a href=#tables>2. Tables</a></p>
  1753. <h1 id=reports>1. Reports</h1>
  1754. <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
  1755. The result is a GUI window with a tree representing a context-sensitive
  1756. call-graph. Expanding a couple of levels of the tree and adjusting column
  1757. widths to suit will display something like:
  1758. <pre>
  1759. Call Graph: pt_example
  1760. Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
  1761. v- ls
  1762. v- 2638:2638
  1763. v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
  1764. |- unknown unknown 1 13198 0.1 1 0.0
  1765. >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
  1766. >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
  1767. v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
  1768. >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
  1769. >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
  1770. >- __libc_csu_init ls 1 10354 0.1 10 0.0
  1771. |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
  1772. v- main ls 1 8182043 99.6 180254 99.9
  1773. </pre>
  1774. <h3>Points to note:</h3>
  1775. <ul>
  1776. <li>The top level is a command name (comm)</li>
  1777. <li>The next level is a thread (pid:tid)</li>
  1778. <li>Subsequent levels are functions</li>
  1779. <li>'Count' is the number of calls</li>
  1780. <li>'Time' is the elapsed time until the function returns</li>
  1781. <li>Percentages are relative to the level above</li>
  1782. <li>'Branch Count' is the total number of branches for that function and all functions that it calls
  1783. </ul>
  1784. <h3>Find</h3>
  1785. Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
  1786. The pattern matching symbols are ? for any character and * for zero or more characters.
  1787. <h2 id=allbranches>1.2 All branches</h2>
  1788. The All branches report displays all branches in chronological order.
  1789. Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
  1790. <h3>Disassembly</h3>
  1791. Open a branch to display disassembly. This only works if:
  1792. <ol>
  1793. <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
  1794. <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
  1795. The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
  1796. One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
  1797. or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
  1798. </ol>
  1799. <h4 id=xed>Intel XED Setup</h4>
  1800. To use Intel XED, libxed.so must be present. To build and install libxed.so:
  1801. <pre>
  1802. git clone https://github.com/intelxed/mbuild.git mbuild
  1803. git clone https://github.com/intelxed/xed
  1804. cd xed
  1805. ./mfile.py --share
  1806. sudo ./mfile.py --prefix=/usr/local install
  1807. sudo ldconfig
  1808. </pre>
  1809. <h3>Find</h3>
  1810. Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
  1811. Refer to Python documentation for the regular expression syntax.
  1812. All columns are searched, but only currently fetched rows are searched.
  1813. <h2 id=selectedbranches>1.3 Selected branches</h2>
  1814. This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
  1815. by various selection criteria. A dialog box displays available criteria which are AND'ed together.
  1816. <h3>1.3.1 Time ranges</h3>
  1817. The time ranges hint text shows the total time range. Relative time ranges can also be entered in
  1818. ms, us or ns. Also, negative values are relative to the end of trace. Examples:
  1819. <pre>
  1820. 81073085947329-81073085958238 From 81073085947329 to 81073085958238
  1821. 100us-200us From 100us to 200us
  1822. 10ms- From 10ms to the end
  1823. -100ns The first 100ns
  1824. -10ms- The last 10ms
  1825. </pre>
  1826. N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
  1827. <h1 id=tables>2. Tables</h1>
  1828. The Tables menu shows all tables and views in the database. Most tables have an associated view
  1829. which displays the information in a more friendly way. Not all data for large tables is fetched
  1830. immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
  1831. but that can be slow for large tables.
  1832. <p>There are also tables of database meta-information.
  1833. For SQLite3 databases, the sqlite_master table is included.
  1834. For PostgreSQL databases, information_schema.tables/views/columns are included.
  1835. <h3>Find</h3>
  1836. Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
  1837. Refer to Python documentation for the regular expression syntax.
  1838. All columns are searched, but only currently fetched rows are searched.
  1839. <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
  1840. will go to the next/previous result in id order, instead of display order.
  1841. """
  1842. # Help window
  1843. class HelpWindow(QMdiSubWindow):
  1844. def __init__(self, glb, parent=None):
  1845. super(HelpWindow, self).__init__(parent)
  1846. self.text = QTextBrowser()
  1847. self.text.setHtml(glb_help_text)
  1848. self.text.setReadOnly(True)
  1849. self.text.setOpenExternalLinks(True)
  1850. self.setWidget(self.text)
  1851. AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
  1852. # Main window that only displays the help text
  1853. class HelpOnlyWindow(QMainWindow):
  1854. def __init__(self, parent=None):
  1855. super(HelpOnlyWindow, self).__init__(parent)
  1856. self.setMinimumSize(200, 100)
  1857. self.resize(800, 600)
  1858. self.setWindowTitle("Exported SQL Viewer Help")
  1859. self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
  1860. self.text = QTextBrowser()
  1861. self.text.setHtml(glb_help_text)
  1862. self.text.setReadOnly(True)
  1863. self.text.setOpenExternalLinks(True)
  1864. self.setCentralWidget(self.text)
  1865. # Font resize
  1866. def ResizeFont(widget, diff):
  1867. font = widget.font()
  1868. sz = font.pointSize()
  1869. font.setPointSize(sz + diff)
  1870. widget.setFont(font)
  1871. def ShrinkFont(widget):
  1872. ResizeFont(widget, -1)
  1873. def EnlargeFont(widget):
  1874. ResizeFont(widget, 1)
  1875. # Unique name for sub-windows
  1876. def NumberedWindowName(name, nr):
  1877. if nr > 1:
  1878. name += " <" + str(nr) + ">"
  1879. return name
  1880. def UniqueSubWindowName(mdi_area, name):
  1881. nr = 1
  1882. while True:
  1883. unique_name = NumberedWindowName(name, nr)
  1884. ok = True
  1885. for sub_window in mdi_area.subWindowList():
  1886. if sub_window.name == unique_name:
  1887. ok = False
  1888. break
  1889. if ok:
  1890. return unique_name
  1891. nr += 1
  1892. # Add a sub-window
  1893. def AddSubWindow(mdi_area, sub_window, name):
  1894. unique_name = UniqueSubWindowName(mdi_area, name)
  1895. sub_window.setMinimumSize(200, 100)
  1896. sub_window.resize(800, 600)
  1897. sub_window.setWindowTitle(unique_name)
  1898. sub_window.setAttribute(Qt.WA_DeleteOnClose)
  1899. sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
  1900. sub_window.name = unique_name
  1901. mdi_area.addSubWindow(sub_window)
  1902. sub_window.show()
  1903. # Main window
  1904. class MainWindow(QMainWindow):
  1905. def __init__(self, glb, parent=None):
  1906. super(MainWindow, self).__init__(parent)
  1907. self.glb = glb
  1908. self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
  1909. self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
  1910. self.setMinimumSize(200, 100)
  1911. self.mdi_area = QMdiArea()
  1912. self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  1913. self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  1914. self.setCentralWidget(self.mdi_area)
  1915. menu = self.menuBar()
  1916. file_menu = menu.addMenu("&File")
  1917. file_menu.addAction(CreateExitAction(glb.app, self))
  1918. edit_menu = menu.addMenu("&Edit")
  1919. edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
  1920. edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
  1921. edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
  1922. edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
  1923. reports_menu = menu.addMenu("&Reports")
  1924. reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
  1925. self.EventMenu(GetEventList(glb.db), reports_menu)
  1926. self.TableMenu(GetTableList(glb), menu)
  1927. self.window_menu = WindowMenu(self.mdi_area, menu)
  1928. help_menu = menu.addMenu("&Help")
  1929. help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
  1930. def Find(self):
  1931. win = self.mdi_area.activeSubWindow()
  1932. if win:
  1933. try:
  1934. win.find_bar.Activate()
  1935. except:
  1936. pass
  1937. def FetchMoreRecords(self):
  1938. win = self.mdi_area.activeSubWindow()
  1939. if win:
  1940. try:
  1941. win.fetch_bar.Activate()
  1942. except:
  1943. pass
  1944. def ShrinkFont(self):
  1945. win = self.mdi_area.activeSubWindow()
  1946. ShrinkFont(win.view)
  1947. def EnlargeFont(self):
  1948. win = self.mdi_area.activeSubWindow()
  1949. EnlargeFont(win.view)
  1950. def EventMenu(self, events, reports_menu):
  1951. branches_events = 0
  1952. for event in events:
  1953. event = event.split(":")[0]
  1954. if event == "branches":
  1955. branches_events += 1
  1956. dbid = 0
  1957. for event in events:
  1958. dbid += 1
  1959. event = event.split(":")[0]
  1960. if event == "branches":
  1961. label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
  1962. reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
  1963. label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
  1964. reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
  1965. def TableMenu(self, tables, menu):
  1966. table_menu = menu.addMenu("&Tables")
  1967. for table in tables:
  1968. table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
  1969. def NewCallGraph(self):
  1970. CallGraphWindow(self.glb, self)
  1971. def NewBranchView(self, event_id):
  1972. BranchWindow(self.glb, event_id, "", "", self)
  1973. def NewSelectedBranchView(self, event_id):
  1974. dialog = SelectedBranchDialog(self.glb, self)
  1975. ret = dialog.exec_()
  1976. if ret:
  1977. BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self)
  1978. def NewTableView(self, table_name):
  1979. TableWindow(self.glb, table_name, self)
  1980. def Help(self):
  1981. HelpWindow(self.glb, self)
  1982. # XED Disassembler
  1983. class xed_state_t(Structure):
  1984. _fields_ = [
  1985. ("mode", c_int),
  1986. ("width", c_int)
  1987. ]
  1988. class XEDInstruction():
  1989. def __init__(self, libxed):
  1990. # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
  1991. xedd_t = c_byte * 512
  1992. self.xedd = xedd_t()
  1993. self.xedp = addressof(self.xedd)
  1994. libxed.xed_decoded_inst_zero(self.xedp)
  1995. self.state = xed_state_t()
  1996. self.statep = addressof(self.state)
  1997. # Buffer for disassembled instruction text
  1998. self.buffer = create_string_buffer(256)
  1999. self.bufferp = addressof(self.buffer)
  2000. class LibXED():
  2001. def __init__(self):
  2002. try:
  2003. self.libxed = CDLL("libxed.so")
  2004. except:
  2005. self.libxed = None
  2006. if not self.libxed:
  2007. self.libxed = CDLL("/usr/local/lib/libxed.so")
  2008. self.xed_tables_init = self.libxed.xed_tables_init
  2009. self.xed_tables_init.restype = None
  2010. self.xed_tables_init.argtypes = []
  2011. self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
  2012. self.xed_decoded_inst_zero.restype = None
  2013. self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
  2014. self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
  2015. self.xed_operand_values_set_mode.restype = None
  2016. self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
  2017. self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
  2018. self.xed_decoded_inst_zero_keep_mode.restype = None
  2019. self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
  2020. self.xed_decode = self.libxed.xed_decode
  2021. self.xed_decode.restype = c_int
  2022. self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
  2023. self.xed_format_context = self.libxed.xed_format_context
  2024. self.xed_format_context.restype = c_uint
  2025. self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
  2026. self.xed_tables_init()
  2027. def Instruction(self):
  2028. return XEDInstruction(self)
  2029. def SetMode(self, inst, mode):
  2030. if mode:
  2031. inst.state.mode = 4 # 32-bit
  2032. inst.state.width = 4 # 4 bytes
  2033. else:
  2034. inst.state.mode = 1 # 64-bit
  2035. inst.state.width = 8 # 8 bytes
  2036. self.xed_operand_values_set_mode(inst.xedp, inst.statep)
  2037. def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
  2038. self.xed_decoded_inst_zero_keep_mode(inst.xedp)
  2039. err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
  2040. if err:
  2041. return 0, ""
  2042. # Use AT&T mode (2), alternative is Intel (3)
  2043. ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
  2044. if not ok:
  2045. return 0, ""
  2046. # Return instruction length and the disassembled instruction text
  2047. # For now, assume the length is in byte 166
  2048. return inst.xedd[166], inst.buffer.value
  2049. def TryOpen(file_name):
  2050. try:
  2051. return open(file_name, "rb")
  2052. except:
  2053. return None
  2054. def Is64Bit(f):
  2055. result = sizeof(c_void_p)
  2056. # ELF support only
  2057. pos = f.tell()
  2058. f.seek(0)
  2059. header = f.read(7)
  2060. f.seek(pos)
  2061. magic = header[0:4]
  2062. eclass = ord(header[4])
  2063. encoding = ord(header[5])
  2064. version = ord(header[6])
  2065. if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
  2066. result = True if eclass == 2 else False
  2067. return result
  2068. # Global data
  2069. class Glb():
  2070. def __init__(self, dbref, db, dbname):
  2071. self.dbref = dbref
  2072. self.db = db
  2073. self.dbname = dbname
  2074. self.home_dir = os.path.expanduser("~")
  2075. self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
  2076. if self.buildid_dir:
  2077. self.buildid_dir += "/.build-id/"
  2078. else:
  2079. self.buildid_dir = self.home_dir + "/.debug/.build-id/"
  2080. self.app = None
  2081. self.mainwindow = None
  2082. self.instances_to_shutdown_on_exit = weakref.WeakSet()
  2083. try:
  2084. self.disassembler = LibXED()
  2085. self.have_disassembler = True
  2086. except:
  2087. self.have_disassembler = False
  2088. def FileFromBuildId(self, build_id):
  2089. file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
  2090. return TryOpen(file_name)
  2091. def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
  2092. # Assume current machine i.e. no support for virtualization
  2093. if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
  2094. file_name = os.getenv("PERF_KCORE")
  2095. f = TryOpen(file_name) if file_name else None
  2096. if f:
  2097. return f
  2098. # For now, no special handling if long_name is /proc/kcore
  2099. f = TryOpen(long_name)
  2100. if f:
  2101. return f
  2102. f = self.FileFromBuildId(build_id)
  2103. if f:
  2104. return f
  2105. return None
  2106. def AddInstanceToShutdownOnExit(self, instance):
  2107. self.instances_to_shutdown_on_exit.add(instance)
  2108. # Shutdown any background processes or threads
  2109. def ShutdownInstances(self):
  2110. for x in self.instances_to_shutdown_on_exit:
  2111. try:
  2112. x.Shutdown()
  2113. except:
  2114. pass
  2115. # Database reference
  2116. class DBRef():
  2117. def __init__(self, is_sqlite3, dbname):
  2118. self.is_sqlite3 = is_sqlite3
  2119. self.dbname = dbname
  2120. def Open(self, connection_name):
  2121. dbname = self.dbname
  2122. if self.is_sqlite3:
  2123. db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
  2124. else:
  2125. db = QSqlDatabase.addDatabase("QPSQL", connection_name)
  2126. opts = dbname.split()
  2127. for opt in opts:
  2128. if "=" in opt:
  2129. opt = opt.split("=")
  2130. if opt[0] == "hostname":
  2131. db.setHostName(opt[1])
  2132. elif opt[0] == "port":
  2133. db.setPort(int(opt[1]))
  2134. elif opt[0] == "username":
  2135. db.setUserName(opt[1])
  2136. elif opt[0] == "password":
  2137. db.setPassword(opt[1])
  2138. elif opt[0] == "dbname":
  2139. dbname = opt[1]
  2140. else:
  2141. dbname = opt
  2142. db.setDatabaseName(dbname)
  2143. if not db.open():
  2144. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  2145. return db, dbname
  2146. # Main
  2147. def Main():
  2148. if (len(sys.argv) < 2):
  2149. print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
  2150. raise Exception("Too few arguments")
  2151. dbname = sys.argv[1]
  2152. if dbname == "--help-only":
  2153. app = QApplication(sys.argv)
  2154. mainwindow = HelpOnlyWindow()
  2155. mainwindow.show()
  2156. err = app.exec_()
  2157. sys.exit(err)
  2158. is_sqlite3 = False
  2159. try:
  2160. f = open(dbname)
  2161. if f.read(15) == "SQLite format 3":
  2162. is_sqlite3 = True
  2163. f.close()
  2164. except:
  2165. pass
  2166. dbref = DBRef(is_sqlite3, dbname)
  2167. db, dbname = dbref.Open("main")
  2168. glb = Glb(dbref, db, dbname)
  2169. app = QApplication(sys.argv)
  2170. glb.app = app
  2171. mainwindow = MainWindow(glb)
  2172. glb.mainwindow = mainwindow
  2173. mainwindow.show()
  2174. err = app.exec_()
  2175. glb.ShutdownInstances()
  2176. db.close()
  2177. sys.exit(err)
  2178. if __name__ == "__main__":
  2179. Main()