]> Git Repo - linux.git/blobdiff - tools/perf/scripts/python/exported-sql-viewer.py
Merge tag 'platform-drivers-x86-v5.3-1' of git://git.infradead.org/linux-platform...
[linux.git] / tools / perf / scripts / python / exported-sql-viewer.py
index 74ef92f1d19ad6637140829497997cc2cf8b686e..6e7934f2ac9a31549cddfd1a669e6cc7f61b613e 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python
 # SPDX-License-Identifier: GPL-2.0
 # exported-sql-viewer.py: view data from sql database
 # Copyright (c) 2014-2018, Intel Corporation.
@@ -91,6 +91,7 @@
 from __future__ import print_function
 
 import sys
+import argparse
 import weakref
 import threading
 import string
@@ -104,10 +105,23 @@ except ImportError:
        glb_nsz = 16
 import re
 import os
-from PySide.QtCore import *
-from PySide.QtGui import *
-from PySide.QtSql import *
+
 pyside_version_1 = True
+if not "--pyside-version-1" in sys.argv:
+       try:
+               from PySide2.QtCore import *
+               from PySide2.QtGui import *
+               from PySide2.QtSql import *
+               from PySide2.QtWidgets import *
+               pyside_version_1 = False
+       except:
+               pass
+
+if pyside_version_1:
+       from PySide.QtCore import *
+       from PySide.QtGui import *
+       from PySide.QtSql import *
+
 from decimal import *
 from ctypes import *
 from multiprocessing import Process, Array, Value, Event
@@ -186,9 +200,10 @@ class Thread(QThread):
 
 class TreeModel(QAbstractItemModel):
 
-       def __init__(self, glb, parent=None):
+       def __init__(self, glb, params, parent=None):
                super(TreeModel, self).__init__(parent)
                self.glb = glb
+               self.params = params
                self.root = self.GetRoot()
                self.last_row_read = 0
 
@@ -385,6 +400,7 @@ class FindBar():
 
        def Activate(self):
                self.bar.show()
+               self.textbox.lineEdit().selectAll()
                self.textbox.setFocus()
 
        def Deactivate(self):
@@ -449,13 +465,18 @@ class FindBar():
 
 class CallGraphLevelItemBase(object):
 
-       def __init__(self, glb, row, parent_item):
+       def __init__(self, glb, params, row, parent_item):
                self.glb = glb
+               self.params = params
                self.row = row
                self.parent_item = parent_item
                self.query_done = False;
                self.child_count = 0
                self.child_items = []
+               if parent_item:
+                       self.level = parent_item.level + 1
+               else:
+                       self.level = 0
 
        def getChildItem(self, row):
                return self.child_items[row]
@@ -485,18 +506,24 @@ class CallGraphLevelItemBase(object):
 
 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)
+       def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
+               super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
                self.comm_id = comm_id
                self.thread_id = thread_id
                self.call_path_id = call_path_id
+               self.insn_cnt = insn_cnt
+               self.cyc_cnt = cyc_cnt
                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)"
+               if self.params.have_ipc:
+                       ipc_str = ", SUM(insn_count), SUM(cyc_count)"
+               else:
+                       ipc_str = ""
+               QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", 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"
@@ -507,7 +534,15 @@ class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
                                        " 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)
+                       if self.params.have_ipc:
+                               insn_cnt = int(query.value(5))
+                               cyc_cnt = int(query.value(6))
+                               branch_count = int(query.value(7))
+                       else:
+                               insn_cnt = 0
+                               cyc_cnt = 0
+                               branch_count = int(query.value(5))
+                       child_item = CallGraphLevelThreeItem(self.glb, self.params, 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)), insn_cnt, cyc_cnt, branch_count, self)
                        self.child_items.append(child_item)
                        self.child_count += 1
 
@@ -515,37 +550,57 @@ class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
 
 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)
