call-graph-from-postgresql.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. #!/usr/bin/python2
  2. # call-graph-from-postgresql.py: create call-graph from postgresql database
  3. # Copyright (c) 2014, Intel Corporation.
  4. #
  5. # This program is free software; you can redistribute it and/or modify it
  6. # under the terms and conditions of the GNU General Public License,
  7. # version 2, as published by the Free Software Foundation.
  8. #
  9. # This program is distributed in the hope it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12. # more details.
  13. # To use this script you will need to have exported data using the
  14. # export-to-postgresql.py script. Refer to that script for details.
  15. #
  16. # Following on from the example in the export-to-postgresql.py script, a
  17. # call-graph can be displayed for the pt_example database like this:
  18. #
  19. # python tools/perf/scripts/python/call-graph-from-postgresql.py pt_example
  20. #
  21. # Note this script supports connecting to remote databases by setting hostname,
  22. # port, username, password, and dbname e.g.
  23. #
  24. # python tools/perf/scripts/python/call-graph-from-postgresql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
  25. #
  26. # The result is a GUI window with a tree representing a context-sensitive
  27. # call-graph. Expanding a couple of levels of the tree and adjusting column
  28. # widths to suit will display something like:
  29. #
  30. # Call Graph: pt_example
  31. # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
  32. # v- ls
  33. # v- 2638:2638
  34. # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
  35. # |- unknown unknown 1 13198 0.1 1 0.0
  36. # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
  37. # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
  38. # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
  39. # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
  40. # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
  41. # >- __libc_csu_init ls 1 10354 0.1 10 0.0
  42. # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
  43. # v- main ls 1 8182043 99.6 180254 99.9
  44. #
  45. # Points to note:
  46. # The top level is a command name (comm)
  47. # The next level is a thread (pid:tid)
  48. # Subsequent levels are functions
  49. # 'Count' is the number of calls
  50. # 'Time' is the elapsed time until the function returns
  51. # Percentages are relative to the level above
  52. # 'Branch Count' is the total number of branches for that function and all
  53. # functions that it calls
  54. import sys
  55. from PySide.QtCore import *
  56. from PySide.QtGui import *
  57. from PySide.QtSql import *
  58. from decimal import *
  59. class TreeItem():
  60. def __init__(self, db, row, parent_item):
  61. self.db = db
  62. self.row = row
  63. self.parent_item = parent_item
  64. self.query_done = False;
  65. self.child_count = 0
  66. self.child_items = []
  67. self.data = ["", "", "", "", "", "", ""]
  68. self.comm_id = 0
  69. self.thread_id = 0
  70. self.call_path_id = 1
  71. self.branch_count = 0
  72. self.time = 0
  73. if not parent_item:
  74. self.setUpRoot()
  75. def setUpRoot(self):
  76. self.query_done = True
  77. query = QSqlQuery(self.db)
  78. ret = query.exec_('SELECT id, comm FROM comms')
  79. if not ret:
  80. raise Exception("Query failed: " + query.lastError().text())
  81. while query.next():
  82. if not query.value(0):
  83. continue
  84. child_item = TreeItem(self.db, self.child_count, self)
  85. self.child_items.append(child_item)
  86. self.child_count += 1
  87. child_item.setUpLevel1(query.value(0), query.value(1))
  88. def setUpLevel1(self, comm_id, comm):
  89. self.query_done = True;
  90. self.comm_id = comm_id
  91. self.data[0] = comm
  92. self.child_items = []
  93. self.child_count = 0
  94. query = QSqlQuery(self.db)
  95. 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))
  96. if not ret:
  97. raise Exception("Query failed: " + query.lastError().text())
  98. while query.next():
  99. child_item = TreeItem(self.db, self.child_count, self)
  100. self.child_items.append(child_item)
  101. self.child_count += 1
  102. child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2))
  103. def setUpLevel2(self, comm_id, thread_id, pid, tid):
  104. self.comm_id = comm_id
  105. self.thread_id = thread_id
  106. self.data[0] = str(pid) + ":" + str(tid)
  107. def getChildItem(self, row):
  108. return self.child_items[row]
  109. def getParentItem(self):
  110. return self.parent_item
  111. def getRow(self):
  112. return self.row
  113. def timePercent(self, b):
  114. if not self.time:
  115. return "0.0"
  116. x = (b * Decimal(100)) / self.time
  117. return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
  118. def branchPercent(self, b):
  119. if not self.branch_count:
  120. return "0.0"
  121. x = (b * Decimal(100)) / self.branch_count
  122. return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
  123. def addChild(self, call_path_id, name, dso, count, time, branch_count):
  124. child_item = TreeItem(self.db, self.child_count, self)
  125. child_item.comm_id = self.comm_id
  126. child_item.thread_id = self.thread_id
  127. child_item.call_path_id = call_path_id
  128. child_item.branch_count = branch_count
  129. child_item.time = time
  130. child_item.data[0] = name
  131. if dso == "[kernel.kallsyms]":
  132. dso = "[kernel]"
  133. child_item.data[1] = dso
  134. child_item.data[2] = str(count)
  135. child_item.data[3] = str(time)
  136. child_item.data[4] = self.timePercent(time)
  137. child_item.data[5] = str(branch_count)
  138. child_item.data[6] = self.branchPercent(branch_count)
  139. self.child_items.append(child_item)
  140. self.child_count += 1
  141. def selectCalls(self):
  142. self.query_done = True;
  143. query = QSqlQuery(self.db)
  144. ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
  145. '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
  146. '( 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 ) ) ), '
  147. '( SELECT ip FROM call_paths where id = call_path_id ) '
  148. '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) +
  149. 'ORDER BY call_path_id')
  150. if not ret:
  151. raise Exception("Query failed: " + query.lastError().text())
  152. last_call_path_id = 0
  153. name = ""
  154. dso = ""
  155. count = 0
  156. branch_count = 0
  157. total_branch_count = 0
  158. time = 0
  159. total_time = 0
  160. while query.next():
  161. if query.value(1) == last_call_path_id:
  162. count += 1
  163. branch_count += query.value(2)
  164. time += query.value(4) - query.value(3)
  165. else:
  166. if count:
  167. self.addChild(last_call_path_id, name, dso, count, time, branch_count)
  168. last_call_path_id = query.value(1)
  169. name = query.value(5)
  170. dso = query.value(6)
  171. count = 1
  172. total_branch_count += branch_count
  173. total_time += time
  174. branch_count = query.value(2)
  175. time = query.value(4) - query.value(3)
  176. if count:
  177. self.addChild(last_call_path_id, name, dso, count, time, branch_count)
  178. total_branch_count += branch_count
  179. total_time += time
  180. # Top level does not have time or branch count, so fix that here
  181. if total_branch_count > self.branch_count:
  182. self.branch_count = total_branch_count
  183. if self.branch_count:
  184. for child_item in self.child_items:
  185. child_item.data[6] = self.branchPercent(child_item.branch_count)
  186. if total_time > self.time:
  187. self.time = total_time
  188. if self.time:
  189. for child_item in self.child_items:
  190. child_item.data[4] = self.timePercent(child_item.time)
  191. def childCount(self):
  192. if not self.query_done:
  193. self.selectCalls()
  194. return self.child_count
  195. def columnCount(self):
  196. return 7
  197. def columnHeader(self, column):
  198. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  199. return headers[column]
  200. def getData(self, column):
  201. return self.data[column]
  202. class TreeModel(QAbstractItemModel):
  203. def __init__(self, db, parent=None):
  204. super(TreeModel, self).__init__(parent)
  205. self.db = db
  206. self.root = TreeItem(db, 0, None)
  207. def columnCount(self, parent):
  208. return self.root.columnCount()
  209. def rowCount(self, parent):
  210. if parent.isValid():
  211. parent_item = parent.internalPointer()
  212. else:
  213. parent_item = self.root
  214. return parent_item.childCount()
  215. def headerData(self, section, orientation, role):
  216. if role == Qt.TextAlignmentRole:
  217. if section > 1:
  218. return Qt.AlignRight
  219. if role != Qt.DisplayRole:
  220. return None
  221. if orientation != Qt.Horizontal:
  222. return None
  223. return self.root.columnHeader(section)
  224. def parent(self, child):
  225. child_item = child.internalPointer()
  226. if child_item is self.root:
  227. return QModelIndex()
  228. parent_item = child_item.getParentItem()
  229. return self.createIndex(parent_item.getRow(), 0, parent_item)
  230. def index(self, row, column, parent):
  231. if parent.isValid():
  232. parent_item = parent.internalPointer()
  233. else:
  234. parent_item = self.root
  235. child_item = parent_item.getChildItem(row)
  236. return self.createIndex(row, column, child_item)
  237. def data(self, index, role):
  238. if role == Qt.TextAlignmentRole:
  239. if index.column() > 1:
  240. return Qt.AlignRight
  241. if role != Qt.DisplayRole:
  242. return None
  243. index_item = index.internalPointer()
  244. return index_item.getData(index.column())
  245. class MainWindow(QMainWindow):
  246. def __init__(self, db, dbname, parent=None):
  247. super(MainWindow, self).__init__(parent)
  248. self.setObjectName("MainWindow")
  249. self.setWindowTitle("Call Graph: " + dbname)
  250. self.move(100, 100)
  251. self.resize(800, 600)
  252. style = self.style()
  253. icon = style.standardIcon(QStyle.SP_MessageBoxInformation)
  254. self.setWindowIcon(icon);
  255. self.model = TreeModel(db)
  256. self.view = QTreeView()
  257. self.view.setModel(self.model)
  258. self.setCentralWidget(self.view)
  259. if __name__ == '__main__':
  260. if (len(sys.argv) < 2):
  261. print >> sys.stderr, "Usage is: call-graph-from-postgresql.py <database name>"
  262. raise Exception("Too few arguments")
  263. dbname = sys.argv[1]
  264. db = QSqlDatabase.addDatabase('QPSQL')
  265. opts = dbname.split()
  266. for opt in opts:
  267. if '=' in opt:
  268. opt = opt.split('=')
  269. if opt[0] == 'hostname':
  270. db.setHostName(opt[1])
  271. elif opt[0] == 'port':
  272. db.setPort(int(opt[1]))
  273. elif opt[0] == 'username':
  274. db.setUserName(opt[1])
  275. elif opt[0] == 'password':
  276. db.setPassword(opt[1])
  277. elif opt[0] == 'dbname':
  278. dbname = opt[1]
  279. else:
  280. dbname = opt
  281. db.setDatabaseName(dbname)
  282. if not db.open():
  283. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  284. app = QApplication(sys.argv)
  285. window = MainWindow(db, dbname)
  286. window.show()
  287. err = app.exec_()
  288. db.close()
  289. sys.exit(err)