perf scripts python: call-graph-from-sql.py: Rename to exported-sql-viewer.py
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 1 Oct 2018 06:28:46 +0000 (09:28 +0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Tue, 23 Oct 2018 17:26:44 +0000 (14:26 -0300)
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>
tools/perf/Documentation/intel-pt.txt
tools/perf/scripts/python/call-graph-from-sql.py [deleted file]
tools/perf/scripts/python/export-to-postgresql.py
tools/perf/scripts/python/export-to-sqlite.py
tools/perf/scripts/python/exported-sql-viewer.py [new file with mode: 0755]

index 76971d2e416450c24fbb24bb51db584da7661180..115eaacc455fdb020ca34f3c16fb1bcad092b9d1 100644 (file)
@@ -106,7 +106,7 @@ in transaction, respectively.
 While it is possible to create scripts to analyze the data, an alternative
 approach is available to export the data to a sqlite or postgresql database.
 Refer to script export-to-sqlite.py or export-to-postgresql.py for more details,
-and to script call-graph-from-sql.py for an example of using the database.
+and to script exported-sql-viewer.py for an example of using the database.
 
 There is also script intel-pt-events.py which provides an example of how to
 unpack the raw data for power events and PTWRITE.
diff --git a/tools/perf/scripts/python/call-graph-from-sql.py b/tools/perf/scripts/python/call-graph-from-sql.py
deleted file mode 100644 (file)
index ee10851..0000000
+++ /dev/null
@@ -1,395 +0,0 @@
-#!/usr/bin/python2
-# SPDX-License-Identifier: GPL-2.0
-# exported-sql-viewer.py: view data from sql database
-# Copyright (c) 2014-2018, Intel Corporation.
-
-# To use this script you will need to have exported data using either the
-# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
-# scripts for details.
-#
-# Following on from the example in the export scripts, a
-# call-graph can be displayed for the pt_example database like this:
-#
-#      python tools/perf/scripts/python/call-graph-from-sql.py pt_example
-#
-# Note that for PostgreSQL, 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-sql.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 *
-
-# Data formatting helpers
-
-def dsoname(name):
-       if name == "[kernel.kallsyms]":
-               return "[kernel]"
-       return name
-
-# Percent to one decimal place
-
-def PercentToOneDP(n, d):
-       if not d:
-               return "0.0"
-       x = (n * Decimal(100)) / d
-       return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
-
-# Helper for queries that must not fail
-
-def QueryExec(query, stmt):
-       ret = query.exec_(stmt)
-       if not ret:
-               raise Exception("Query failed: " + query.lastError().text())
-
-# Tree data model
-
-class TreeModel(QAbstractItemModel):
-
-       def __init__(self, root, parent=None):
-               super(TreeModel, self).__init__(parent)
-               self.root = root
-               self.last_row_read = 0
-
-       def Item(self, parent):
-               if parent.isValid():
-                       return parent.internalPointer()
-               else:
-                       return self.root
-
-       def rowCount(self, parent):
-               result = self.Item(parent).childCount()
-               if result < 0:
-                       result = 0
-                       self.dataChanged.emit(parent, parent)
-               return result
-
-       def hasChildren(self, parent):
-               return self.Item(parent).hasChildren()
-
-       def headerData(self, section, orientation, role):
-               if role == Qt.TextAlignmentRole:
-                       return self.columnAlignment(section)
-               if role != Qt.DisplayRole:
-                       return None
-               if orientation != Qt.Horizontal:
-                       return None
-               return self.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):
-               child_item = self.Item(parent).getChildItem(row)
-               return self.createIndex(row, column, child_item)
-
-       def DisplayData(self, item, index):
-               return item.getData(index.column())
-
-       def columnAlignment(self, column):
-               return Qt.AlignLeft
-
-       def columnFont(self, column):
-               return None
-
-       def data(self, index, role):
-               if role == Qt.TextAlignmentRole:
-                       return self.columnAlignment(index.column())
-               if role == Qt.FontRole:
-                       return self.columnFont(index.column())
-               if role != Qt.DisplayRole:
-                       return None
-               item = index.internalPointer()
-               return self.DisplayData(item, index)
-
-# Context-sensitive call graph data model item base
-
-class CallGraphLevelItemBase(object):
-
-       def __init__(self, glb, row, parent_item):
-               self.glb = glb
-               self.row = row
-               self.parent_item = parent_item
-               self.query_done = False;
-               self.child_count = 0
-               self.child_items = []
-
-       def getChildItem(self, row):
-               return self.child_items[row]
-
-       def getParentItem(self):
-               return self.parent_item
-
-       def getRow(self):
-               return self.row
-
-       def childCount(self):
-               if not self.query_done:
-                       self.Select()
-                       if not self.child_count:
-                               return -1
-               return self.child_count
-
-       def hasChildren(self):
-               if not self.query_done:
-                       return True
-               return self.child_count > 0
-
-       def getData(self, column):
-               return self.data[column]
-
-# Context-sensitive call graph data model level 2+ item base
-
-class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
-
-       def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
-               super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
-               self.comm_id = comm_id
-               self.thread_id = thread_id
-               self.call_path_id = call_path_id
-               self.branch_count = branch_count
-               self.time = time
-
-       def Select(self):
-               self.query_done = True;
-               query = QSqlQuery(self.glb.db)
-               QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
-                                       " FROM calls"
-                                       " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
-                                       " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
-                                       " INNER JOIN dsos ON symbols.dso_id = dsos.id"
-                                       " WHERE parent_call_path_id = " + str(self.call_path_id) +
-                                       " AND comm_id = " + str(self.comm_id) +
-                                       " AND thread_id = " + str(self.thread_id) +
-                                       " GROUP BY call_path_id, name, short_name"
-                                       " ORDER BY call_path_id")
-               while query.next():
-                       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)
-                       self.child_items.append(child_item)
-                       self.child_count += 1
-
-# Context-sensitive call graph data model level three item
-
-class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
-
-       def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
-               super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
-               dso = dsoname(dso)
-               self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
-               self.dbid = call_path_id
-
-# Context-sensitive call graph data model level two item
-
-class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
-
-       def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
-               super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
-               self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
-               self.dbid = thread_id
-
-       def Select(self):
-               super(CallGraphLevelTwoItem, self).Select()
-               for child_item in self.child_items:
-                       self.time += child_item.time
-                       self.branch_count += child_item.branch_count
-               for child_item in self.child_items:
-                       child_item.data[4] = PercentToOneDP(child_item.time, self.time)
-                       child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
-
-# Context-sensitive call graph data model level one item
-
-class CallGraphLevelOneItem(CallGraphLevelItemBase):
-
-       def __init__(self, glb, row, comm_id, comm, parent_item):
-               super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
-               self.data = [comm, "", "", "", "", "", ""]
-               self.dbid = comm_id
-
-       def Select(self):
-               self.query_done = True;
-               query = QSqlQuery(self.glb.db)
-               QueryExec(query, "SELECT thread_id, pid, tid"
-                                       " FROM comm_threads"
-                                       " INNER JOIN threads ON thread_id = threads.id"
-                                       " WHERE comm_id = " + str(self.dbid))
-               while query.next():
-                       child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
-                       self.child_items.append(child_item)
-                       self.child_count += 1
-
-# Context-sensitive call graph data model root item
-
-class CallGraphRootItem(CallGraphLevelItemBase):
-
-       def __init__(self, glb):
-               super(CallGraphRootItem, self).__init__(glb, 0, None)
-               self.dbid = 0
-               self.query_done = True;
-               query = QSqlQuery(glb.db)
-               QueryExec(query, "SELECT id, comm FROM comms")
-               while query.next():
-                       if not query.value(0):
-                               continue
-                       child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
-                       self.child_items.append(child_item)
-                       self.child_count += 1
-
-# Context-sensitive call graph data model
-
-class CallGraphModel(TreeModel):
-
-       def __init__(self, glb, parent=None):
-               super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
-               self.glb = glb
-
-       def columnCount(self, parent=None):
-               return 7
-
-       def columnHeader(self, column):
-               headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
-               return headers[column]
-
-       def columnAlignment(self, column):
-               alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
-               return alignment[column]
-
-# Main window
-
-class MainWindow(QMainWindow):
-
-       def __init__(self, glb, parent=None):
-               super(MainWindow, self).__init__(parent)
-
-               self.glb = glb
-
-               self.setWindowTitle("Call Graph: " + glb.dbname)
-               self.move(100, 100)
-               self.resize(800, 600)
-               self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
-               self.setMinimumSize(200, 100)
-
-               self.model = CallGraphModel(glb)
-
-               self.view = QTreeView()
-               self.view.setModel(self.model)
-
-               for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
-                       self.view.setColumnWidth(c, w)
-
-               self.setCentralWidget(self.view)
-
-# Global data
-
-class Glb():
-
-       def __init__(self, dbref, db, dbname):
-               self.dbref = dbref
-               self.db = db
-               self.dbname = dbname
-               self.app = None
-               self.mainwindow = None
-
-# Database reference
-
-class DBRef():
-
-       def __init__(self, is_sqlite3, dbname):
-               self.is_sqlite3 = is_sqlite3
-               self.dbname = dbname
-
-       def Open(self, connection_name):
-               dbname = self.dbname
-               if self.is_sqlite3:
-                       db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
-               else:
-                       db = QSqlDatabase.addDatabase("QPSQL", connection_name)
-                       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())
-               return db, dbname
-
-# Main
-
-def Main():
-       if (len(sys.argv) < 2):
-               print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
-               raise Exception("Too few arguments")
-
-       dbname = sys.argv[1]
-
-       is_sqlite3 = False
-       try:
-               f = open(dbname)
-               if f.read(15) == "SQLite format 3":
-                       is_sqlite3 = True
-               f.close()
-       except:
-               pass
-
-       dbref = DBRef(is_sqlite3, dbname)
-       db, dbname = dbref.Open("main")
-       glb = Glb(dbref, db, dbname)
-       app = QApplication(sys.argv)
-       glb.app = app
-       mainwindow = MainWindow(glb)
-       glb.mainwindow = mainwindow
-       mainwindow.show()
-       err = app.exec_()
-       db.close()
-       sys.exit(err)
-
-if __name__ == "__main__":
-       Main()
index e46f51b1751310a5263283fa9a9b5f9227ed1e52..0564dd7377f22f098d98a254bc028949628dd824 100644 (file)
@@ -59,7 +59,7 @@ import datetime
 #      pt_example=# \q
 #
 # An example of using the database is provided by the script
