summaryrefslogtreecommitdiff
path: root/tools/perf/scripts/python/call-graph-from-postgresql.py
blob: e78fdc2a5a9d7516d4d3642b16a44bc9aed7491d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#!/usr/bin/python2
# call-graph-from-postgresql.py: create call-graph from postgresql database
# Copyright (c) 2014, Intel Corporation.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms and conditions of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.

# To use this script you will need to have exported data using the
# export-to-postgresql.py script.  Refer to that script for details.
#
# Following on from the example in the export-to-postgresql.py script, a
# call-graph can be displayed for the pt_example database like this:
#
#	python tools/perf/scripts/python/call-graph-from-postgresql.py pt_example
#
# Note this script supports connecting to remote databases by setting hostname,
# port, username, password, and dbname e.g.
#
#	python tools/perf/scripts/python/call-graph-from-postgresql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
#
# The result is a GUI window with a tree representing a context-sensitive
# call-graph.  Expanding a couple of levels of the tree and adjusting column
# widths to suit will display something like:
#
#                                         Call Graph: pt_example
# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
# v- ls
#     v- 2638:2638
#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
#           |- unknown               unknown       1        13198     0.1              1              0.0
#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
#              v- main               ls            1      8182043    99.6         180254             99.9
#
# Points to note:
#	The top level is a command name (comm)
#	The next level is a thread (pid:tid)
#	Subsequent levels are functions
#	'Count' is the number of calls
#	'Time' is the elapsed time until the function returns
#	Percentages are relative to the level above
#	'Branch Count' is the total number of branches for that function and all
#       functions that it calls

import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtSql import *
from decimal import *

class TreeItem():

	def __init__(self, db, row, parent_item):
		self.db = db
		self.row = row
		self.parent_item = parent_item
		self.query_done = False;
		self.child_count = 0
		self.child_items = []
		self.data = ["", "", "", "", "", "", ""]
		self.comm_id = 0
		self.thread_id = 0
		self.call_path_id = 1
		self.branch_count = 0
		self.time = 0
		if not parent_item:
			self.setUpRoot()

	def setUpRoot(self):
		self.query_done = True
		query = QSqlQuery(self.db)
		ret = query.exec_('SELECT id, comm FROM comms')
		if not ret:
			raise Exception("Query failed: " + query.lastError().text())
		while query.next():
			if not query.value(0):
				continue
			child_item = TreeItem(self.db, self.child_count, self)
			self.child_items.append(child_item)
			self.child_count += 1
			child_item.setUpLevel1(query.value(0), query.value(1))

	def setUpLevel1(self, comm_id, comm):
		self.query_done = True;
		self.comm_id = comm_id
		self.data[0] = comm
		self.child_items = []
		self.child_count = 0
		query = QSqlQuery(self.db)
		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))
		if not ret:
			raise Exception("Query failed: " + query.lastError().text())
		while query.next():
			child_item = TreeItem(self.db, self.child_count, self)
			self.child_items.append(child_item)
			self.child_count += 1
			child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2))

	def setUpLevel2(self, comm_id, thread_id, pid, tid):
		self.comm_id = comm_id
		self.thread_id = thread_id
		self.data[0] = str(pid) + ":" + str(tid)

	def getChildItem(self, row):
		return self.child_items[row]

	def getParentItem(self):
		return self.parent_item

	def getRow(self):
		return self.row

	def timePercent(self, b):
		if not self.time:
			return "0.0"
		x = (b * Decimal(100)) / self.time
		return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))

	def branchPercent(self, b):
		if not self.branch_count:
			return "0.0"
		x = (b * Decimal(100)) / self.branch_count
		return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))

	def addChild(self, call_path_id, name, dso, count, time, branch_count):
		child_item = TreeItem(self.db, self.child_count, self)
		child_item.comm_id = self.comm_id
		child_item.thread_id = self.thread_id
		child_item.call_path_id = call_path_id
		child_item.branch_count = branch_count
		child_item.time = time
		child_item.data[0] = name
		if dso == "[kernel.kallsyms]":
			dso = "[kernel]"
		child_item.data[1] = dso
		child_item.data[2] = str(count)
		child_item.data[3] = str(time)
		child_item.data[4] = self.timePercent(time)
		child_item.data[5] = str(branch_count)
		child_item.data[6] = self.branchPercent(branch_count)
		self.child_items.append(child_item)
		self.child_count += 1

	def selectCalls(self):
		self.query_done = True;
		query = QSqlQuery(self.db)
		ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
				  '( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
				  '( 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 ) ) ), '
				  '( SELECT ip FROM call_paths where id = call_path_id ) '
				  '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) +
				  'ORDER BY call_path_id')
		if not ret:
			raise Exception("Query failed: " + query.lastError().text())
		last_call_path_id = 0
		name = ""
		dso = ""
		count = 0
		branch_count = 0
		total_branch_count = 0
		time = 0
		total_time = 0
		while query.next():
			if query.value(1) == last_call_path_id:
				count += 1
				branch_count += query.value(2)
				time += query.value(4) - query.value(3)
			else:
				if count:
					self.addChild(last_call_path_id, name, dso, count, time, branch_count)
				last_call_path_id = query.value(1)
				name = query.value(5)
				dso = query.value(6)
				count = 1
				total_branch_count += branch_count
				total_time += time
				branch_count = query.value(2)
				time = query.value(4) - query.value(3)
		if count:
			self.addChild(last_call_path_id, name, dso, count, time, branch_count)
		total_branch_count += branch_count
		total_time += time
		# Top level does not have time or branch count, so fix that here
		if total_branch_count > self.branch_count:
			self.branch_count = total_branch_count
			if self.branch_count:
				for child_item in self.child_items:
					child_item.data[6] = self.branchPercent(child_item.branch_count)
		if total_time > self.time:
			self.time = total_time
			if self.time:
				for child_item in self.child_items:
					child_item.data[4] = self.timePercent(child_item.time)

	def childCount(self):
		if not self.query_done:
			self.selectCalls()
		return self.child_count

	def columnCount(self):
		return 7

	def columnHeader(self, column):
		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
		return headers[column]

	def getData(self, column):
		return self.data[column]

