1031c2a00SAdrian Hunter#!/usr/bin/python2
2031c2a00SAdrian Hunter# SPDX-License-Identifier: GPL-2.0
3031c2a00SAdrian Hunter# exported-sql-viewer.py: view data from sql database
4031c2a00SAdrian Hunter# Copyright (c) 2014-2018, Intel Corporation.
5031c2a00SAdrian Hunter
6031c2a00SAdrian Hunter# To use this script you will need to have exported data using either the
7031c2a00SAdrian Hunter# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
8031c2a00SAdrian Hunter# scripts for details.
9031c2a00SAdrian Hunter#
10031c2a00SAdrian Hunter# Following on from the example in the export scripts, a
11031c2a00SAdrian Hunter# call-graph can be displayed for the pt_example database like this:
12031c2a00SAdrian Hunter#
13031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14031c2a00SAdrian Hunter#
15031c2a00SAdrian Hunter# Note that for PostgreSQL, this script supports connecting to remote databases
16031c2a00SAdrian Hunter# by setting hostname, port, username, password, and dbname e.g.
17031c2a00SAdrian Hunter#
18031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19031c2a00SAdrian Hunter#
20031c2a00SAdrian Hunter# The result is a GUI window with a tree representing a context-sensitive
21031c2a00SAdrian Hunter# call-graph.  Expanding a couple of levels of the tree and adjusting column
22031c2a00SAdrian Hunter# widths to suit will display something like:
23031c2a00SAdrian Hunter#
24031c2a00SAdrian Hunter#                                         Call Graph: pt_example
25031c2a00SAdrian Hunter# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
26031c2a00SAdrian Hunter# v- ls
27031c2a00SAdrian Hunter#     v- 2638:2638
28031c2a00SAdrian Hunter#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
29031c2a00SAdrian Hunter#           |- unknown               unknown       1        13198     0.1              1              0.0
30031c2a00SAdrian Hunter#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
31031c2a00SAdrian Hunter#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
32031c2a00SAdrian Hunter#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
33031c2a00SAdrian Hunter#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
34031c2a00SAdrian Hunter#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
35031c2a00SAdrian Hunter#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
36031c2a00SAdrian Hunter#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
37031c2a00SAdrian Hunter#              v- main               ls            1      8182043    99.6         180254             99.9
38031c2a00SAdrian Hunter#
39031c2a00SAdrian Hunter# Points to note:
40031c2a00SAdrian Hunter#	The top level is a command name (comm)
41031c2a00SAdrian Hunter#	The next level is a thread (pid:tid)
42031c2a00SAdrian Hunter#	Subsequent levels are functions
43031c2a00SAdrian Hunter#	'Count' is the number of calls
44031c2a00SAdrian Hunter#	'Time' is the elapsed time until the function returns
45031c2a00SAdrian Hunter#	Percentages are relative to the level above
46031c2a00SAdrian Hunter#	'Branch Count' is the total number of branches for that function and all
47031c2a00SAdrian Hunter#       functions that it calls
48031c2a00SAdrian Hunter
49031c2a00SAdrian Hunterimport sys
50*1beb5c7bSAdrian Hunterimport weakref
51*1beb5c7bSAdrian Hunterimport threading
52031c2a00SAdrian Hunterfrom PySide.QtCore import *
53031c2a00SAdrian Hunterfrom PySide.QtGui import *
54031c2a00SAdrian Hunterfrom PySide.QtSql import *
55031c2a00SAdrian Hunterfrom decimal import *
56031c2a00SAdrian Hunter
57031c2a00SAdrian Hunter# Data formatting helpers
58031c2a00SAdrian Hunter
59031c2a00SAdrian Hunterdef dsoname(name):
60031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
61031c2a00SAdrian Hunter		return "[kernel]"
62031c2a00SAdrian Hunter	return name
63031c2a00SAdrian Hunter
64031c2a00SAdrian Hunter# Percent to one decimal place
65031c2a00SAdrian Hunter
66031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
67031c2a00SAdrian Hunter	if not d:
68031c2a00SAdrian Hunter		return "0.0"
69031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
70031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
71031c2a00SAdrian Hunter
72031c2a00SAdrian Hunter# Helper for queries that must not fail
73031c2a00SAdrian Hunter
74031c2a00SAdrian Hunterdef QueryExec(query, stmt):
75031c2a00SAdrian Hunter	ret = query.exec_(stmt)
76031c2a00SAdrian Hunter	if not ret:
77031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
78031c2a00SAdrian Hunter
79031c2a00SAdrian Hunter# Tree data model
80031c2a00SAdrian Hunter
81031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
82031c2a00SAdrian Hunter
83031c2a00SAdrian Hunter	def __init__(self, root, parent=None):
84031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
85031c2a00SAdrian Hunter		self.root = root
86031c2a00SAdrian Hunter		self.last_row_read = 0
87031c2a00SAdrian Hunter
88031c2a00SAdrian Hunter	def Item(self, parent):
89031c2a00SAdrian Hunter		if parent.isValid():
90031c2a00SAdrian Hunter			return parent.internalPointer()
91031c2a00SAdrian Hunter		else:
92031c2a00SAdrian Hunter			return self.root
93031c2a00SAdrian Hunter
94031c2a00SAdrian Hunter	def rowCount(self, parent):
95031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
96031c2a00SAdrian Hunter		if result < 0:
97031c2a00SAdrian Hunter			result = 0
98031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
99031c2a00SAdrian Hunter		return result
100031c2a00SAdrian Hunter
101031c2a00SAdrian Hunter	def hasChildren(self, parent):
102031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
103031c2a00SAdrian Hunter
104031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
105031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
106031c2a00SAdrian Hunter			return self.columnAlignment(section)
107031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
108031c2a00SAdrian Hunter			return None
109031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
110031c2a00SAdrian Hunter			return None
111031c2a00SAdrian Hunter		return self.columnHeader(section)
112031c2a00SAdrian Hunter
113031c2a00SAdrian Hunter	def parent(self, child):
114031c2a00SAdrian Hunter		child_item = child.internalPointer()
115031c2a00SAdrian Hunter		if child_item is self.root:
116031c2a00SAdrian Hunter			return QModelIndex()
117031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
118031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
119031c2a00SAdrian Hunter
120031c2a00SAdrian Hunter	def index(self, row, column, parent):
121031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
122031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
123031c2a00SAdrian Hunter
124031c2a00SAdrian Hunter	def DisplayData(self, item, index):
125031c2a00SAdrian Hunter		return item.getData(index.column())
126031c2a00SAdrian Hunter
127031c2a00SAdrian Hunter	def columnAlignment(self, column):
128031c2a00SAdrian Hunter		return Qt.AlignLeft
129031c2a00SAdrian Hunter
130031c2a00SAdrian Hunter	def columnFont(self, column):
131031c2a00SAdrian Hunter		return None
132031c2a00SAdrian Hunter
133031c2a00SAdrian Hunter	def data(self, index, role):
134031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
135031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
136031c2a00SAdrian Hunter		if role == Qt.FontRole:
137031c2a00SAdrian Hunter			return self.columnFont(index.column())
138031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
139031c2a00SAdrian Hunter			return None
140031c2a00SAdrian Hunter		item = index.internalPointer()
141031c2a00SAdrian Hunter		return self.DisplayData(item, index)
142031c2a00SAdrian Hunter
143*1beb5c7bSAdrian Hunter# Model cache
144*1beb5c7bSAdrian Hunter
145*1beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
146*1beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
147*1beb5c7bSAdrian Hunter
148*1beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
149*1beb5c7bSAdrian Hunter	model_cache_lock.acquire()
150*1beb5c7bSAdrian Hunter	try:
151*1beb5c7bSAdrian Hunter		model = model_cache[model_name]
152*1beb5c7bSAdrian Hunter	except:
153*1beb5c7bSAdrian Hunter		model = None
154*1beb5c7bSAdrian Hunter	if model is None:
155*1beb5c7bSAdrian Hunter		model = create_fn()
156*1beb5c7bSAdrian Hunter		model_cache[model_name] = model
157*1beb5c7bSAdrian Hunter	model_cache_lock.release()
158*1beb5c7bSAdrian Hunter	return model
159*1beb5c7bSAdrian Hunter
160031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
161031c2a00SAdrian Hunter
162031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
163031c2a00SAdrian Hunter
164031c2a00SAdrian Hunter	def __init__(self, glb, row, parent_item):
165031c2a00SAdrian Hunter		self.glb = glb
166031c2a00SAdrian Hunter		self.row = row
167031c2a00SAdrian Hunter		self.parent_item = parent_item
168031c2a00SAdrian Hunter		self.query_done = False;
169031c2a00SAdrian Hunter		self.child_count = 0
170031c2a00SAdrian Hunter		self.child_items = []
171031c2a00SAdrian Hunter
172031c2a00SAdrian Hunter	def getChildItem(self, row):
173031c2a00SAdrian Hunter		return self.child_items[row]
174031c2a00SAdrian Hunter
175031c2a00SAdrian Hunter	def getParentItem(self):
176031c2a00SAdrian Hunter		return self.parent_item
177031c2a00SAdrian Hunter
178031c2a00SAdrian Hunter	def getRow(self):
179031c2a00SAdrian Hunter		return self.row
180031c2a00SAdrian Hunter
181031c2a00SAdrian Hunter	def childCount(self):
182031c2a00SAdrian Hunter		if not self.query_done:
183031c2a00SAdrian Hunter			self.Select()
184031c2a00SAdrian Hunter			if not self.child_count:
185031c2a00SAdrian Hunter				return -1
186031c2a00SAdrian Hunter		return self.child_count
187031c2a00SAdrian Hunter
188031c2a00SAdrian Hunter	def hasChildren(self):
189031c2a00SAdrian Hunter		if not self.query_done:
190031c2a00SAdrian Hunter			return True
191031c2a00SAdrian Hunter		return self.child_count > 0
192031c2a00SAdrian Hunter
193031c2a00SAdrian Hunter	def getData(self, column):
194031c2a00SAdrian Hunter		return self.data[column]
195031c2a00SAdrian Hunter
196031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
197031c2a00SAdrian Hunter
198031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
199031c2a00SAdrian Hunter
200031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
201031c2a00SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
202031c2a00SAdrian Hunter		self.comm_id = comm_id
203031c2a00SAdrian Hunter		self.thread_id = thread_id
204031c2a00SAdrian Hunter		self.call_path_id = call_path_id
205031c2a00SAdrian Hunter		self.branch_count = branch_count
206031c2a00SAdrian Hunter		self.time = time
207031c2a00SAdrian Hunter
208031c2a00SAdrian Hunter	def Select(self):
209031c2a00SAdrian Hunter		self.query_done = True;
210031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
211031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
212031c2a00SAdrian Hunter					" FROM calls"
213031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
214031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
215031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
216031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
217031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
218031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
219031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
220031c2a00SAdrian Hunter					" ORDER BY call_path_id")
221031c2a00SAdrian Hunter		while query.next():
222031c2a00SAdrian Hunter			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)
223031c2a00SAdrian Hunter			self.child_items.append(child_item)
224031c2a00SAdrian Hunter			self.child_count += 1
225031c2a00SAdrian Hunter
226031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
227031c2a00SAdrian Hunter
228031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
229031c2a00SAdrian Hunter
230031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
231031c2a00SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
232031c2a00SAdrian Hunter		dso = dsoname(dso)
233031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
234031c2a00SAdrian Hunter		self.dbid = call_path_id
235031c2a00SAdrian Hunter
236031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
237031c2a00SAdrian Hunter
238031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
239031c2a00SAdrian Hunter
240031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
241031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
242031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
243031c2a00SAdrian Hunter		self.dbid = thread_id
244031c2a00SAdrian Hunter
245031c2a00SAdrian Hunter	def Select(self):
246031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
247031c2a00SAdrian Hunter		for child_item in self.child_items:
248031c2a00SAdrian Hunter			self.time += child_item.time
249031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
250031c2a00SAdrian Hunter		for child_item in self.child_items:
251031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
252031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
253031c2a00SAdrian Hunter
254031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
255031c2a00SAdrian Hunter
256031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
257031c2a00SAdrian Hunter
258031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
259031c2a00SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
260031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
261031c2a00SAdrian Hunter		self.dbid = comm_id
262031c2a00SAdrian Hunter
263031c2a00SAdrian Hunter	def Select(self):
264031c2a00SAdrian Hunter		self.query_done = True;
265031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
266031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
267031c2a00SAdrian Hunter					" FROM comm_threads"
268031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
269031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
270031c2a00SAdrian Hunter		while query.next():
271031c2a00SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
272031c2a00SAdrian Hunter			self.child_items.append(child_item)
273031c2a00SAdrian Hunter			self.child_count += 1
274031c2a00SAdrian Hunter
275031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
276031c2a00SAdrian Hunter
277031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
278031c2a00SAdrian Hunter
279031c2a00SAdrian Hunter	def __init__(self, glb):
280031c2a00SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, 0, None)
281031c2a00SAdrian Hunter		self.dbid = 0
282031c2a00SAdrian Hunter		self.query_done = True;
283031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
284031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
285031c2a00SAdrian Hunter		while query.next():
286031c2a00SAdrian Hunter			if not query.value(0):
287031c2a00SAdrian Hunter				continue
288031c2a00SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
289031c2a00SAdrian Hunter			self.child_items.append(child_item)
290031c2a00SAdrian Hunter			self.child_count += 1
291031c2a00SAdrian Hunter
292031c2a00SAdrian Hunter# Context-sensitive call graph data model
293031c2a00SAdrian Hunter
294031c2a00SAdrian Hunterclass CallGraphModel(TreeModel):
295031c2a00SAdrian Hunter
296031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
297031c2a00SAdrian Hunter		super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
298031c2a00SAdrian Hunter		self.glb = glb
299031c2a00SAdrian Hunter
300031c2a00SAdrian Hunter	def columnCount(self, parent=None):
301031c2a00SAdrian Hunter		return 7
302031c2a00SAdrian Hunter
303031c2a00SAdrian Hunter	def columnHeader(self, column):
304031c2a00SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
305031c2a00SAdrian Hunter		return headers[column]
306031c2a00SAdrian Hunter
307031c2a00SAdrian Hunter	def columnAlignment(self, column):
308031c2a00SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
309031c2a00SAdrian Hunter		return alignment[column]
310031c2a00SAdrian Hunter
311*1beb5c7bSAdrian Hunter# Context-sensitive call graph window
312*1beb5c7bSAdrian Hunter
313*1beb5c7bSAdrian Hunterclass CallGraphWindow(QMdiSubWindow):
314*1beb5c7bSAdrian Hunter
315*1beb5c7bSAdrian Hunter	def __init__(self, glb, parent=None):
316*1beb5c7bSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
317*1beb5c7bSAdrian Hunter
318*1beb5c7bSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
319*1beb5c7bSAdrian Hunter
320*1beb5c7bSAdrian Hunter		self.view = QTreeView()
321*1beb5c7bSAdrian Hunter		self.view.setModel(self.model)
322*1beb5c7bSAdrian Hunter
323*1beb5c7bSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
324*1beb5c7bSAdrian Hunter			self.view.setColumnWidth(c, w)
325*1beb5c7bSAdrian Hunter
326*1beb5c7bSAdrian Hunter		self.setWidget(self.view)
327*1beb5c7bSAdrian Hunter
328*1beb5c7bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
329*1beb5c7bSAdrian Hunter
330*1beb5c7bSAdrian Hunter# Action Definition
331*1beb5c7bSAdrian Hunter
332*1beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
333*1beb5c7bSAdrian Hunter	action = QAction(label, parent)
334*1beb5c7bSAdrian Hunter	if shortcut != None:
335*1beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
336*1beb5c7bSAdrian Hunter	action.setStatusTip(tip)
337*1beb5c7bSAdrian Hunter	action.triggered.connect(callback)
338*1beb5c7bSAdrian Hunter	return action
339*1beb5c7bSAdrian Hunter
340*1beb5c7bSAdrian Hunter# Typical application actions
341*1beb5c7bSAdrian Hunter
342*1beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
343*1beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
344*1beb5c7bSAdrian Hunter
345*1beb5c7bSAdrian Hunter# Typical MDI actions
346*1beb5c7bSAdrian Hunter
347*1beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
348*1beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
349*1beb5c7bSAdrian Hunter
350*1beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
351*1beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
352*1beb5c7bSAdrian Hunter
353*1beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
354*1beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
355*1beb5c7bSAdrian Hunter
356*1beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
357*1beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
358*1beb5c7bSAdrian Hunter
359*1beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
360*1beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
361*1beb5c7bSAdrian Hunter
362*1beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
363*1beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
364*1beb5c7bSAdrian Hunter
365*1beb5c7bSAdrian Hunter# Typical MDI window menu
366*1beb5c7bSAdrian Hunter
367*1beb5c7bSAdrian Hunterclass WindowMenu():
368*1beb5c7bSAdrian Hunter
369*1beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
370*1beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
371*1beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
372*1beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
373*1beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
374*1beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
375*1beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
376*1beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
377*1beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
378*1beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
379*1beb5c7bSAdrian Hunter
380*1beb5c7bSAdrian Hunter	def Update(self):
381*1beb5c7bSAdrian Hunter		self.window_menu.clear()
382*1beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
383*1beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
384*1beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
385*1beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
386*1beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
387*1beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
388*1beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
389*1beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
390*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
391*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
392*1beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
393*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
394*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
395*1beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
396*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
397*1beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
398*1beb5c7bSAdrian Hunter		if sub_window_count == 0:
399*1beb5c7bSAdrian Hunter			return
400*1beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
401*1beb5c7bSAdrian Hunter		nr = 1
402*1beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
403*1beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
404*1beb5c7bSAdrian Hunter			if nr < 10:
405*1beb5c7bSAdrian Hunter				label = "&" + label
406*1beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
407*1beb5c7bSAdrian Hunter			action.setCheckable(True)
408*1beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
409*1beb5c7bSAdrian Hunter			action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
410*1beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
411*1beb5c7bSAdrian Hunter			nr += 1
412*1beb5c7bSAdrian Hunter
413*1beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
414*1beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
415*1beb5c7bSAdrian Hunter
416*1beb5c7bSAdrian Hunter# Unique name for sub-windows
417*1beb5c7bSAdrian Hunter
418*1beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
419*1beb5c7bSAdrian Hunter	if nr > 1:
420*1beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
421*1beb5c7bSAdrian Hunter	return name
422*1beb5c7bSAdrian Hunter
423*1beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
424*1beb5c7bSAdrian Hunter	nr = 1
425*1beb5c7bSAdrian Hunter	while True:
426*1beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
427*1beb5c7bSAdrian Hunter		ok = True
428*1beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
429*1beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
430*1beb5c7bSAdrian Hunter				ok = False
431*1beb5c7bSAdrian Hunter				break
432*1beb5c7bSAdrian Hunter		if ok:
433*1beb5c7bSAdrian Hunter			return unique_name
434*1beb5c7bSAdrian Hunter		nr += 1
435*1beb5c7bSAdrian Hunter
436*1beb5c7bSAdrian Hunter# Add a sub-window
437*1beb5c7bSAdrian Hunter
438*1beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
439*1beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
440*1beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
441*1beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
442*1beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
443*1beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
444*1beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
445*1beb5c7bSAdrian Hunter	sub_window.name = unique_name
446*1beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
447*1beb5c7bSAdrian Hunter	sub_window.show()
448*1beb5c7bSAdrian Hunter
449031c2a00SAdrian Hunter# Main window
450031c2a00SAdrian Hunter
451031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
452031c2a00SAdrian Hunter
453031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
454031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
455031c2a00SAdrian Hunter
456031c2a00SAdrian Hunter		self.glb = glb
457031c2a00SAdrian Hunter
458*1beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
459031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
460031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
461031c2a00SAdrian Hunter
462*1beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
463*1beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
464*1beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
465031c2a00SAdrian Hunter
466*1beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
467031c2a00SAdrian Hunter
468*1beb5c7bSAdrian Hunter		menu = self.menuBar()
469031c2a00SAdrian Hunter
470*1beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
471*1beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
472*1beb5c7bSAdrian Hunter
473*1beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
474*1beb5c7bSAdrian Hunter		reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
475*1beb5c7bSAdrian Hunter
476*1beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
477*1beb5c7bSAdrian Hunter
478*1beb5c7bSAdrian Hunter	def NewCallGraph(self):
479*1beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
480031c2a00SAdrian Hunter
481031c2a00SAdrian Hunter# Global data
482031c2a00SAdrian Hunter
483031c2a00SAdrian Hunterclass Glb():
484031c2a00SAdrian Hunter
485031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
486031c2a00SAdrian Hunter		self.dbref = dbref
487031c2a00SAdrian Hunter		self.db = db
488031c2a00SAdrian Hunter		self.dbname = dbname
489031c2a00SAdrian Hunter		self.app = None
490031c2a00SAdrian Hunter		self.mainwindow = None
491031c2a00SAdrian Hunter
492031c2a00SAdrian Hunter# Database reference
493031c2a00SAdrian Hunter
494031c2a00SAdrian Hunterclass DBRef():
495031c2a00SAdrian Hunter
496031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
497031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
498031c2a00SAdrian Hunter		self.dbname = dbname
499031c2a00SAdrian Hunter
500031c2a00SAdrian Hunter	def Open(self, connection_name):
501031c2a00SAdrian Hunter		dbname = self.dbname
502031c2a00SAdrian Hunter		if self.is_sqlite3:
503031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
504031c2a00SAdrian Hunter		else:
505031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
506031c2a00SAdrian Hunter			opts = dbname.split()
507031c2a00SAdrian Hunter			for opt in opts:
508031c2a00SAdrian Hunter				if "=" in opt:
509031c2a00SAdrian Hunter					opt = opt.split("=")
510031c2a00SAdrian Hunter					if opt[0] == "hostname":
511031c2a00SAdrian Hunter						db.setHostName(opt[1])
512031c2a00SAdrian Hunter					elif opt[0] == "port":
513031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
514031c2a00SAdrian Hunter					elif opt[0] == "username":
515031c2a00SAdrian Hunter						db.setUserName(opt[1])
516031c2a00SAdrian Hunter					elif opt[0] == "password":
517031c2a00SAdrian Hunter						db.setPassword(opt[1])
518031c2a00SAdrian Hunter					elif opt[0] == "dbname":
519031c2a00SAdrian Hunter						dbname = opt[1]
520031c2a00SAdrian Hunter				else:
521031c2a00SAdrian Hunter					dbname = opt
522031c2a00SAdrian Hunter
523031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
524031c2a00SAdrian Hunter		if not db.open():
525031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
526031c2a00SAdrian Hunter		return db, dbname
527031c2a00SAdrian Hunter
528031c2a00SAdrian Hunter# Main
529031c2a00SAdrian Hunter
530031c2a00SAdrian Hunterdef Main():
531031c2a00SAdrian Hunter	if (len(sys.argv) < 2):
532031c2a00SAdrian Hunter		print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
533031c2a00SAdrian Hunter		raise Exception("Too few arguments")
534031c2a00SAdrian Hunter
535031c2a00SAdrian Hunter	dbname = sys.argv[1]
536031c2a00SAdrian Hunter
537031c2a00SAdrian Hunter	is_sqlite3 = False
538031c2a00SAdrian Hunter	try:
539031c2a00SAdrian Hunter		f = open(dbname)
540031c2a00SAdrian Hunter		if f.read(15) == "SQLite format 3":
541031c2a00SAdrian Hunter			is_sqlite3 = True
542031c2a00SAdrian Hunter		f.close()
543031c2a00SAdrian Hunter	except:
544031c2a00SAdrian Hunter		pass
545031c2a00SAdrian Hunter
546031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
547031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
548031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
549031c2a00SAdrian Hunter	app = QApplication(sys.argv)
550031c2a00SAdrian Hunter	glb.app = app
551031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
552031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
553031c2a00SAdrian Hunter	mainwindow.show()
554031c2a00SAdrian Hunter	err = app.exec_()
555031c2a00SAdrian Hunter	db.close()
556031c2a00SAdrian Hunter	sys.exit(err)
557031c2a00SAdrian Hunter
558031c2a00SAdrian Hunterif __name__ == "__main__":
559031c2a00SAdrian Hunter	Main()
560