History

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.

Adrian Hunter committed 8 years ago

Commit 031c2a01 parent 341e73c4 files · +6 −6Browse files

Showing the changes to tools/perf/scripts/python/exported-sql-viewer.py (1 of 4 files). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+395−0
@@ -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+
49+import sys
50+from PySide.QtCore import *
51+from PySide.QtGui import *
52+from PySide.QtSql import *
53+from decimal import *
54+
55+# Data formatting helpers
56+
57+def dsoname(name):
58+ if name == "[kernel.kallsyms]":
59+ return "[kernel]"
60+ return name
61+
62+# Percent to one decimal place
63+
64+def 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+
72+def 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+
79+class 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+
143+class 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+
179+class 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+
209+class 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+
219+class 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+
237+class 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+
258+class 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+
275+class 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+
294+class 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+
319+class 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+
330+class 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+
366+def 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+
394+if __name__ == "__main__":
395+ Main()

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t