+       def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
+               super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, 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) ]
+               if self.params.have_ipc:
+                       insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
+                       cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
+                       br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
+                       ipc = CalcIPC(cyc_cnt, insn_cnt)
+                       self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
+               else:
+                       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), "", "", "", "", "", ""]
+       def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
+               super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
+               if self.params.have_ipc:
+                       self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
+               else:
+                       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.insn_cnt += child_item.insn_cnt
+                       self.cyc_cnt += child_item.cyc_cnt
                        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)
+                       if self.params.have_ipc:
+                               child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
+                               child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
+                               child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
+                       else:
+                               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, "", "", "", "", "", ""]
+       def __init__(self, glb, params, row, comm_id, comm, parent_item):
+               super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
+               if self.params.have_ipc:
+                       self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
+               else:
+                       self.data = [comm, "", "", "", "", "", ""]
                self.dbid = comm_id
 
        def Select(self):
@@ -556,7 +611,7 @@ class CallGraphLevelOneItem(CallGraphLevelItemBase):
                                        " 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)
+                       child_item = CallGraphLevelTwoItem(self.glb, self.params, 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
 
@@ -564,8 +619,8 @@ class CallGraphLevelOneItem(CallGraphLevelItemBase):
 
 class CallGraphRootItem(CallGraphLevelItemBase):
 
-       def __init__(self, glb):
-               super(CallGraphRootItem, self).__init__(glb, 0, None)
+       def __init__(self, glb, params):
+               super(CallGraphRootItem, self).__init__(glb, params, 0, None)
                self.dbid = 0
                self.query_done = True;
                query = QSqlQuery(glb.db)
@@ -573,16 +628,23 @@ class CallGraphRootItem(CallGraphLevelItemBase):
                while query.next():
                        if not query.value(0):
                                continue
-                       child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
+                       child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
                        self.child_items.append(child_item)
                        self.child_count += 1
 
+# Call graph model parameters
+
+class CallGraphModelParams():
+
+       def __init__(self, glb, parent=None):
+               self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
+
 # Context-sensitive call graph data model base
 
 class CallGraphModelBase(TreeModel):
 
        def __init__(self, glb, parent=None):
-               super(CallGraphModelBase, self).__init__(glb, parent)
+               super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
 
        def FindSelect(self, value, pattern, query):
                if pattern:
@@ -664,17 +726,26 @@ class CallGraphModel(CallGraphModelBase):
                super(CallGraphModel, self).__init__(glb, parent)
 
        def GetRoot(self):
-               return CallGraphRootItem(self.glb)
+               return CallGraphRootItem(self.glb, self.params)
 
        def columnCount(self, parent=None):
-               return 7
+               if self.params.have_ipc:
+                       return 12
+               else:
+                       return 7
 
        def columnHeader(self, column):
-               headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
+               if self.params.have_ipc:
+                       headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
+               else:
+                       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 ]
+               if self.params.have_ipc:
+                       alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
+               else:
+                       alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
                return alignment[column]
 
        def DoFindSelect(self, query, match):
@@ -711,11 +782,13 @@ class CallGraphModel(CallGraphModelBase):
 
 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
 
-       def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
-               super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
+       def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
+               super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
                self.comm_id = comm_id
                self.thread_id = thread_id
                self.calls_id = calls_id
+               self.insn_cnt = insn_cnt
+               self.cyc_cnt = cyc_cnt
                self.branch_count = branch_count
                self.time = time
 
@@ -725,8 +798,12 @@ class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
                        comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
                else:
                        comm_thread = ""
+               if self.params.have_ipc:
+                       ipc_str = ", insn_count, cyc_count"
+               else:
+                       ipc_str = ""
                query = QSqlQuery(self.glb.db)
-               QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
+               QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", 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"
@@ -734,7 +811,15 @@ class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
                                        " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
                                        " ORDER BY call_time, calls.id")
                while query.next():
-                       child_item = CallTreeLevelThreeItem(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)
+                       if self.params.have_ipc:
+                               insn_cnt = int(query.value(5))
+                               cyc_cnt = int(query.value(6))
+                               branch_count = int(query.value(7))
+                       else:
+                               insn_cnt = 0
+                               cyc_cnt = 0
+                               branch_count = int(query.value(5))
+                       child_item = CallTreeLevelThreeItem(self.glb, self.params, 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)), insn_cnt, cyc_cnt, branch_count, self)
                        self.child_items.append(child_item)
                        self.child_count += 1
 
