2 # SPDX-License-Identifier: GPL-2.0
3 # exported-sql-viewer.py: view data from sql database
4 # Copyright (c) 2014-2018, Intel Corporation.
6 # To use this script you will need to have exported data using either the
7 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
10 # Following on from the example in the export scripts, a
11 # call-graph can be displayed for the pt_example database like this:
13 # python tools/perf/scripts/python/call-graph-from-sql.py pt_example
15 # Note that for PostgreSQL, this script supports connecting to remote databases
16 # by setting hostname, port, username, password, and dbname e.g.
18 # python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
20 # The result is a GUI window with a tree representing a context-sensitive
21 # call-graph. Expanding a couple of levels of the tree and adjusting column
22 # widths to suit will display something like:
24 # Call Graph: pt_example
25 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
28 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
29 # |- unknown unknown 1 13198 0.1 1 0.0
30 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
31 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
32 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
33 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
34 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
35 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
36 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
37 # v- main ls 1 8182043 99.6 180254 99.9
40 # The top level is a command name (comm)
41 # The next level is a thread (pid:tid)
42 # Subsequent levels are functions
43 # 'Count' is the number of calls
44 # 'Time' is the elapsed time until the function returns
45 # Percentages are relative to the level above
46 # 'Branch Count' is the total number of branches for that function and all
47 # functions that it calls
50 from PySide.QtCore import *
51 from PySide.QtGui import *
52 from PySide.QtSql import *
55 # Data formatting helpers
58 if name == "[kernel.kallsyms]":
62 # Percent to one decimal place
64 def PercentToOneDP(n, d):
67 x = (n * Decimal(100)) / d
68 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
70 # Helper for queries that must not fail
72 def QueryExec(query, stmt):
73 ret = query.exec_(stmt)
75 raise Exception("Query failed: " + query.lastError().text())
79 def __init__(self, db, row, parent_item):
82 self.parent_item = parent_item
83 self.query_done = False;
86 self.data = ["", "", "", "", "", "", ""]
96 self.query_done = True
97 query = QSqlQuery(self.db)
98 QueryExec(query, 'SELECT id, comm FROM comms')
100 if not query.value(0):
102 child_item = TreeItem(self.db, self.child_count, self)
103 self.child_items.append(child_item)
104 self.child_count += 1
105 child_item.setUpLevel1(query.value(0), query.value(1))
107 def setUpLevel1(self, comm_id, comm):
108 self.query_done = True;
109 self.comm_id = comm_id
111 self.child_items = []
113 query = QSqlQuery(self.db)
114 QueryExec(query, '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))
116 child_item = TreeItem(self.db, self.child_count, self)
117 self.child_items.append(child_item)
118 self.child_count += 1
119 child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2))
121 def setUpLevel2(self, comm_id, thread_id, pid, tid):
122 self.comm_id = comm_id
123 self.thread_id = thread_id
124 self.data[0] = str(pid) + ":" + str(tid)
126 def getChildItem(self, row):
127 return self.child_items[row]
129 def getParentItem(self):
130 return self.parent_item
135 def addChild(self, call_path_id, name, dso, count, time, branch_count):
136 child_item = TreeItem(self.db, self.child_count, self)
137 child_item.comm_id = self.comm_id
138 child_item.thread_id = self.thread_id
139 child_item.call_path_id = call_path_id
140 child_item.branch_count = branch_count
141 child_item.time = time
142 child_item.data[0] = name
143 child_item.data[1] = dsoname(dso)
144 child_item.data[2] = str(count)
145 child_item.data[3] = str(time)
146 child_item.data[4] = PercentToOneDP(time, self.time)
147 child_item.data[5] = str(branch_count)
148 child_item.data[6] = PercentToOneDP(branch_count, self.branch_count)
149 self.child_items.append(child_item)
150 self.child_count += 1
152 def selectCalls(self):
153 self.query_done = True;
154 query = QSqlQuery(self.db)
155 ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
156 '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
157 '( 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 ) ) ), '
158 '( SELECT ip FROM call_paths where id = call_path_id ) '
159 '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) +
160 ' ORDER BY call_path_id')
162 raise Exception("Query failed: " + query.lastError().text())
163 last_call_path_id = 0
168 total_branch_count = 0
172 if query.value(1) == last_call_path_id:
174 branch_count += query.value(2)
175 time += query.value(4) - query.value(3)
178 self.addChild(last_call_path_id, name, dso, count, time, branch_count)
179 last_call_path_id = query.value(1)
180 name = query.value(5)
183 total_branch_count += branch_count
185 branch_count = query.value(2)
186 time = query.value(4) - query.value(3)
188 self.addChild(last_call_path_id, name, dso, count, time, branch_count)
189 total_branch_count += branch_count
191 # Top level does not have time or branch count, so fix that here
192 if total_branch_count > self.branch_count:
193 self.branch_count = total_branch_count
194 if self.branch_count:
195 for child_item in self.child_items:
196 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
197 if total_time > self.time:
198 self.time = total_time
200 for child_item in self.child_items:
201 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
203 def childCount(self):
204 if not self.query_done:
206 return self.child_count
208 def hasChildren(self):
209 if not self.query_done:
211 return self.child_count > 0
213 def getData(self, column):
214 return self.data[column]
218 class TreeModel(QAbstractItemModel):
220 def __init__(self, root, parent=None):
221 super(TreeModel, self).__init__(parent)
223 self.last_row_read = 0
225 def Item(self, parent):
227 return parent.internalPointer()
231 def rowCount(self, parent):
232 result = self.Item(parent).childCount()
235 self.dataChanged.emit(parent, parent)
238 def hasChildren(self, parent):
239 return self.Item(parent).hasChildren()
241 def headerData(self, section, orientation, role):
242 if role == Qt.TextAlignmentRole:
243 return self.columnAlignment(section)
244 if role != Qt.DisplayRole:
246 if orientation != Qt.Horizontal:
248 return self.columnHeader(section)
250 def parent(self, child):
251 child_item = child.internalPointer()
252 if child_item is self.root:
254 parent_item = child_item.getParentItem()
255 return self.createIndex(parent_item.getRow(), 0, parent_item)
257 def index(self, row, column, parent):
258 child_item = self.Item(parent).getChildItem(row)
259 return self.createIndex(row, column, child_item)
261 def DisplayData(self, item, index):
262 return item.getData(index.column())
264 def columnAlignment(self, column):
267 def columnFont(self, column):
270 def data(self, index, role):
271 if role == Qt.TextAlignmentRole:
272 return self.columnAlignment(index.column())
273 if role == Qt.FontRole:
274 return self.columnFont(index.column())
275 if role != Qt.DisplayRole:
277 item = index.internalPointer()
278 return self.DisplayData(item, index)
280 # Context-sensitive call graph data model
282 class CallGraphModel(TreeModel):
284 def __init__(self, glb, parent=None):
285 super(CallGraphModel, self).__init__(TreeItem(glb.db, 0, None), parent)
288 def columnCount(self, parent=None):
291 def columnHeader(self, column):
292 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
293 return headers[column]
295 def columnAlignment(self, column):
296 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
297 return alignment[column]
301 class MainWindow(QMainWindow):
303 def __init__(self, glb, parent=None):
304 super(MainWindow, self).__init__(parent)
308 self.setWindowTitle("Call Graph: " + glb.dbname)
310 self.resize(800, 600)
311 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
312 self.setMinimumSize(200, 100)
314 self.model = CallGraphModel(glb)
316 self.view = QTreeView()
317 self.view.setModel(self.model)
319 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
320 self.view.setColumnWidth(c, w)
322 self.setCentralWidget(self.view)
328 def __init__(self, dbref, db, dbname):
333 self.mainwindow = None
339 def __init__(self, is_sqlite3, dbname):
340 self.is_sqlite3 = is_sqlite3
343 def Open(self, connection_name):
346 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
348 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
349 opts = dbname.split()
353 if opt[0] == "hostname":
354 db.setHostName(opt[1])
355 elif opt[0] == "port":
356 db.setPort(int(opt[1]))
357 elif opt[0] == "username":
358 db.setUserName(opt[1])
359 elif opt[0] == "password":
360 db.setPassword(opt[1])
361 elif opt[0] == "dbname":
366 db.setDatabaseName(dbname)
368 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
374 if (len(sys.argv) < 2):
375 print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
376 raise Exception("Too few arguments")
383 if f.read(15) == "SQLite format 3":
389 dbref = DBRef(is_sqlite3, dbname)
390 db, dbname = dbref.Open("main")
391 glb = Glb(dbref, db, dbname)
392 app = QApplication(sys.argv)
394 mainwindow = MainWindow(glb)
395 glb.mainwindow = mainwindow
401 if __name__ == "__main__":