class TreeModel(QAbstractItemModel):

	def __init__(self, db, parent=None):
		super(TreeModel, self).__init__(parent)
		self.db = db
		self.root = TreeItem(db, 0, None)

	def columnCount(self, parent):
		return self.root.columnCount()

	def rowCount(self, parent):
		if parent.isValid():
			parent_item = parent.internalPointer()
		else:
			parent_item = self.root
		return parent_item.childCount()

	def headerData(self, section, orientation, role):
		if role == Qt.TextAlignmentRole:
			if section > 1:
				return Qt.AlignRight
		if role != Qt.DisplayRole:
			return None
		if orientation != Qt.Horizontal:
			return None
		return self.root.columnHeader(section)

	def parent(self, child):
		child_item = child.internalPointer()
		if child_item is self.root:
			return QModelIndex()
		parent_item = child_item.getParentItem()
		return self.createIndex(parent_item.getRow(), 0, parent_item)

	def index(self, row, column, parent):
		if parent.isValid():
			parent_item = parent.internalPointer()
		else:
			parent_item = self.root
		child_item = parent_item.getChildItem(row)
		return self.createIndex(row, column, child_item)

	def data(self, index, role):
		if role == Qt.TextAlignmentRole:
			if index.column() > 1:
				return Qt.AlignRight
		if role != Qt.DisplayRole:
			return None
		index_item = index.internalPointer()
		return index_item.getData(index.column())

class MainWindow(QMainWindow):

	def __init__(self, db, dbname, parent=None):
		super(MainWindow, self).__init__(parent)

		self.setObjectName("MainWindow")
		self.setWindowTitle("Call Graph: " + dbname)
		self.move(100, 100)
		self.resize(800, 600)
		style = self.style()
		icon = style.standardIcon(QStyle.SP_MessageBoxInformation)
		self.setWindowIcon(icon);

		self.model = TreeModel(db)

		self.view = QTreeView()
		self.view.setModel(self.model)

		self.setCentralWidget(self.view)

if __name__ == '__main__':
	if (len(sys.argv) < 2):
		print >> sys.stderr, "Usage is: call-graph-from-postgresql.py <database name>"
		raise Exception("Too few arguments")

	dbname = sys.argv[1]

	db = QSqlDatabase.addDatabase('QPSQL')

	opts = dbname.split()
	for opt in opts:
		if '=' in opt:
			opt = opt.split('=')
			if opt[0] == 'hostname':
				db.setHostName(opt[1])
			elif opt[0] == 'port':
				db.setPort(int(opt[1]))
			elif opt[0] == 'username':
				db.setUserName(opt[1])
			elif opt[0] == 'password':
				db.setPassword(opt[1])
			elif opt[0] == 'dbname':
				dbname = opt[1]
		else:
			dbname = opt

	db.setDatabaseName(dbname)
	if not db.open():
		raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())

	app = QApplication(sys.argv)
	window = MainWindow(db, dbname)
	window.show()
	err = app.exec_()
	db.close()
	sys.exit(err)