@@ -742,37 +827,57 @@ class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
 
 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
 
-       def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
-               super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
+       def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
+               super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, 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) ]
+               if self.params.have_ipc:
+                       insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
+                       cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
+                       br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
+                       ipc = CalcIPC(cyc_cnt, insn_cnt)
+                       self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
+               else:
+                       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 = calls_id
 
 # Call tree data model level two item
 
 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
 
-       def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
-               super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
-               self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
+       def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
+               super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, parent_item)
+               if self.params.have_ipc:
+                       self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
+               else:
+                       self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
                self.dbid = thread_id
 
        def Select(self):
                super(CallTreeLevelTwoItem, self).Select()
                for child_item in self.child_items:
                        self.time += child_item.time
+                       self.insn_cnt += child_item.insn_cnt
+                       self.cyc_cnt += child_item.cyc_cnt
                        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)
+                       if self.params.have_ipc:
+                               child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
+                               child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
+                               child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
+                       else:
+                               child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
 
 # Call tree data model level one item
 
 class CallTreeLevelOneItem(CallGraphLevelItemBase):
 
-       def __init__(self, glb, row, comm_id, comm, parent_item):
-               super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
-               self.data = [comm, "", "", "", "", "", ""]
+       def __init__(self, glb, params, row, comm_id, comm, parent_item):
+               super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
+               if self.params.have_ipc:
+                       self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
+               else:
+                       self.data = [comm, "", "", "", "", "", ""]
                self.dbid = comm_id
 
        def Select(self):
@@ -783,7 +888,7 @@ class CallTreeLevelOneItem(CallGraphLevelItemBase):
                                        " INNER JOIN threads ON thread_id = threads.id"
                                        " WHERE comm_id = " + str(self.dbid))
                while query.next():
