2 # call-graph-from-postgresql.py: create call-graph from postgresql database
3 # Copyright (c) 2014, 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 the
15 # export-to-postgresql.py script. Refer to that script for details.
17 # Following on from the example in the export-to-postgresql.py script, a
18 # call-graph can be displayed for the pt_example database like this:
20 # python tools/perf/scripts/python/call-graph-from-postgresql.py pt_example
22 # Note this script supports connecting to remote databases by setting hostname,
23 # port, username, password, and dbname e.g.
25 # python tools/perf/scripts/python/call-graph-from-postgresql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
27 # The result is a GUI window with a tree representing a context-sensitive
28 # call-graph. Expanding a couple of levels of the tree and adjusting column
29 # widths to suit will display something like:
31 # Call Graph: pt_example
32 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
35 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
36 # |- unknown unknown 1 13198 0.1 1 0.0
37 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
38 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
39 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
40 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
41 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
42 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
43 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
44 # v- main ls 1 8182043 99.6 180254 99.9
47 # The top level is a command name (comm)
48 # The next level is a thread (pid:tid)
49 # Subsequent levels are functions
50 # 'Count' is the number of calls
51 # 'Time' is the elapsed time until the function returns
52 # Percentages are relative to the level above
53 # 'Branch Count' is the total number of branches for that function and all
54 # functions that it calls
57 from PySide
.QtCore
import *
58 from PySide
.QtGui
import *
59 from PySide
.QtSql
import *
64 def __init__(self
, db
, row
, parent_item
):
67 self
.parent_item
= parent_item
68 self
.query_done
= False;
71 self
.data
= ["", "", "", "", "", "", ""]
81 self
.query_done
= True
82 query
= QSqlQuery(self
.db
)
83 ret
= query
.exec_('SELECT id, comm FROM comms')
85 raise Exception("Query failed: " + query
.lastError().text())
87 if not query
.value(0):
89 child_item
= TreeItem(self
.db
, self
.child_count
, self
)
90 self
.child_items
.append(child_item
)
92 child_item
.setUpLevel1(query
.value(0), query
.value(1))
94 def setUpLevel1(self
, comm_id
, comm
):
95 self
.query_done
= True;
96 self
.comm_id
= comm_id
100 query
= QSqlQuery(self
.db
)
101 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 raise Exception("Query failed: " + query
.lastError().text())
105 child_item
= TreeItem(self
.db
, self
.child_count
, self
)
106 self
.child_items
.append(child_item
)
107 self
.child_count
+= 1
108 child_item
.setUpLevel2(comm_id
, query
.value(0), query
.value(1), query
.value(2))
110 def setUpLevel2(self
, comm_id
, thread_id
, pid
, tid
):
111 self
.comm_id
= comm_id
112 self
.thread_id
= thread_id
113 self
.data
[0] = str(pid
) + ":" + str(tid
)
115 def getChildItem(self
, row
):
116 return self
.child_items
[row
]
118 def getParentItem(self
):
119 return self
.parent_item
124 def timePercent(self
, b
):
127 x
= (b
* Decimal(100)) / self
.time
128 return str(x
.quantize(Decimal('.1'), rounding
=ROUND_HALF_UP
))
130 def branchPercent(self
, b
):
131 if not self
.branch_count
:
133 x
= (b
* Decimal(100)) / self
.branch_count
134 return str(x
.quantize(Decimal('.1'), rounding
=ROUND_HALF_UP
))
136 def addChild(self
, call_path_id
, name
, dso
, count
, time
, branch_count
):
137 child_item
= TreeItem(self
.db
, self
.child_count
, self
)
138 child_item
.comm_id
= self
.comm_id
139 child_item
.thread_id
= self
.thread_id
140 child_item
.call_path_id
= call_path_id
141 child_item
.branch_count
= branch_count
142 child_item
.time
= time
143 child_item
.data
[0] = name
144 if dso
== "[kernel.kallsyms]":
146 child_item
.data
[1] = dso
147 child_item
.data
[2] = str(count
)
148 child_item
.data
[3] = str(time
)
149 child_item
.data
[4] = self
.timePercent(time
)
150 child_item
.data
[5] = str(branch_count
)
151 child_item
.data
[6] = self
.branchPercent(branch_count
)
152 self
.child_items
.append(child_item
)
153 self
.child_count
+= 1
155 def selectCalls(self
):
156 self
.query_done
= True;
157 query
= QSqlQuery(self
.db
)
158 ret
= query
.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
159 '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
160 '( 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 ) ) ), '
161 '( SELECT ip FROM call_paths where id = call_path_id ) '
162 '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
) +
163 'ORDER BY call_path_id')
165 raise Exception("Query failed: " + query
.lastError().text())
166 last_call_path_id
= 0
171 total_branch_count
= 0
175 if query
.value(1) == last_call_path_id
:
177 branch_count
+= query
.value(2)
178 time
+= query
.value(4) - query
.value(3)
181 self
.addChild(last_call_path_id
, name
, dso
, count
, time
, branch_count
)
182 last_call_path_id
= query
.value(1)
183 name
= query
.value(5)
186 total_branch_count
+= branch_count
188 branch_count
= query
.value(2)
189 time
= query
.value(4) - query
.value(3)
191 self
.addChild(last_call_path_id
, name
, dso
, count
, time
, branch_count
)
192 total_branch_count
+= branch_count
194 # Top level does not have time or branch count, so fix that here
195 if total_branch_count
> self
.branch_count
:
196 self
.branch_count
= total_branch_count
197 if self
.branch_count
:
198 for child_item
in self
.child_items
:
199 child_item
.data
[6] = self
.branchPercent(child_item
.branch_count
)
200 if total_time
> self
.time
:
201 self
.time
= total_time
203 for child_item
in self
.child_items
:
204 child_item
.data
[4] = self
.timePercent(child_item
.time
)
206 def childCount(self
):
207 if not self
.query_done
:
209 return self
.child_count
211 def columnCount(self
):
214 def columnHeader(self
, column
):
215 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
216 return headers
[column
]
218 def getData(self
, column
):
219 return self
.data
[column
]
221 class TreeModel(QAbstractItemModel
):
223 def __init__(self
, db
, parent
=None):
224 super(TreeModel
, self
).__init
__(parent
)
226 self
.root
= TreeItem(db
, 0, None)
228 def columnCount(self
, parent
):
229 return self
.root
.columnCount()
231 def rowCount(self
, parent
):
233 parent_item
= parent
.internalPointer()
235 parent_item
= self
.root
236 return parent_item
.childCount()
238 def headerData(self
, section
, orientation
, role
):
239 if role
== Qt
.TextAlignmentRole
:
242 if role
!= Qt
.DisplayRole
:
244 if orientation
!= Qt
.Horizontal
:
246 return self
.root
.columnHeader(section
)
248 def parent(self
, child
):
249 child_item
= child
.internalPointer()
250 if child_item
is self
.root
:
252 parent_item
= child_item
.getParentItem()
253 return self
.createIndex(parent_item
.getRow(), 0, parent_item
)
255 def index(self
, row
, column
, parent
):
257 parent_item
= parent
.internalPointer()
259 parent_item
= self
.root
260 child_item
= parent_item
.getChildItem(row
)
261 return self
.createIndex(row
, column
, child_item
)
263 def data(self
, index
, role
):
264 if role
== Qt
.TextAlignmentRole
:
265 if index
.column() > 1:
267 if role
!= Qt
.DisplayRole
:
269 index_item
= index
.internalPointer()
270 return index_item
.getData(index
.column())
272 class MainWindow(QMainWindow
):
274 def __init__(self
, db
, dbname
, parent
=None):
275 super(MainWindow
, self
).__init
__(parent
)
277 self
.setObjectName("MainWindow")
278 self
.setWindowTitle("Call Graph: " + dbname
)
280 self
.resize(800, 600)
282 icon
= style
.standardIcon(QStyle
.SP_MessageBoxInformation
)
283 self
.setWindowIcon(icon
);
285 self
.model
= TreeModel(db
)
287 self
.view
= QTreeView()
288 self
.view
.setModel(self
.model
)
290 self
.setCentralWidget(self
.view
)
292 if __name__
== '__main__':
293 if (len(sys
.argv
) < 2):
294 print >> sys
.stderr
, "Usage is: call-graph-from-postgresql.py <database name>"
295 raise Exception("Too few arguments")
299 db
= QSqlDatabase
.addDatabase('QPSQL')
301 opts
= dbname
.split()
305 if opt
[0] == 'hostname':
306 db
.setHostName(opt
[1])
307 elif opt
[0] == 'port':
308 db
.setPort(int(opt
[1]))
309 elif opt
[0] == 'username':
310 db
.setUserName(opt
[1])
311 elif opt
[0] == 'password':
312 db
.setPassword(opt
[1])
313 elif opt
[0] == 'dbname':
318 db
.setDatabaseName(dbname
)
320 raise Exception("Failed to open database " + dbname
+ " error: " + db
.lastError().text())
322 app
= QApplication(sys
.argv
)
323 window
= MainWindow(db
, dbname
)