exported-sql-viewer.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. import sys
  48. from PySide.QtCore import *
  49. from PySide.QtGui import *
  50. from PySide.QtSql import *
  51. from decimal import *
  52. # Data formatting helpers
  53. def dsoname(name):
  54. if name == "[kernel.kallsyms]":
  55. return "[kernel]"
  56. return name
  57. # Percent to one decimal place
  58. def PercentToOneDP(n, d):
  59. if not d:
  60. return "0.0"
  61. x = (n * Decimal(100)) / d
  62. return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
  63. # Helper for queries that must not fail
  64. def QueryExec(query, stmt):
  65. ret = query.exec_(stmt)
  66. if not ret:
  67. raise Exception("Query failed: " + query.lastError().text())
  68. # Tree data model
  69. class TreeModel(QAbstractItemModel):
  70. def __init__(self, root, parent=None):
  71. super(TreeModel, self).__init__(parent)
  72. self.root = root
  73. self.last_row_read = 0
  74. def Item(self, parent):
  75. if parent.isValid():
  76. return parent.internalPointer()
  77. else:
  78. return self.root
  79. def rowCount(self, parent):
  80. result = self.Item(parent).childCount()
  81. if result < 0:
  82. result = 0
  83. self.dataChanged.emit(parent, parent)
  84. return result
  85. def hasChildren(self, parent):
  86. return self.Item(parent).hasChildren()
  87. def headerData(self, section, orientation, role):
  88. if role == Qt.TextAlignmentRole:
  89. return self.columnAlignment(section)
  90. if role != Qt.DisplayRole:
  91. return None
  92. if orientation != Qt.Horizontal:
  93. return None
  94. return self.columnHeader(section)
  95. def parent(self, child):
  96. child_item = child.internalPointer()
  97. if child_item is self.root:
  98. return QModelIndex()
  99. parent_item = child_item.getParentItem()
  100. return self.createIndex(parent_item.getRow(), 0, parent_item)
  101. def index(self, row, column, parent):
  102. child_item = self.Item(parent).getChildItem(row)
  103. return self.createIndex(row, column, child_item)
  104. def DisplayData(self, item, index):
  105. return item.getData(index.column())
  106. def columnAlignment(self, column):
  107. return Qt.AlignLeft
  108. def columnFont(self, column):
  109. return None
  110. def data(self, index, role):
  111. if role == Qt.TextAlignmentRole:
  112. return self.columnAlignment(index.column())
  113. if role == Qt.FontRole:
  114. return self.columnFont(index.column())
  115. if role != Qt.DisplayRole:
  116. return None
  117. item = index.internalPointer()
  118. return self.DisplayData(item, index)
  119. # Context-sensitive call graph data model item base
  120. class CallGraphLevelItemBase(object):
  121. def __init__(self, glb, row, parent_item):
  122. self.glb = glb
  123. self.row = row
  124. self.parent_item = parent_item
  125. self.query_done = False;
  126. self.child_count = 0
  127. self.child_items = []
  128. def getChildItem(self, row):
  129. return self.child_items[row]
  130. def getParentItem(self):
  131. return self.parent_item
  132. def getRow(self):
  133. return self.row
  134. def childCount(self):
  135. if not self.query_done:
  136. self.Select()
  137. if not self.child_count:
  138. return -1
  139. return self.child_count
  140. def hasChildren(self):
  141. if not self.query_done:
  142. return True
  143. return self.child_count > 0
  144. def getData(self, column):
  145. return self.data[column]
  146. # Context-sensitive call graph data model level 2+ item base
  147. class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
  148. def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
  149. super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
  150. self.comm_id = comm_id
  151. self.thread_id = thread_id
  152. self.call_path_id = call_path_id
  153. self.branch_count = branch_count
  154. self.time = time
  155. def Select(self):
  156. self.query_done = True;
  157. query = QSqlQuery(self.glb.db)
  158. QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
  159. " FROM calls"
  160. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  161. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  162. " INNER JOIN dsos ON symbols.dso_id = dsos.id"
  163. " WHERE parent_call_path_id = " + str(self.call_path_id) +
  164. " AND comm_id = " + str(self.comm_id) +
  165. " AND thread_id = " + str(self.thread_id) +
  166. " GROUP BY call_path_id, name, short_name"
  167. " ORDER BY call_path_id")
  168. while query.next():
  169. 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)
  170. self.child_items.append(child_item)
  171. self.child_count += 1
  172. # Context-sensitive call graph data model level three item
  173. class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
  174. def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
  175. super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
  176. dso = dsoname(dso)
  177. self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
  178. self.dbid = call_path_id
  179. # Context-sensitive call graph data model level two item
  180. class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
  181. def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
  182. super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
  183. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
  184. self.dbid = thread_id
  185. def Select(self):
  186. super(CallGraphLevelTwoItem, self).Select()
  187. for child_item in self.child_items:
  188. self.time += child_item.time
  189. self.branch_count += child_item.branch_count
  190. for child_item in self.child_items:
  191. child_item.data[4] = PercentToOneDP(child_item.time, self.time)
  192. child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
  193. # Context-sensitive call graph data model level one item
  194. class CallGraphLevelOneItem(CallGraphLevelItemBase):
  195. def __init__(self, glb, row, comm_id, comm, parent_item):
  196. super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
  197. self.data = [comm, "", "", "", "", "", ""]
  198. self.dbid = comm_id
  199. def Select(self):
  200. self.query_done = True;
  201. query = QSqlQuery(self.glb.db)
  202. QueryExec(query, "SELECT thread_id, pid, tid"
  203. " FROM comm_threads"
  204. " INNER JOIN threads ON thread_id = threads.id"
  205. " WHERE comm_id = " + str(self.dbid))
  206. while query.next():
  207. child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
  208. self.child_items.append(child_item)
  209. self.child_count += 1
  210. # Context-sensitive call graph data model root item
  211. class CallGraphRootItem(CallGraphLevelItemBase):
  212. def __init__(self, glb):
  213. super(CallGraphRootItem, self).__init__(glb, 0, None)
  214. self.dbid = 0
  215. self.query_done = True;
  216. query = QSqlQuery(glb.db)
  217. QueryExec(query, "SELECT id, comm FROM comms")
  218. while query.next():
  219. if not query.value(0):
  220. continue
  221. child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
  222. self.child_items.append(child_item)
  223. self.child_count += 1
  224. # Context-sensitive call graph data model
  225. class CallGraphModel(TreeModel):
  226. def __init__(self, glb, parent=None):
  227. super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
  228. self.glb = glb
  229. def columnCount(self, parent=None):
  230. return 7
  231. def columnHeader(self, column):
  232. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  233. return headers[column]
  234. def columnAlignment(self, column):
  235. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  236. return alignment[column]
  237. # Main window
  238. class MainWindow(QMainWindow):
  239. def __init__(self, glb, parent=None):
  240. super(MainWindow, self).__init__(parent)
  241. self.glb = glb
  242. self.setWindowTitle("Call Graph: " + glb.dbname)
  243. self.move(100, 100)
  244. self.resize(800, 600)
  245. self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
  246. self.setMinimumSize(200, 100)
  247. self.model = CallGraphModel(glb)
  248. self.view = QTreeView()
  249. self.view.setModel(self.model)
  250. for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
  251. self.view.setColumnWidth(c, w)
  252. self.setCentralWidget(self.view)
  253. # Global data
  254. class Glb():
  255. def __init__(self, dbref, db, dbname):
  256. self.dbref = dbref
  257. self.db = db
  258. self.dbname = dbname
  259. self.app = None
  260. self.mainwindow = None
  261. # Database reference
  262. class DBRef():
  263. def __init__(self, is_sqlite3, dbname):
  264. self.is_sqlite3 = is_sqlite3
  265. self.dbname = dbname
  266. def Open(self, connection_name):
  267. dbname = self.dbname
  268. if self.is_sqlite3:
  269. db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
  270. else:
  271. db = QSqlDatabase.addDatabase("QPSQL", connection_name)
  272. opts = dbname.split()
  273. for opt in opts:
  274. if "=" in opt:
  275. opt = opt.split("=")
  276. if opt[0] == "hostname":
  277. db.setHostName(opt[1])
  278. elif opt[0] == "port":
  279. db.setPort(int(opt[1]))
  280. elif opt[0] == "username":
  281. db.setUserName(opt[1])
  282. elif opt[0] == "password":
  283. db.setPassword(opt[1])
  284. elif opt[0] == "dbname":
  285. dbname = opt[1]
  286. else:
  287. dbname = opt
  288. db.setDatabaseName(dbname)
  289. if not db.open():
  290. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  291. return db, dbname
  292. # Main
  293. def Main():
  294. if (len(sys.argv) < 2):
  295. print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
  296. raise Exception("Too few arguments")
  297. dbname = sys.argv[1]
  298. is_sqlite3 = False
  299. try:
  300. f = open(dbname)
  301. if f.read(15) == "SQLite format 3":
  302. is_sqlite3 = True
  303. f.close()
  304. except:
  305. pass
  306. dbref = DBRef(is_sqlite3, dbname)
  307. db, dbname = dbref.Open("main")
  308. glb = Glb(dbref, db, dbname)
  309. app = QApplication(sys.argv)
  310. glb.app = app
  311. mainwindow = MainWindow(glb)
  312. glb.mainwindow = mainwindow
  313. mainwindow.show()
  314. err = app.exec_()
  315. db.close()
  316. sys.exit(err)
  317. if __name__ == "__main__":
  318. Main()