-                       child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
+                       child_item = CallTreeLevelTwoItem(self.glb, self.params, 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
 
@@ -791,8 +896,8 @@ class CallTreeLevelOneItem(CallGraphLevelItemBase):
 
 class CallTreeRootItem(CallGraphLevelItemBase):
 
-       def __init__(self, glb):
-               super(CallTreeRootItem, self).__init__(glb, 0, None)
+       def __init__(self, glb, params):
+               super(CallTreeRootItem, self).__init__(glb, params, 0, None)
                self.dbid = 0
                self.query_done = True;
                query = QSqlQuery(glb.db)
@@ -800,7 +905,7 @@ class CallTreeRootItem(CallGraphLevelItemBase):
                while query.next():
                        if not query.value(0):
                                continue
-                       child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
+                       child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
                        self.child_items.append(child_item)
                        self.child_count += 1
 
@@ -812,17 +917,26 @@ class CallTreeModel(CallGraphModelBase):
                super(CallTreeModel, self).__init__(glb, parent)
 
        def GetRoot(self):
-               return CallTreeRootItem(self.glb)
+               return CallTreeRootItem(self.glb, self.params)
 
        def columnCount(self, parent=None):
-               return 7
+               if self.params.have_ipc:
+                       return 12
+               else:
+                       return 7
 
        def columnHeader(self, column):
-               headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
+               if self.params.have_ipc:
+                       headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
+               else:
+                       headers = ["Call Path", "Object", "Call Time", "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 ]
+               if self.params.have_ipc:
+                       alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
+               else:
+                       alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
                return alignment[column]
 
        def DoFindSelect(self, query, match):
@@ -877,9 +991,14 @@ class TreeWindowBase(QMdiSubWindow):
                super(TreeWindowBase, self).__init__(parent)
 
                self.model = None
-               self.view = None
                self.find_bar = None
 
+               self.view = QTreeView()
+               self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+               self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
+
+               self.context_menu = TreeContextMenu(self.view)
+
        def DisplayFound(self, ids):
                if not len(ids):
                        return False
@@ -921,7 +1040,6 @@ class CallGraphWindow(TreeWindowBase):
 
                self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
 
-               self.view = QTreeView()
                self.view.setModel(self.model)
 
                for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
@@ -944,7 +1062,6 @@ class CallTreeWindow(TreeWindowBase):
 
                self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
 
-               self.view = QTreeView()
                self.view.setModel(self.model)
 
                for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
@@ -1348,11 +1465,11 @@ class FetchMoreRecordsBar():
 
 class BranchLevelTwoItem():
 
-       def __init__(self, row, text, parent_item):
+       def __init__(self, row, col, text, parent_item):
                self.row = row
                self.parent_item = parent_item
-               self.data = [""] * 8
-               self.data[7] = text
+               self.data = [""] * (col + 1)
+               self.data[col] = text
                self.level = 2
 
        def getParentItem(self):
@@ -1384,6 +1501,7 @@ class BranchLevelOneItem():
                self.dbid = data[0]
                self.level = 1
                self.query_done = False
+               self.br_col = len(self.data) - 1
 
        def getChildItem(self, row):
                return self.child_items[row]
@@ -1464,7 +1582,7 @@ class BranchLevelOneItem():
                                while k < 15:
                                        byte_str += "   "
                                        k += 1
-                               self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
+                               self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
                                self.child_count += 1
                        else:
                                return
@@ -1515,16 +1633,37 @@ class BranchRootItem():
        def getData(self, column):
                return ""
 
+# Calculate instructions per cycle
+
+def CalcIPC(cyc_cnt, insn_cnt):
+       if cyc_cnt and insn_cnt:
+               ipc = Decimal(float(insn_cnt) / cyc_cnt)
+               ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
+       else:
+               ipc = "0"
+       return ipc
+
 # Branch data preparation
 
-def BranchDataPrep(query):
-       data = []
-       for i in xrange(0, 8):
-               data.append(query.value(i))
+def BranchDataPrepBr(query, data):
        data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
                        " (" + dsoname(query.value(11)) + ")" + " -> " +
                        tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
                        " (" + dsoname(query.value(15)) + ")")
+
+def BranchDataPrepIPC(query, data):
+       insn_cnt = query.value(16)
+       cyc_cnt = query.value(17)
+       ipc = CalcIPC(cyc_cnt, insn_cnt)
+       data.append(insn_cnt)
+       data.append(cyc_cnt)
+       data.append(ipc)
+
+def BranchDataPrep(query):
+       data = []
+       for i in xrange(0, 8):
+               data.append(query.value(i))
+       BranchDataPrepBr(query, data)
        return data
 
 def BranchDataPrepWA(query):
@@ -1534,10 +1673,26 @@ def BranchDataPrepWA(query):
        data.append("{:>19}".format(query.value(1)))
        for i in xrange(2, 8):
                data.append(query.value(i))
-       data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
-                       " (" + dsoname(query.value(11)) + ")" + " -> " +
-                       tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
-                       " (" + dsoname(query.value(15)) + ")")
+       BranchDataPrepBr(query, data)
+       return data
+
+def BranchDataWithIPCPrep(query):
+       data = []
+       for i in xrange(0, 8):
+               data.append(query.value(i))
+       BranchDataPrepIPC(query, data)
+       BranchDataPrepBr(query, data)
+       return data
+
+def BranchDataWithIPCPrepWA(query):
+       data = []
+       data.append(query.value(0))
+       # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
+       data.append("{:>19}".format(query.value(1)))
+       for i in xrange(2, 8):
+               data.append(query.value(i))
+       BranchDataPrepIPC(query, data)
+       BranchDataPrepBr(query, data)
        return data
 
 # Branch data model
@@ -1547,14 +1702,24 @@ class BranchModel(TreeModel):
        progress = Signal(object)
 
        def __init__(self, glb, event_id, where_clause, parent=None):
