aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/scripts/python/exported-sql-viewer.py
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@intel.com>2018-10-01 02:28:46 -0400
committerArnaldo Carvalho de Melo <acme@redhat.com>2018-10-23 13:26:44 -0400
commit031c2a004ba75a4f8f2a6d0a7ca6f2fe5912de22 (patch)
treee8795e96609efbad3bf294a4d4d270a47778f0b7 /tools/perf/scripts/python/exported-sql-viewer.py
parent341e73cbd3019d350d1271803b45d84af88f2408 (diff)
perf scripts python: call-graph-from-sql.py: Rename to exported-sql-viewer.py
Additional reports will be added to the script so rename to reflect the more general purpose. Signed-off-by: Adrian Hunter <adrian.hunter@intel.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Jiri Olsa <jolsa@redhat.com> Link: http://lkml.kernel.org/r/20181001062853.28285-13-adrian.hunter@intel.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
-rwxr-xr-xtools/perf/scripts/python/exported-sql-viewer.py395
1 files changed, 395 insertions, 0 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
new file mode 100755
index 000000000000..03e7a1de7f31
--- /dev/null
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -0,0 +1,395 @@
1#!/usr/bin/python2
2# SPDX-License-Identifier: GPL-2.0
3# exported-sql-viewer.py: view data from sql database
4# Copyright (c) 2014-2018, Intel Corporation.
5
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
8# scripts for details.
9#
10# Following on from the example in the export scripts, a
11# call-graph can be displayed for the pt_example database like this:
12#
13# python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14#
15# Note that for PostgreSQL, this script supports connecting to remote databases
16# by setting hostname, port, username, password, and dbname e.g.
17#
18# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19#
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:
23#
24# Call Graph: pt_example
25# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
26# v- ls
27# v- 2638:2638
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
38#
39# Points to note:
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
48
49import sys
50from PySide.QtCore import *
51from PySide.QtGui import *
52from PySide.QtSql import *
53from decimal import *
54
55# Data formatting helpers
56
57def dsoname(name):
58 if name == "[kernel.kallsyms]":
59 return "[kernel]"
60 return name
61
62# Percent to one decimal place
63
64def PercentToOneDP(n, d):
65 if not d:
66 return "0.0"
67 x = (n * Decimal(100)) / d
68 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
69
70# Helper for queries that must not fail
71
72def QueryExec(query, stmt):
73 ret = query.exec_(stmt)
74 if not ret:
75 raise Exception("Query failed: " + query.lastError().text())
76
77# Tree data model
78
79class TreeModel(QAbstractItemModel):
80
81 def __init__(self, root, parent=None):
82 super(TreeModel, self).__init__(parent)
83 self.root = root
84 self.last_row_read = 0
85
86 def Item(self, parent):
87 if parent.isValid():
88 return parent.internalPointer()
89 else:
90 return self.root
91
92 def rowCount(self, parent):
93 result = self.Item(parent).childCount()
94 if result < 0:
95 result = 0
96 self.dataChanged.emit(parent, parent)
97 return result
98
99 def hasChildren(self, parent):
100 return self.Item(parent).hasChildren()
101
102 def headerData(self, section, orientation, role):
103 if role == Qt.TextAlignmentRole:
104 return self.columnAlignment(section)
105 if role != Qt.DisplayRole:
106 return None
107 if orientation != Qt.Horizontal:
108 return None
109 return self.columnHeader(section)
110
111 def parent(self, child):
112 child_item = child.internalPointer()
113 if child_item is self.root:
114 return QModelIndex()
115 parent_item = child_item.getParentItem()
116 return self.createIndex(parent_item.getRow(), 0, parent_item)
117
118 def index(self, row, column, parent):
119 child_item = self.Item(parent).getChildItem(row)
120 return self.createIndex(row, column, child_item)
121
122 def DisplayData(self, item, index):
123 return item.getData(index.column())
124
125 def columnAlignment(self, column):
126 return Qt.AlignLeft
127
128 def columnFont(self, column):
129 return None
130
131 def data(self, index, role):
132 if role == Qt.TextAlignmentRole:
133 return self.columnAlignment(index.column())
134 if role == Qt.FontRole:
135 return self.columnFont(index.column())
136 if role != Qt.DisplayRole:
137 return None
138 item = index.internalPointer()
139 return self.DisplayData(item, index)
140
141# Context-sensitive call graph data model item base
142
143class CallGraphLevelItemBase(object):
144
145 def __init__(self, glb, row, parent_item):
146 self.glb = glb
147 self.row = row
148 self.parent_item = parent_item
149 self.query_done = False;
150 self.child_count = 0
151 self.child_items = []
152
153 def getChildItem(self, row):
154 return self.child_items[row]
155
156 def getParentItem(self):
157 return self.parent_item
158
159 def getRow(self):
160 return self.row
161
162 def childCount(self):
163 if not self.query_done:
164 self.Select()
165 if not self.child_count:
166 return -1
167 return self.child_count
168
169 def hasChildren(self):
170 if not self.query_done:
171 return True
172 return self.child_count > 0
173
174 def getData(self, column):
175 return self.data[column]
176
177# Context-sensitive call graph data model level 2+ item base
178
179class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
180
181 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
182 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
183 self.comm_id = comm_id
184 self.thread_id = thread_id
185 self.call_path_id = call_path_id
186 self.branch_count = branch_count
187 self.time = time
188
189 def Select(self):
190 self.query_done = True;
191 query = QSqlQuery(self.glb.db)
192 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
193 " FROM calls"
194 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
195 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
196 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
197 " WHERE parent_call_path_id = " + str(self.call_path_id) +
198 " AND comm_id = " + str(self.comm_id) +
199 " AND thread_id = " + str(self.thread_id) +
200 " GROUP BY call_path_id, name, short_name"
201 " ORDER BY call_path_id")
202 while query.next():
203 child_item = CallGraphLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
204 self.child_items.append(child_item)
205 self.child_count += 1
206
207# Context-sensitive call graph data model level three item
208
209class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
210
211 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
212 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
213 dso = dsoname(dso)
214 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
215 self.dbid = call_path_id
216
217# Context-sensitive call graph data model level two item
218
219class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
220
221 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
222 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
223 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
224 self.dbid = thread_id
225
226 def Select(self):
227 super(CallGraphLevelTwoItem, self).Select()
228 for child_item in self.child_items:
229 self.time += child_item.time
230 self.branch_count += child_item.branch_count
231 for child_item in self.child_items:
232 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
233 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
234
235# Context-sensitive call graph data model level one item
236
237class CallGraphLevelOneItem(CallGraphLevelItemBase):
238
239 def __init__(self, glb, row, comm_id, comm, parent_item):
240 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
241 self.data = [comm, "", "", "", "", "", ""]
242 self.dbid = comm_id
243
244 def Select(self):
245 self.query_done = True;
246 query = QSqlQuery(self.glb.db)
247 QueryExec(query, "SELECT thread_id, pid, tid"
248 " FROM comm_threads"
249 " INNER JOIN threads ON thread_id = threads.id"
250 " WHERE comm_id = " + str(self.dbid))
251 while query.next():
252 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
253 self.child_items.append(child_item)
254 self.child_count += 1
255
256# Context-sensitive call graph data model root item
257
258class CallGraphRootItem(CallGraphLevelItemBase):
259
260 def __init__(self, glb):
261 super(CallGraphRootItem, self).__init__(glb, 0, None)
262 self.dbid = 0
263 self.query_done = True;
264 query = QSqlQuery(glb.db)
265 QueryExec(query, "SELECT id, comm FROM comms")
266 while query.next():
267 if not query.value(0):
268 continue
269 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
270 self.child_items.append(child_item)
271 self.child_count += 1
272
273# Context-sensitive call graph data model
274
275class CallGraphModel(TreeModel):
276
277 def __init__(self, glb, parent=None):
278 super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
279 self.glb = glb
280
281 def columnCount(self, parent=None):
282 return 7
283
284 def columnHeader(self, column):
285 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
286 return headers[column]
287
288 def columnAlignment(self, column):
289 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
290 return alignment[column]
291
292# Main window
293
294class MainWindow(QMainWindow):
295
296 def __init__(self, glb, parent=None):
297 super(MainWindow, self).__init__(parent)
298
299 self.glb = glb
300
301 self.setWindowTitle("Call Graph: " + glb.dbname)
302 self.move(100, 100)
303 self.resize(800, 600)
304 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
305 self.setMinimumSize(200, 100)
306
307 self.model = CallGraphModel(glb)
308
309 self.view = QTreeView()
310 self.view.setModel(self.model)
311
312 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
313 self.view.setColumnWidth(c, w)
314
315 self.setCentralWidget(self.view)
316
317# Global data
318
319class Glb():
320
321 def __init__(self, dbref, db, dbname):
322 self.dbref = dbref
323 self.db = db
324 self.dbname = dbname
325 self.app = None
326 self.mainwindow = None
327
328# Database reference
329
330class DBRef():
331
332 def __init__(self, is_sqlite3, dbname):
333 self.is_sqlite3 = is_sqlite3
334 self.dbname = dbname
335
336 def Open(self, connection_name):
337 dbname = self.dbname
338 if self.is_sqlite3:
339 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
340 else:
341 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
342 opts = dbname.split()
343 for opt in opts:
344 if "=" in opt:
345 opt = opt.split("=")
346 if opt[0] == "hostname":
347 db.setHostName(opt[1])
348 elif opt[0] == "port":
349 db.setPort(int(opt[1]))
350 elif opt[0] == "username":
351 db.setUserName(opt[1])
352 elif opt[0] == "password":
353 db.setPassword(opt[1])
354 elif opt[0] == "dbname":
355 dbname = opt[1]
356 else:
357 dbname = opt
358
359 db.setDatabaseName(dbname)
360 if not db.open():
361 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
362 return db, dbname
363
364# Main
365
366def Main():
367 if (len(sys.argv) < 2):
368 print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
369 raise Exception("Too few arguments")
370
371 dbname = sys.argv[1]
372
373 is_sqlite3 = False
374 try:
375 f = open(dbname)
376 if f.read(15) == "SQLite format 3":
377 is_sqlite3 = True
378 f.close()
379 except:
380 pass
381
382 dbref = DBRef(is_sqlite3, dbname)
383 db, dbname = dbref.Open("main")
384 glb = Glb(dbref, db, dbname)
385 app = QApplication(sys.argv)
386 glb.app = app
387 mainwindow = MainWindow(glb)
388 glb.mainwindow = mainwindow
389 mainwindow.show()
390 err = app.exec_()
391 db.close()
392 sys.exit(err)
393
394if __name__ == "__main__":
395 Main()