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