-               super(BranchModel, self).__init__(glb, parent)
+               super(BranchModel, self).__init__(glb, None, parent)
                self.event_id = event_id
                self.more = True
                self.populated = 0
+               self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
+               if self.have_ipc:
+                       select_ipc = ", insn_count, cyc_count"
+                       prep_fn = BranchDataWithIPCPrep
+                       prep_wa_fn = BranchDataWithIPCPrepWA
+               else:
+                       select_ipc = ""
+                       prep_fn = BranchDataPrep
+                       prep_wa_fn = BranchDataPrepWA
                sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
                        " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
                        " ip, symbols.name, sym_offset, dsos.short_name,"
                        " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
+                       + select_ipc +
                        " FROM samples"
                        " INNER JOIN comms ON comm_id = comms.id"
                        " INNER JOIN threads ON thread_id = threads.id"
@@ -1568,9 +1733,9 @@ class BranchModel(TreeModel):
                        " ORDER BY samples.id"
                        " LIMIT " + str(glb_chunk_sz))
                if pyside_version_1 and sys.version_info[0] == 3:
-                       prep = BranchDataPrepWA
+                       prep = prep_fn
                else:
-                       prep = BranchDataPrep
+                       prep = prep_wa_fn
                self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
                self.fetcher.done.connect(self.Update)
                self.fetcher.Fetch(glb_chunk_sz)
@@ -1579,13 +1744,23 @@ class BranchModel(TreeModel):
                return BranchRootItem()
 
        def columnCount(self, parent=None):
-               return 8
+               if self.have_ipc:
+                       return 11
+               else:
+                       return 8
 
        def columnHeader(self, column):
-               return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
+               if self.have_ipc:
+                       return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
+               else:
+                       return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
 
        def columnFont(self, column):
-               if column != 7:
+               if self.have_ipc:
+                       br_col = 10
+               else:
+                       br_col = 7
+               if column != br_col:
                        return None
                return QFont("Monospace")
 
@@ -1649,10 +1824,14 @@ class BranchWindow(QMdiSubWindow):
 
                self.view = QTreeView()
                self.view.setUniformRowHeights(True)
+               self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+               self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
                self.view.setModel(self.model)
 
                self.ResizeColumnsToContents()
 
+               self.context_menu = TreeContextMenu(self.view)
+
                self.find_bar = FindBar(self, self, True)
 
                self.finder = ChildDataItemFinder(self.model.root)
@@ -2089,10 +2268,10 @@ def GetEventList(db):
 
 # Is a table selectable
 
-def IsSelectable(db, table, sql = ""):
+def IsSelectable(db, table, sql = "", columns = "*"):
        query = QSqlQuery(db)
        try:
-               QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
+               QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
        except:
                return False
        return True
@@ -2261,6 +2440,240 @@ class ResizeColumnsToContentsBase(QObject):
                self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
                self.ResizeColumnsToContents()
 
