Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[linux-2.6-microblaze.git] / tools / perf / scripts / python / call-graph-from-sql.py
1 #!/usr/bin/python2
2 # call-graph-from-sql.py: create call-graph from sql database
3 # Copyright (c) 2014-2017, Intel Corporation.
4 #
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.
8 #
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
12 # more details.
13
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.
17 #
18 # Following on from the example in the export scripts, a
19 # call-graph can be displayed for the pt_example database like this:
20 #
21 #       python tools/perf/scripts/python/call-graph-from-sql.py pt_example
22 #
23 # Note that for PostgreSQL, this script supports connecting to remote databases
24 # by setting hostname, port, username, password, and dbname e.g.
25 #
26 #       python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
27 #
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:
31 #
32 #                                         Call Graph: pt_example
33 # Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
34 # v- ls
35 #     v- 2638:2638
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
46 #
47 # Points to note:
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
56
57 import sys
58 from PySide.QtCore import *
59 from PySide.QtGui import *
60 from PySide.QtSql import *
61 from decimal import *
62
63 class TreeItem():
64
65         def __init__(self, db, row, parent_item):
66                 self.db = db
67                 self.row = row
68                 self.parent_item = parent_item
69                 self.query_done = False;
70                 self.child_count = 0
71                 self.child_items = []
72                 self.data = ["", "", "", "", "", "", ""]
73                 self.comm_id = 0
74                 self.thread_id = 0
75                 self.call_path_id = 1
76                 self.branch_count = 0
77                 self.time = 0
78                 if not parent_item:
79                         self.setUpRoot()
80
81         def setUpRoot(self):
82                 self.query_done = True
83                 query = QSqlQuery(self.db)
84                 ret = query.exec_('SELECT id, comm FROM comms')
85                 if not ret:
86                         raise Exception("Query failed: " + query.lastError().text())
87                 while query.next():
88                         if not query.value(0):
89                                 continue
90                         child_item = TreeItem(self.db, self.child_count, self)
91                         self.child_items.append(child_item)
92                         self.child_count += 1
93                         child_item.setUpLevel1(query.value(0), query.value(1))
94
95         def setUpLevel1(self, comm_id, comm):
96                 self.query_done = True;
97                 self.comm_id = comm_id
98                 self.data[0] = comm
99                 self.child_items = []
100                 self.child_count = 0
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))
103                 if not ret:
104                         raise Exception("Query failed: " + query.lastError().text())
105                 while query.next():
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))
110
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)
115
116         def getChildItem(self, row):
117                 return self.child_items[row]
118
119         def getParentItem(self):
120                 return self.parent_item
121
122         def getRow(self):
123                 return self.row
124
125         def timePercent(self, b):
126                 if not self.time:
127                         return "0.0"
128                 x = (b * Decimal(100)) / self.time
129                 return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
130
131         def branchPercent(self, b):
132                 if not self.branch_count:
133                         return "0.0"
134                 x = (b * Decimal(100)) / self.branch_count
135                 return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
136
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]":
146                         dso = "[kernel]"
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
155
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')
165                 if not ret:
166                         raise Exception("Query failed: " + query.lastError().text())
167                 last_call_path_id = 0
168                 name = ""
169                 dso = ""
170                 count = 0
171                 branch_count = 0
172                 total_branch_count = 0
173                 time = 0
174                 total_time = 0
175                 while query.next():
176                         if query.value(1) == last_call_path_id:
177                                 count += 1
178                                 branch_count += query.value(2)
179                                 time += query.value(4) - query.value(3)
180                         else:
181                                 if count:
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)
185                                 dso = query.value(6)
186                                 count = 1
187                                 total_branch_count += branch_count
188                                 total_time += time
189                                 branch_count = query.value(2)
190                                 time = query.value(4) - query.value(3)
191                 if count:
192                         self.addChild(last_call_path_id, name, dso, count, time, branch_count)
193                 total_branch_count += branch_count
194                 total_time += time
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
203                         if self.time:
204                                 for child_item in self.child_items:
205                                         child_item.data[4] = self.timePercent(child_item.time)
206
207         def childCount(self):
208                 if not self.query_done:
209                         self.selectCalls()
210                 return self.child_count
211
212         def columnCount(self):
213                 return 7
214
215         def columnHeader(self, column):
216                 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
217                 return headers[column]
218
219         def getData(self, column):
220                 return self.data[column]
221
222 class TreeModel(QAbstractItemModel):
223
224         def __init__(self, db, parent=None):
225                 super(TreeModel, self).__init__(parent)
226                 self.db = db
227                 self.root = TreeItem(db, 0, None)
228
229         def columnCount(self, parent):
230                 return self.root.columnCount()
231
232         def rowCount(self, parent):
233                 if parent.isValid():
234                         parent_item = parent.internalPointer()
235                 else:
236                         parent_item = self.root
237                 return parent_item.childCount()
238
239         def headerData(self, section, orientation, role):
240                 if role == Qt.TextAlignmentRole:
241                         if section > 1:
242                                 return Qt.AlignRight
243                 if role != Qt.DisplayRole:
244                         return None
245                 if orientation != Qt.Horizontal:
246                         return None
247                 return self.root.columnHeader(section)
248
249         def parent(self, child):
250                 child_item = child.internalPointer()
251                 if child_item is self.root:
252                         return QModelIndex()
253                 parent_item = child_item.getParentItem()
254                 return self.createIndex(parent_item.getRow(), 0, parent_item)
255
256         def index(self, row, column, parent):
257                 if parent.isValid():
258                         parent_item = parent.internalPointer()
259                 else:
260                         parent_item = self.root
261                 child_item = parent_item.getChildItem(row)
262                 return self.createIndex(row, column, child_item)
263
264         def data(self, index, role):
265                 if role == Qt.TextAlignmentRole:
266                         if index.column() > 1:
267                                 return Qt.AlignRight
268                 if role != Qt.DisplayRole:
269                         return None
270                 index_item = index.internalPointer()
271                 return index_item.getData(index.column())
272
273 class MainWindow(QMainWindow):
274
275         def __init__(self, db, dbname, parent=None):
276                 super(MainWindow, self).__init__(parent)
277
278                 self.setObjectName("MainWindow")
279                 self.setWindowTitle("Call Graph: " + dbname)
280                 self.move(100, 100)
281                 self.resize(800, 600)
282                 style = self.style()
283                 icon = style.standardIcon(QStyle.SP_MessageBoxInformation)
284                 self.setWindowIcon(icon);
285
286                 self.model = TreeModel(db)
287
288                 self.view = QTreeView()
289                 self.view.setModel(self.model)
290
291                 self.setCentralWidget(self.view)
292
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")
297
298         dbname = sys.argv[1]
299
300         is_sqlite3 = False
301         try:
302                 f = open(dbname)
303                 if f.read(15) == "SQLite format 3":
304                         is_sqlite3 = True
305                 f.close()
306         except:
307                 pass
308
309         if is_sqlite3:
310                 db = QSqlDatabase.addDatabase('QSQLITE')
311         else:
312                 db = QSqlDatabase.addDatabase('QPSQL')
313                 opts = dbname.split()
314                 for opt in opts:
315                         if '=' in opt:
316                                 opt = opt.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':
326                                         dbname = opt[1]
327                         else:
328                                 dbname = opt
329
330         db.setDatabaseName(dbname)
331         if not db.open():
332                 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
333
334         app = QApplication(sys.argv)
335         window = MainWindow(db, dbname)
336         window.show()
337         err = app.exec_()
338         db.close()
339         sys.exit(err)