call-graph-from-sql.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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 hasChildren(self):
  189. if not self.query_done:
  190. return True
  191. return self.child_count > 0
  192. def getData(self, column):
  193. return self.data[column]
  194. # Tree data model
  195. class TreeModel(QAbstractItemModel):
  196. def __init__(self, root, parent=None):
  197. super(TreeModel, self).__init__(parent)
  198. self.root = root
  199. self.last_row_read = 0
  200. def Item(self, parent):
  201. if parent.isValid():
  202. return parent.internalPointer()
  203. else:
  204. return self.root
  205. def rowCount(self, parent):
  206. result = self.Item(parent).childCount()
  207. if result < 0:
  208. result = 0
  209. self.dataChanged.emit(parent, parent)
  210. return result
  211. def hasChildren(self, parent):
  212. return self.Item(parent).hasChildren()
  213. def headerData(self, section, orientation, role):
  214. if role == Qt.TextAlignmentRole:
  215. return self.columnAlignment(section)
  216. if role != Qt.DisplayRole:
  217. return None
  218. if orientation != Qt.Horizontal:
  219. return None
  220. return self.columnHeader(section)
  221. def parent(self, child):
  222. child_item = child.internalPointer()
  223. if child_item is self.root:
  224. return QModelIndex()
  225. parent_item = child_item.getParentItem()
  226. return self.createIndex(parent_item.getRow(), 0, parent_item)
  227. def index(self, row, column, parent):
  228. child_item = self.Item(parent).getChildItem(row)
  229. return self.createIndex(row, column, child_item)
  230. def DisplayData(self, item, index):
  231. return item.getData(index.column())
  232. def columnAlignment(self, column):
  233. return Qt.AlignLeft
  234. def columnFont(self, column):
  235. return None
  236. def data(self, index, role):
  237. if role == Qt.TextAlignmentRole:
  238. return self.columnAlignment(index.column())
  239. if role == Qt.FontRole:
  240. return self.columnFont(index.column())
  241. if role != Qt.DisplayRole:
  242. return None
  243. item = index.internalPointer()
  244. return self.DisplayData(item, index)
  245. # Context-sensitive call graph data model
  246. class CallGraphModel(TreeModel):
  247. def __init__(self, glb, parent=None):
  248. super(CallGraphModel, self).__init__(TreeItem(glb.db, 0, None), parent)
  249. self.glb = glb
  250. def columnCount(self, parent=None):
  251. return 7
  252. def columnHeader(self, column):
  253. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  254. return headers[column]
  255. def columnAlignment(self, column):
  256. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  257. return alignment[column]
  258. # Main window
  259. class MainWindow(QMainWindow):
  260. def __init__(self, glb, parent=None):
  261. super(MainWindow, self).__init__(parent)
  262. self.glb = glb
  263. self.setWindowTitle("Call Graph: " + glb.dbname)
  264. self.move(100, 100)
  265. self.resize(800, 600)
  266. self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
  267. self.setMinimumSize(200, 100)
  268. self.model = CallGraphModel(glb)
  269. self.view = QTreeView()
  270. self.view.setModel(self.model)
  271. for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
  272. self.view.setColumnWidth(c, w)
  273. self.setCentralWidget(self.view)
  274. # Global data
  275. class Glb():
  276. def __init__(self, dbref, db, dbname):
  277. self.dbref = dbref
  278. self.db = db
  279. self.dbname = dbname
  280. self.app = None
  281. self.mainwindow = None
  282. # Database reference
  283. class DBRef():
  284. def __init__(self, is_sqlite3, dbname):
  285. self.is_sqlite3 = is_sqlite3
  286. self.dbname = dbname
  287. def Open(self, connection_name):
  288. dbname = self.dbname
  289. if self.is_sqlite3:
  290. db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
  291. else:
  292. db = QSqlDatabase.addDatabase("QPSQL", connection_name)
  293. opts = dbname.split()
  294. for opt in opts:
  295. if "=" in opt:
  296. opt = opt.split("=")
  297. if opt[0] == "hostname":
  298. db.setHostName(opt[1])
  299. elif opt[0] == "port":
  300. db.setPort(int(opt[1]))
  301. elif opt[0] == "username":
  302. db.setUserName(opt[1])
  303. elif opt[0] == "password":
  304. db.setPassword(opt[1])
  305. elif opt[0] == "dbname":
  306. dbname = opt[1]
  307. else:
  308. dbname = opt
  309. db.setDatabaseName(dbname)
  310. if not db.open():
  311. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  312. return db, dbname
  313. # Main
  314. def Main():
  315. if (len(sys.argv) < 2):
  316. print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
  317. raise Exception("Too few arguments")
  318. dbname = sys.argv[1]
  319. is_sqlite3 = False
  320. try:
  321. f = open(dbname)
  322. if f.read(15) == "SQLite format 3":
  323. is_sqlite3 = True
  324. f.close()
  325. except:
  326. pass
  327. dbref = DBRef(is_sqlite3, dbname)
  328. db, dbname = dbref.Open("main")
  329. glb = Glb(dbref, db, dbname)
  330. app = QApplication(sys.argv)
  331. glb.app = app
  332. mainwindow = MainWindow(glb)
  333. glb.mainwindow = mainwindow
  334. mainwindow.show()
  335. err = app.exec_()
  336. db.close()
  337. sys.exit(err)
  338. if __name__ == "__main__":
  339. Main()