+# Convert value to CSV
+
+def ToCSValue(val):
+       if '"' in val:
+               val = val.replace('"', '""')
+       if "," in val or '"' in val:
+               val = '"' + val + '"'
+       return val
+
+# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
+
+glb_max_cols = 1000
+
+def RowColumnKey(a):
+       return a.row() * glb_max_cols + a.column()
+
+# Copy selected table cells to clipboard
+
+def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
+       indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
+       idx_cnt = len(indexes)
+       if not idx_cnt:
+               return
+       if idx_cnt == 1:
+               with_hdr=False
+       min_row = indexes[0].row()
+       max_row = indexes[0].row()
+       min_col = indexes[0].column()
+       max_col = indexes[0].column()
+       for i in indexes:
+               min_row = min(min_row, i.row())
+               max_row = max(max_row, i.row())
+               min_col = min(min_col, i.column())
+               max_col = max(max_col, i.column())
+       if max_col > glb_max_cols:
+               raise RuntimeError("glb_max_cols is too low")
+       max_width = [0] * (1 + max_col - min_col)
+       for i in indexes:
+               c = i.column() - min_col
+               max_width[c] = max(max_width[c], len(str(i.data())))
+       text = ""
+       pad = ""
+       sep = ""
+       if with_hdr:
+               model = indexes[0].model()
+               for col in range(min_col, max_col + 1):
+                       val = model.headerData(col, Qt.Horizontal)
+                       if as_csv:
+                               text += sep + ToCSValue(val)
+                               sep = ","
+                       else:
+                               c = col - min_col
+                               max_width[c] = max(max_width[c], len(val))
+                               width = max_width[c]
+                               align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
+                               if align & Qt.AlignRight:
+                                       val = val.rjust(width)
+                               text += pad + sep + val
+                               pad = " " * (width - len(val))
+                               sep = "  "
+               text += "\n"
+               pad = ""
+               sep = ""
+       last_row = min_row
+       for i in indexes:
+               if i.row() > last_row:
+                       last_row = i.row()
+                       text += "\n"
+                       pad = ""
+                       sep = ""
+               if as_csv:
+                       text += sep + ToCSValue(str(i.data()))
+                       sep = ","
+               else:
+                       width = max_width[i.column() - min_col]
+                       if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+                               val = str(i.data()).rjust(width)
+                       else:
+                               val = str(i.data())
+                       text += pad + sep + val
+                       pad = " " * (width - len(val))
+                       sep = "  "
+       QApplication.clipboard().setText(text)
+
+def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
+       indexes = view.selectedIndexes()
+       if not len(indexes):
+               return
+
+       selection = view.selectionModel()
+
+       first = None
+       for i in indexes:
+               above = view.indexAbove(i)
+               if not selection.isSelected(above):
+                       first = i
+                       break
+
+       if first is None:
+               raise RuntimeError("CopyTreeCellsToClipboard internal error")
+
+       model = first.model()
+       row_cnt = 0
+       col_cnt = model.columnCount(first)
+       max_width = [0] * col_cnt
+
+       indent_sz = 2
+       indent_str = " " * indent_sz
+
+       expanded_mark_sz = 2
+       if sys.version_info[0] == 3:
+               expanded_mark = "\u25BC "
+               not_expanded_mark = "\u25B6 "
+       else:
+               expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
+               not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
+       leaf_mark = "  "
+
+       if not as_csv:
+               pos = first
+               while True:
+                       row_cnt += 1
+                       row = pos.row()
+                       for c in range(col_cnt):
+                               i = pos.sibling(row, c)
+                               if c:
+                                       n = len(str(i.data()))
+                               else:
+                                       n = len(str(i.data()).strip())
+                                       n += (i.internalPointer().level - 1) * indent_sz
+                                       n += expanded_mark_sz
+                               max_width[c] = max(max_width[c], n)
+                       pos = view.indexBelow(pos)
+                       if not selection.isSelected(pos):
+                               break
+
+       text = ""
+       pad = ""
+       sep = ""
+       if with_hdr:
+               for c in range(col_cnt):
+                       val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
+                       if as_csv:
+                               text += sep + ToCSValue(val)
+                               sep = ","
+                       else:
+                               max_width[c] = max(max_width[c], len(val))
+                               width = max_width[c]
+                               align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
+                               if align & Qt.AlignRight:
+                                       val = val.rjust(width)
+                               text += pad + sep + val
+                               pad = " " * (width - len(val))
+                               sep = "   "
+               text += "\n"
+               pad = ""
+               sep = ""
+
+       pos = first
+       while True:
+               row = pos.row()
+               for c in range(col_cnt):
+                       i = pos.sibling(row, c)
+                       val = str(i.data())
+                       if not c:
+                               if model.hasChildren(i):
+                                       if view.isExpanded(i):
+                                               mark = expanded_mark
+                                       else:
+                                               mark = not_expanded_mark
+                               else:
+                                       mark = leaf_mark
+                               val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
+                       if as_csv:
+                               text += sep + ToCSValue(val)
+                               sep = ","
+                       else:
+                               width = max_width[c]
+                               if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+                                       val = val.rjust(width)
+                               text += pad + sep + val
+                               pad = " " * (width - len(val))
+                               sep = "   "
+               pos = view.indexBelow(pos)
+               if not selection.isSelected(pos):
+                       break
+               text = text.rstrip() + "\n"
+               pad = ""
+               sep = ""
+
+       QApplication.clipboard().setText(text)
+
+def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
+       view.CopyCellsToClipboard(view, as_csv, with_hdr)
+
+def CopyCellsToClipboardHdr(view):
+       CopyCellsToClipboard(view, False, True)
+
+def CopyCellsToClipboardCSV(view):
+       CopyCellsToClipboard(view, True, True)
+
+# Context menu
+
+class ContextMenu(object):
+
+       def __init__(self, view):
+               self.view = view
+               self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+               self.view.customContextMenuRequested.connect(self.ShowContextMenu)
+
+       def ShowContextMenu(self, pos):
+               menu = QMenu(self.view)
+               self.AddActions(menu)
+               menu.exec_(self.view.mapToGlobal(pos))
+
+       def AddCopy(self, menu):
+               menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
+               menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
+
+       def AddActions(self, menu):
+               self.AddCopy(menu)
+
+class TreeContextMenu(ContextMenu):
+
+       def __init__(self, view):
+               super(TreeContextMenu, self).__init__(view)
+
+       def AddActions(self, menu):
+               i = self.view.currentIndex()
+               text = str(i.data()).strip()
+               if len(text):
+                       menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
+               self.AddCopy(menu)
+
 # Table window
 
 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
