2 # call-graph-from-sql.py: create call-graph from sql database
3 # Copyright (c) 2014-2017, Intel Corporation.
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.
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
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.
18 # Following on from the example in the export scripts, a
19 # call-graph can be displayed for the pt_example database like this:
21 # python tools/perf/scripts/python/call-graph-from-sql.py pt_example
23 # Note that for PostgreSQL, this script supports connecting to remote databases
24 # by setting hostname, port, username, password, and dbname e.g.
26 # python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
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:
32 # Call Graph: pt_example
33 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
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
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
58 from PySide
.QtCore
import *
59 from PySide
.QtGui
import *
60 from PySide
.QtSql
import *
65 def __init__(self
, db
, row
, parent_item
):
68 self
.parent_item
= parent_item
69 self
.query_done
= False;
72 self
.data
= ["", "", "", "", "", "", ""]
82 self
.query_done
= True
83 query
= QSqlQuery(self
.db
)
84 ret
= query
.exec_('SELECT id, comm FROM comms')
86 raise Exception("Query failed: " + query
.lastError().text())
88 if not query
.value(0):
90 child_item
= TreeItem(self
.db
, self
.child_count
, self
)
91 self
.child_items
.append(child_item
)
93 child_item
.setUpLevel1(query
.value(0), query
.value(1))
95 def setUpLevel1(self
, comm_id
, comm
):
96 self
.query_done
= True;
97 self
.comm_id
= comm_id
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
))
104 raise Exception("Query failed: " + query
.lastError().text())
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))
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
)
116 def getChildItem(self
, row
):
117 return self
.child_items
[row
]
119 def getParentItem(self
):
120 return self
.parent_item
125 def timePercent(self
, b
):
128 x
= (b
* Decimal(100)) / self
.time
129 return str(x
.quantize(Decimal('.1'), rounding
=ROUND_HALF_UP
))
131 def branchPercent(self
, b
):
132 if not self
.branch_count
:
134 x
= (b
* Decimal(100)) / self
.branch_count
135 return str(x
.quantize(Decimal('.1'), rounding
=ROUND_HALF_UP
))
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]":
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
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')
166 raise Exception("Query failed: " + query
.lastError().text())
167 last_call_path_id
= 0
172 total_branch_count
= 0
176 if query
.value(1) == last_call_path_id
:
178 branch_count
+= query
.value(2)
179 time
+= query
.value(4) - query
.value(3)
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)
187 total_branch_count
+= branch_count
189 branch_count
= query
.value(2)
190 time
= query
.value(4) - query
.value(3)
192 self
.addChild(last_call_path_id
, name
, dso
, count
, time
, branch_count
)
193 total_branch_count
+= branch_count
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
204 for child_item
in self
.child_items
:
205 child_item
.data
[4] = self
.timePercent(child_item
.time
)
207 def childCount(self
):
208 if not self
.query_done
:
210 return self
.child_count
212 def columnCount(self
):
215 def columnHeader(self
, column
):
216 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
217 return headers
[column
]
219 def getData(self
, column
):
220 return self
.data
[column
]
222 class TreeModel(QAbstractItemModel
):
224 def __init__(self
, db
, parent
=None):
225 super(TreeModel
, self
).__init
__(parent
)
227 self
.root
= TreeItem(db
, 0, None)
229 def columnCount(self
, parent
):
230 return self
.root
.columnCount()
232 def rowCount(self
, parent
):
234 parent_item
= parent
.internalPointer()
236 parent_item
= self
.root
237 return parent_item
.childCount()
239 def headerData(self
, section
, orientation
, role
):
240 if role
== Qt
.TextAlignmentRole
:
243 if role
!= Qt
.DisplayRole
:
245 if orientation
!= Qt
.Horizontal
:
247 return self
.root
.columnHeader(section
)
249 def parent(self
, child
):
250 child_item
= child
.internalPointer()
251 if child_item
is self
.root
:
253 parent_item
= child_item
.getParentItem()
254 return self
.createIndex(parent_item
.getRow(), 0, parent_item
)
256 def index(self
, row
, column
, parent
):
258 parent_item
= parent
.internalPointer()
260 parent_item
= self
.root
261 child_item
= parent_item
.getChildItem(row
)
262 return self
.createIndex(row
, column
, child_item
)
264 def data(self
, index
, role
):
265 if role
== Qt
.TextAlignmentRole
:
266 if index
.column() > 1:
268 if role
!= Qt
.DisplayRole
:
270 index_item
= index
.internalPointer()
271 return index_item
.getData(index
.column())
273 class MainWindow(QMainWindow
):
275 def __init__(self
, db
, dbname
, parent
=None):
276 super(MainWindow
, self
).__init
__(parent
)
278 self
.setObjectName("MainWindow")
279 self
.setWindowTitle("Call Graph: " + dbname
)
281 self
.resize(800, 600)
283 icon
= style
.standardIcon(QStyle
.SP_MessageBoxInformation
)
284 self
.setWindowIcon(icon
);
286 self
.model
= TreeModel(db
)
288 self
.view
= QTreeView()
289 self
.view
.setModel(self
.model
)
291 self
.setCentralWidget(self
.view
)
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")
303 if f
.read(15) == "SQLite format 3":
310 db
= QSqlDatabase
.addDatabase('QSQLITE')
312 db
= QSqlDatabase
.addDatabase('QPSQL')
313 opts
= dbname
.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':
330 db
.setDatabaseName(dbname
)
332 raise Exception("Failed to open database " + dbname
+ " error: " + db
.lastError().text())
334 app
= QApplication(sys
.argv
)
335 window
= MainWindow(db
, dbname
)