-# call-graph-from-sql.py.  Refer to that script for details.
+# exported-sql-viewer.py.  Refer to that script for details.
 #
 # Tables:
 #
index e4bb82c8aba9e835ea4712e5c00d43443e1d9a9a..245caf2643ed1c4548549be6a48426e1830ab5e1 100644 (file)
@@ -40,7 +40,7 @@ import datetime
 #      sqlite> .quit
 #
 # An example of using the database is provided by the script
-# call-graph-from-sql.py.  Refer to that script for details.
+# exported-sql-viewer.py.  Refer to that script for details.
 #
 # The database structure is practically the same as created by the script
 # export-to-postgresql.py. Refer to that script for details.  A notable
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
new file mode 100755 (executable)
index 0000000..03e7a1d
--- /dev/null
@@ -0,0 +1,395 @@
+#!/usr/bin/python2
+# SPDX-License-Identifier: GPL-2.0
+# exported-sql-viewer.py: view data from sql database
+# Copyright (c) 2014-2018, Intel Corporation.
+
+# To use this script you will need to have exported data using either the
+# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
+# scripts for details.
+#
+# Following on from the example in the export scripts, a
+# call-graph can be displayed for the pt_example database like this:
+#
+#      python tools/perf/scripts/python/exported-sql-viewer.py pt_example
+#
+# Note that for PostgreSQL, this script supports connecting to remote databases
+# by setting hostname, port, username, password, and dbname e.g.
+#
+#      python tools/perf/scripts/python/exported-sql-viewer.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 *
+
+# Data formatting helpers
+
+def dsoname(name):
+       if name == "[kernel.kallsyms]":
+               return "[kernel]"
+       return name
+
+# Percent to one decimal place
+
+def PercentToOneDP(n, d):
+       if not d:
+               return "0.0"
+       x = (n * Decimal(100)) / d
+       return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
+
+# Helper for queries that must not fail
+
+def QueryExec(query, stmt):
+       ret = query.exec_(stmt)
+       if not ret:
+               raise Exception("Query failed: " + query.lastError().text())
+
+# Tree data model
+
+class TreeModel(QAbstractItemModel):
+
+       def __init__(self, root, parent=None):
+               super(TreeModel, self).__init__(parent)
+               self.root = root
+               self.last_row_read = 0
+
+       def Item(self, parent):
+               if parent.isValid():
+                       return parent.internalPointer()
+               else:
+                       return self.root
+
+       def rowCount(self, parent):
+               result = self.Item(parent).childCount()
+               if result < 0:
+                       result = 0
+                       self.dataChanged.emit(parent, parent)
+               return result
+
+       def hasChildren(self, parent):
+               return self.Item(parent).hasChildren()
+
+       def headerData(self, section, orientation, role):
+               if role == Qt.TextAlignmentRole:
+                       return self.columnAlignment(section)
+               if role != Qt.DisplayRole:
+                       return None
+               if orientation != Qt.Horizontal:
+                       return None
+               return self.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):
+               child_item = self.Item(parent).getChildItem(row)
+               return self.createIndex(row, column, child_item)
+
+       def DisplayData(self, item, index):
+               return item.getData(index.column())
+
+       def columnAlignment(self, column):
+               return Qt.AlignLeft
+
+       def columnFont(self, column):
+               return None
+
+       def data(self, index, role):
+               if role == Qt.TextAlignmentRole:
+                       return self.columnAlignment(index.column())
+               if role == Qt.FontRole:
+                       return self.columnFont(index.column())
+               if role != Qt.DisplayRole:
+                       return None
+               item = index.internalPointer()
+               return self.DisplayData(item, index)
+
+# Context-sensitive call graph data model item base
+
+class CallGraphLevelItemBase(object):
+
+       def __init__(self, glb, row, parent_item):
+               self.glb = glb
+               self.row = row
+               self.parent_item = parent_item
+               self.query_done = False;
+               self.child_count = 0
+               self.child_items = []
+
+       def getChildItem(self, row):
+               return self.child_items[row]
+
+       def getParentItem(self):
+               return self.parent_item
+
+       def getRow(self):
+               return self.row
+
+       def childCount(self):
+               if not self.query_done:
+                       self.Select()
+                       if not self.child_count:
+                               return -1
+               return self.child_count
+
+       def hasChildren(self):
+               if not self.query_done:
+                       return True
+               return self.child_count > 0
+
+       def getData(self, column):
+               return self.data[column]
+
+# Context-sensitive call graph data model level 2+ item base
+
+class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
+
+       def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
+               super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
+               self.comm_id = comm_id
+               self.thread_id = thread_id
+               self.call_path_id = call_path_id
+               self.branch_count = branch_count
+               self.time = time
+
+       def Select(self):
+               self.query_done = True;
+               query = QSqlQuery(self.glb.db)
+               QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
+                                       " FROM calls"
+                                       " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
+                                       " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
+                                       " INNER JOIN dsos ON symbols.dso_id = dsos.id"
+                                       " WHERE parent_call_path_id = " + str(self.call_path_id) +
+                                       " AND comm_id = " + str(self.comm_id) +
+                                       " AND thread_id = " + str(self.thread_id) +
+                                       " GROUP BY call_path_id, name, short_name"
+                                       " ORDER BY call_path_id")
+               while query.next():
+                       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)
+                       self.child_items.append(child_item)
+                       self.child_count += 1
+
+# Context-sensitive call graph data model level three item
+
+class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
+
+       def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
+               super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
+               dso = dsoname(dso)
+               self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
+               self.dbid = call_path_id
+
+# Context-sensitive call graph data model level two item
+
+class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
+
+       def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
+               super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
+               self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
+               self.dbid = thread_id
+
+       def Select(self):
+               super(CallGraphLevelTwoItem, self).Select()
+               for child_item in self.child_items:
+                       self.time += child_item.time
+                       self.branch_count += child_item.branch_count
+               for child_item in self.child_items:
+                       child_item.data[4] = PercentToOneDP(child_item.time, self.time)
+                       child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
+
+# Context-sensitive call graph data model level one item
+
+class CallGraphLevelOneItem(CallGraphLevelItemBase):
+
+       def __init__(self, glb, row, comm_id, comm, parent_item):
+               super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
+               self.data = [comm, "", "", "", "", "", ""]
+               self.dbid = comm_id
+
+       def Select(self):
+               self.query_done = True;
+               query = QSqlQuery(self.glb.db)
+               QueryExec(query, "SELECT thread_id, pid, tid"
+                                       " FROM comm_threads"
+                                       " INNER JOIN threads ON thread_id = threads.id"
+                                       " WHERE comm_id = " + str(self.dbid))
+               while query.next():
+                       child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
+                       self.child_items.append(child_item)
+                       self.child_count += 1
+
+# Context-sensitive call graph data model root item
+
+class CallGraphRootItem(CallGraphLevelItemBase):
+
+       def __init__(self, glb):
+               super(CallGraphRootItem, self).__init__(glb, 0, None)
+               self.dbid = 0
+               self.query_done = True;
+               query = QSqlQuery(glb.db)
+               QueryExec(query, "SELECT id, comm FROM comms")
+               while query.next():
+                       if not query.value(0):
+                               continue
+                       child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
+                       self.child_items.append(child_item)
+                       self.child_count += 1
+
+# Context-sensitive call graph data model
+
+class CallGraphModel(TreeModel):
+
+       def __init__(self, glb, parent=None):
+               super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
+               self.glb = glb
+
+       def columnCount(self, parent=None):
+               return 7
+
+       def columnHeader(self, column):
+               headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
+               return headers[column]
+
+       def columnAlignment(self, column):
+               alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
+               return alignment[column]
+
+# Main window
+
+class MainWindow(QMainWindow):
+
+       def __init__(self, glb, parent=None):
+               super(MainWindow, self).__init__(parent)
+
+               self.glb = glb
+
+               self.setWindowTitle("Call Graph: " + glb.dbname)
+               self.move(100, 100)
+               self.resize(800, 600)
+               self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
+               self.setMinimumSize(200, 100)
+
+               self.model = CallGraphModel(glb)
+
+               self.view = QTreeView()
+               self.view.setModel(self.model)
+
+               for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
+                       self.view.setColumnWidth(c, w)
+
+               self.setCentralWidget(self.view)
+
+# Global data
+
+class Glb():
+
+       def __init__(self, dbref, db, dbname):
+               self.dbref = dbref
+               self.db = db
+               self.dbname = dbname
+               self.app = None
+               self.mainwindow = None
+
+# Database reference
+
+class DBRef():
+
+       def __init__(self, is_sqlite3, dbname):
+               self.is_sqlite3 = is_sqlite3
+               self.dbname = dbname
+
+       def Open(self, connection_name):
+               dbname = self.dbname
+               if self.is_sqlite3:
+                       db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
+               else:
+                       db = QSqlDatabase.addDatabase("QPSQL", connection_name)
+                       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())
+               return db, dbname
+
+# Main
+
+def Main():
+       if (len(sys.argv) < 2):
+               print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
+               raise Exception("Too few arguments")
+
+       dbname = sys.argv[1]
+
+       is_sqlite3 = False
+       try:
+               f = open(dbname)
+               if f.read(15) == "SQLite format 3":
+                       is_sqlite3 = True
+               f.close()
+       except:
+               pass
+
+       dbref = DBRef(is_sqlite3, dbname)
+       db, dbname = dbref.Open("main")
+       glb = Glb(dbref, db, dbname)
+       app = QApplication(sys.argv)
+       glb.app = app
+       mainwindow = MainWindow(glb)
+       glb.mainwindow = mainwindow
+       mainwindow.show()
+       err = app.exec_()
+       db.close()
+       sys.exit(err)
+
+if __name__ == "__main__":
+       Main()