@@ -2279,9 +2692,13 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
                self.view.verticalHeader().setVisible(False)
                self.view.sortByColumn(-1, Qt.AscendingOrder)
                self.view.setSortingEnabled(True)
+               self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+               self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
                self.ResizeColumnsToContents()
 
+               self.context_menu = ContextMenu(self.view)
+
                self.find_bar = FindBar(self, self, True)
 
                self.finder = ChildDataItemFinder(self.data_model)
@@ -2395,6 +2812,10 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
                self.view.setModel(self.model)
                self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
                self.view.verticalHeader().setVisible(False)
+               self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+               self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
+
+               self.context_menu = ContextMenu(self.view)
 
                self.ResizeColumnsToContents()
 
@@ -2501,7 +2922,7 @@ class WindowMenu():
                        action = self.window_menu.addAction(label)
                        action.setCheckable(True)
                        action.setChecked(sub_window == self.mdi_area.activeSubWindow())
-                       action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
+                       action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
                        self.window_menu.addAction(action)
                        nr += 1
 
@@ -2587,6 +3008,12 @@ cd xed
 sudo ./mfile.py --prefix=/usr/local install
 sudo ldconfig
 </pre>
+<h3>Instructions per Cycle (IPC)</h3>
+If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
+<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
+Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
+In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
+since the previous displayed 'IPC'.
 <h3>Find</h3>
 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
 Refer to Python documentation for the regular expression syntax.
@@ -2660,6 +3087,60 @@ class HelpOnlyWindow(QMainWindow):
 
                self.setCentralWidget(self.text)
 
+# PostqreSQL server version
+
+def PostqreSQLServerVersion(db):
+       query = QSqlQuery(db)
+       QueryExec(query, "SELECT VERSION()")
+       if query.next():
+               v_str = query.value(0)
+               v_list = v_str.strip().split(" ")
+               if v_list[0] == "PostgreSQL" and v_list[2] == "on":
+                       return v_list[1]
+               return v_str
+       return "Unknown"
+
+# SQLite version
+
+def SQLiteVersion(db):
+       query = QSqlQuery(db)
+       QueryExec(query, "SELECT sqlite_version()")
+       if query.next():
+               return query.value(0)
+       return "Unknown"
+
+# About dialog
+
+class AboutDialog(QDialog):
+
+       def __init__(self, glb, parent=None):
+               super(AboutDialog, self).__init__(parent)
+
+               self.setWindowTitle("About Exported SQL Viewer")
+               self.setMinimumWidth(300)
+
+               pyside_version = "1" if pyside_version_1 else "2"
+
+               text = "<pre>"
+               text += "Python version:     " + sys.version.split(" ")[0] + "\n"
+               text += "PySide version:     " + pyside_version + "\n"
+               text += "Qt version:         " + qVersion() + "\n"
+               if glb.dbref.is_sqlite3:
+                       text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
+               else:
+                       text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
+               text += "</pre>"
+
+               self.text = QTextBrowser()
+               self.text.setHtml(text)
+               self.text.setReadOnly(True)
+               self.text.setOpenExternalLinks(True)
+
+               self.vbox = QVBoxLayout()
+               self.vbox.addWidget(self.text)
+
+               self.setLayout(self.vbox);
+
 # Font resize
 
 def ResizeFont(widget, diff):
