call-graph-from-sql.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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/call-graph-from-sql.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/call-graph-from-sql.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. class TreeItem():
  53. def __init__(self, db, row, parent_item):
  54. self.db = db
  55. self.row = row
  56. self.parent_item = parent_item
  57. self.query_done = False;
  58. self.child_count = 0
  59. self.child_items = []
  60. self.data = ["", "", "", "", "", "", ""]
  61. self.comm_id = 0
  62. self.thread_id = 0
  63. self.call_path_id = 1
  64. self.branch_count = 0
  65. self.time = 0
  66. if not parent_item:
  67. self.setUpRoot()
  68. def setUpRoot(self):
  69. self.query_done = True
  70. query = QSqlQuery(self.db)
  71. ret = query.exec_('SELECT id, comm FROM comms')
  72. if not ret:
  73. raise Exception("Query failed: " + query.lastError().text())
  74. while query.next():
  75. if not query.value(0):
  76. continue
  77. child_item = TreeItem(self.db, self.child_count, self)
  78. self.child_items.append(child_item)
  79. self.child_count += 1
  80. child_item.setUpLevel1(query.value(0), query.value(1))
  81. def setUpLevel1(self, comm_id, comm):
  82. self.query_done = True;
  83. self.comm_id = comm_id
  84. self.data[0] = comm
  85. self.child_items = []
  86. self.child_count = 0
  87. query = QSqlQuery(self.db)
  88. ret = query.exec_('SELECT thread_id, ( SELECT pid FROM threads WHERE id = thread_id ), ( SELECT tid FROM threads WHERE id = thread_id ) FROM comm_threads WHERE comm_id = ' + str(comm_id))
  89. if not ret:
  90. raise Exception("Query failed: " + query.lastError().text())
  91. while query.next():
  92. child_item = TreeItem(self.db, self.child_count, self)
  93. self.child_items.append(child_item)
  94. self.child_count += 1
  95. child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2))
  96. def setUpLevel2(self, comm_id, thread_id, pid, tid):
  97. self.comm_id = comm_id
  98. self.thread_id = thread_id
  99. self.data[0] = str(pid) + ":" + str(tid)
  100. def getChildItem(self, row):
  101. return self.child_items[row]
  102. def getParentItem(self):
  103. return self.parent_item
  104. def getRow(self):
  105. return self.row
  106. def timePercent(self, b):
  107. if not self.time:
  108. return "0.0"
  109. x = (b * Decimal(100)) / self.time
  110. return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
  111. def branchPercent(self, b):
  112. if not self.branch_count:
  113. return "0.0"
  114. x = (b * Decimal(100)) / self.branch_count
  115. return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
  116. def addChild(self, call_path_id, name, dso, count, time, branch_count):
  117. child_item = TreeItem(self.db, self.child_count, self)
  118. child_item.comm_id = self.comm_id
  119. child_item.thread_id = self.thread_id
  120. child_item.call_path_id = call_path_id
  121. child_item.branch_count = branch_count
  122. child_item.time = time
  123. child_item.data[0] = name
  124. if dso == "[kernel.kallsyms]":
  125. dso = "[kernel]"
  126. child_item.data[1] = dso
  127. child_item.data[2] = str(count)
  128. child_item.data[3] = str(time)
  129. child_item.data[4] = self.timePercent(time)
  130. child_item.data[5] = str(branch_count)
  131. child_item.data[6] = self.branchPercent(branch_count)
  132. self.child_items.append(child_item)
  133. self.child_count += 1
  134. def selectCalls(self):
  135. self.query_done = True;
  136. query = QSqlQuery(self.db)
  137. ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
  138. '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
  139. '( SELECT short_name FROM dsos WHERE id = ( SELECT dso_id FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ) ), '
  140. '( SELECT ip FROM call_paths where id = call_path_id ) '
  141. 'FROM calls WHERE parent_call_path_id = ' + str(self.call_path_id) + ' AND comm_id = ' + str(self.comm_id) + ' AND thread_id = ' + str(self.thread_id) +
  142. ' ORDER BY call_path_id')
  143. if not ret:
  144. raise Exception("Query failed: " + query.lastError().text())
  145. last_call_path_id = 0
  146. name = ""
  147. dso = ""
  148. count = 0
  149. branch_count = 0
  150. total_branch_count = 0
  151. time = 0
  152. total_time = 0
  153. while query.next():
  154. if query.value(1) == last_call_path_id:
  155. count += 1
  156. branch_count += query.value(2)
  157. time += query.value(4) - query.value(3)
  158. else:
  159. if count:
  160. self.addChild(last_call_path_id, name, dso, count, time, branch_count)
  161. last_call_path_id = query.value(1)
  162. name = query.value(5)
  163. dso = query.value(6)
  164. count = 1
  165. total_branch_count += branch_count
  166. total_time += time
  167. branch_count = query.value(2)
  168. time = query.value(4) - query.value(3)
  169. if count:
  170. self.addChild(last_call_path_id, name, dso, count, time, branch_count)
  171. total_branch_count += branch_count
  172. total_time += time
  173. # Top level does not have time or branch count, so fix that here
  174. if total_branch_count > self.branch_count:
  175. self.branch_count = total_branch_count
  176. if self.branch_count:
  177. for child_item in self.child_items:
  178. child_item.data[6] = self.branchPercent(child_item.branch_count)
  179. if total_time > self.time:
  180. self.time = total_time
  181. if self.time:
  182. for child_item in self.child_items:
  183. child_item.data[4] = self.timePercent(child_item.time)
  184. def childCount(self):
  185. if not self.query_done:
  186. self.selectCalls()
  187. return self.child_count
  188. def columnCount(self):
  189. return 7
  190. def columnHeader(self, column):
  191. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  192. return headers[column]
  193. def getData(self, column):
  194. return self.data[column]
  195. class TreeModel(QAbstractItemModel):
  196. def __init__(self, db, parent=None):
  197. super(TreeModel, self).__init__(parent)
  198. self.db = db
  199. self.root = TreeItem(db, 0, None)
  200. def columnCount(self, parent):
  201. return self.root.columnCount()
  202. def rowCount(self, parent):
  203. if parent.isValid():
  204. parent_item = parent.internalPointer()
  205. else:
  206. parent_item = self.root
  207. return parent_item.childCount()
  208. def headerData(self, section, orientation, role):
  209. if role == Qt.TextAlignmentRole:
  210. if section > 1:
  211. return Qt.AlignRight
  212. if role != Qt.DisplayRole:
  213. return None
  214. if orientation != Qt.Horizontal:
  215. return None
  216. return self.root.columnHeader(section)
  217. def parent(self, child):
  218. child_item = child.internalPointer()
  219. if child_item is self.root:
  220. return QModelIndex()
  221. parent_item = child_item.getParentItem()
  222. return self.createIndex(parent_item.getRow(), 0, parent_item)
  223. def index(self, row, column, parent):
  224. if parent.isValid():
  225. parent_item = parent.internalPointer()
  226. else:
  227. parent_item = self.root
  228. child_item = parent_item.getChildItem(row)
  229. return self.createIndex(row, column, child_item)
  230. def data(self, index, role):
  231. if role == Qt.TextAlignmentRole:
  232. if index.column() > 1:
  233. return Qt.AlignRight
  234. if role != Qt.DisplayRole:
  235. return None
  236. index_item = index.internalPointer()
  237. return index_item.getData(index.column())
  238. class MainWindow(QMainWindow):
  239. def __init__(self, db, dbname, parent=None):
  240. super(MainWindow, self).__init__(parent)
  241. self.setObjectName("MainWindow")
  242. self.setWindowTitle("Call Graph: " + dbname)
  243. self.move(100, 100)
  244. self.resize(800, 600)
  245. style = self.style()
  246. icon = style.standardIcon(QStyle.SP_MessageBoxInformation)
  247. self.setWindowIcon(icon);
  248. self.model = TreeModel(db)
  249. self.view = QTreeView()
  250. self.view.setModel(self.model)
  251. self.setCentralWidget(self.view)
  252. if __name__ == '__main__':
  253. if (len(sys.argv) < 2):
  254. print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
  255. raise Exception("Too few arguments")
  256. dbname = sys.argv[1]
  257. is_sqlite3 = False
  258. try:
  259. f = open(dbname)
  260. if f.read(15) == "SQLite format 3":
  261. is_sqlite3 = True
  262. f.close()
  263. except:
  264. pass
  265. if is_sqlite3:
  266. db = QSqlDatabase.addDatabase('QSQLITE')
  267. else:
  268. db = QSqlDatabase.addDatabase('QPSQL')
  269. opts = dbname.split()
  270. for opt in opts:
  271. if '=' in opt:
  272. opt = opt.split('=')
  273. if opt[0] == 'hostname':
  274. db.setHostName(opt[1])
  275. elif opt[0] == 'port':
  276. db.setPort(int(opt[1]))
  277. elif opt[0] == 'username':
  278. db.setUserName(opt[1])
  279. elif opt[0] == 'password':
  280. db.setPassword(opt[1])
  281. elif opt[0] == 'dbname':
  282. dbname = opt[1]
  283. else:
  284. dbname = opt
  285. db.setDatabaseName(dbname)
  286. if not db.open():
  287. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  288. app = QApplication(sys.argv)
  289. window = MainWindow(db, dbname)
  290. window.show()
  291. err = app.exec_()
  292. db.close()
  293. sys.exit(err)