@@ -2732,6 +3213,8 @@ class MainWindow(QMainWindow):
                file_menu.addAction(CreateExitAction(glb.app, self))
 
                edit_menu = menu.addMenu("&Edit")
+               edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
+               edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
                edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
                edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
                edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
@@ -2755,6 +3238,21 @@ class MainWindow(QMainWindow):
 
                help_menu = menu.addMenu("&Help")
                help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
+               help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
+
+       def Try(self, fn):
+               win = self.mdi_area.activeSubWindow()
+               if win:
+                       try:
+                               fn(win.view)
+                       except:
+                               pass
+
+       def CopyToClipboard(self):
+               self.Try(CopyCellsToClipboardHdr)
+
+       def CopyToClipboardCSV(self):
+               self.Try(CopyCellsToClipboardCSV)
 
        def Find(self):
                win = self.mdi_area.activeSubWindow()
@@ -2773,12 +3271,10 @@ class MainWindow(QMainWindow):
                                pass
 
        def ShrinkFont(self):
-               win = self.mdi_area.activeSubWindow()
-               ShrinkFont(win.view)
+               self.Try(ShrinkFont)
 
        def EnlargeFont(self):
-               win = self.mdi_area.activeSubWindow()
-               EnlargeFont(win.view)
+               self.Try(EnlargeFont)
 
        def EventMenu(self, events, reports_menu):
                branches_events = 0
@@ -2792,14 +3288,14 @@ class MainWindow(QMainWindow):
                        event = event.split(":")[0]
                        if event == "branches":
                                label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
-                               reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
+                               reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
                                label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
-                               reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
+                               reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
 
        def TableMenu(self, tables, menu):
                table_menu = menu.addMenu("&Tables")
                for table in tables:
-                       table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
+                       table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
 
        def NewCallGraph(self):
                CallGraphWindow(self.glb, self)
@@ -2828,6 +3324,10 @@ class MainWindow(QMainWindow):
        def Help(self):
                HelpWindow(self.glb, self)
 
+       def About(self):
+               dialog = AboutDialog(self.glb, self)
+               dialog.exec_()
+
 # XED Disassembler
 
 class xed_state_t(Structure):
@@ -3035,18 +3535,27 @@ class DBRef():
 # Main
 
 def Main():
-       if (len(sys.argv) < 2):
-               printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
-               raise Exception("Too few arguments")
-
-       dbname = sys.argv[1]
-       if dbname == "--help-only":
+       usage_str =     "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
+                       "   or: exported-sql-viewer.py --help-only"
+       ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
+       ap.add_argument("--pyside-version-1", action='store_true')
+       ap.add_argument("dbname", nargs="?")
+       ap.add_argument("--help-only", action='store_true')
+       args = ap.parse_args()
+
+       if args.help_only:
                app = QApplication(sys.argv)
                mainwindow = HelpOnlyWindow()
                mainwindow.show()
                err = app.exec_()
                sys.exit(err)
 
+       dbname = args.dbname
+       if dbname is None:
+               ap.print_usage()
+               print("Too few arguments")
+               sys.exit(1)
+
        is_sqlite3 = False
        try:
                f = open(dbname, "rb")
This page took 0.074306 seconds and 4 git commands to generate.