aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/scripts/python/exported-sql-viewer.py
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@intel.com>2018-10-01 02:28:50 -0400
committerArnaldo Carvalho de Melo <acme@redhat.com>2018-10-23 13:39:18 -0400
commit8392b74b575c38fa5d50d1fe07fa9a4bcea93862 (patch)
treef27afc8e8d9a5358c847e106f5faba47b5be34c7 /tools/perf/scripts/python/exported-sql-viewer.py
parent82f68e2898e634b8b0efc7ddd57e037ef75ea114 (diff)
perf scripts python: exported-sql-viewer.py: Add ability to display all the database tables
Displaying all the database tables can help make the database easier to understand. Committer testing: Opened all the tables, even the sqlite master table, which I selected everything and used control+C, lets see if it works... CREATE VIEW threads_view AS SELECT id,machine_id,(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,process_id,pid,tid FROM threads Humm, nope, just one of the cells got copied, even with everything selected :-) Anyway, works as advertised, useful for perusing the data. Signed-off-by: Adrian Hunter <adrian.hunter@intel.com> Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Jiri Olsa <jolsa@redhat.com> Link: http://lkml.kernel.org/r/20181001062853.28285-17-adrian.hunter@intel.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
-rwxr-xr-xtools/perf/scripts/python/exported-sql-viewer.py694
1 files changed, 694 insertions, 0 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 310ba7147583..ef822d850109 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -50,10 +50,15 @@ import sys
50import weakref 50import weakref
51import threading 51import threading
52import string 52import string
53import cPickle
54import re
55import os
53from PySide.QtCore import * 56from PySide.QtCore import *
54from PySide.QtGui import * 57from PySide.QtGui import *
55from PySide.QtSql import * 58from PySide.QtSql import *
56from decimal import * 59from decimal import *
60from ctypes import *
61from multiprocessing import Process, Array, Value, Event
57 62
58# Data formatting helpers 63# Data formatting helpers
59 64
@@ -146,6 +151,68 @@ class TreeModel(QAbstractItemModel):
146 def DisplayData(self, item, index): 151 def DisplayData(self, item, index):
147 return item.getData(index.column()) 152 return item.getData(index.column())
148 153
154 def FetchIfNeeded(self, row):
155 if row > self.last_row_read:
156 self.last_row_read = row
157 if row + 10 >= self.root.child_count:
158 self.fetcher.Fetch(glb_chunk_sz)
159
160 def columnAlignment(self, column):
161 return Qt.AlignLeft
162
163 def columnFont(self, column):
164 return None
165
166 def data(self, index, role):
167 if role == Qt.TextAlignmentRole:
168 return self.columnAlignment(index.column())
169 if role == Qt.FontRole:
170 return self.columnFont(index.column())
171 if role != Qt.DisplayRole:
172 return None
173 item = index.internalPointer()
174 return self.DisplayData(item, index)
175
176# Table data model
177
178class TableModel(QAbstractTableModel):
179
180 def __init__(self, parent=None):
181 super(TableModel, self).__init__(parent)
182 self.child_count = 0
183 self.child_items = []
184 self.last_row_read = 0
185
186 def Item(self, parent):
187 if parent.isValid():
188 return parent.internalPointer()
189 else:
190 return self
191
192 def rowCount(self, parent):
193 return self.child_count
194
195 def headerData(self, section, orientation, role):
196 if role == Qt.TextAlignmentRole:
197 return self.columnAlignment(section)
198 if role != Qt.DisplayRole:
199 return None
200 if orientation != Qt.Horizontal:
201 return None
202 return self.columnHeader(section)
203
204 def index(self, row, column, parent):
205 return self.createIndex(row, column, self.child_items[row])
206
207 def DisplayData(self, item, index):
208 return item.getData(index.column())
209
210 def FetchIfNeeded(self, row):
211 if row > self.last_row_read:
212 self.last_row_read = row
213 if row + 10 >= self.child_count:
214 self.fetcher.Fetch(glb_chunk_sz)
215
149 def columnAlignment(self, column): 216 def columnAlignment(self, column):
150 return Qt.AlignLeft 217 return Qt.AlignLeft
151 218
@@ -620,6 +687,601 @@ class CallGraphWindow(QMdiSubWindow):
620 if not found: 687 if not found:
621 self.find_bar.NotFound() 688 self.find_bar.NotFound()
622 689
690# Child data item finder
691
692class ChildDataItemFinder():
693
694 def __init__(self, root):
695 self.root = root
696 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
697 self.rows = []
698 self.pos = 0
699
700 def FindSelect(self):
701 self.rows = []
702 if self.pattern:
703 pattern = re.compile(self.value)
704 for child in self.root.child_items:
705 for column_data in child.data:
706 if re.search(pattern, str(column_data)) is not None:
707 self.rows.append(child.row)
708 break
709 else:
710 for child in self.root.child_items:
711 for column_data in child.data:
712 if self.value in str(column_data):
713 self.rows.append(child.row)
714 break
715
716 def FindValue(self):
717 self.pos = 0
718 if self.last_value != self.value or self.pattern != self.last_pattern:
719 self.FindSelect()
720 if not len(self.rows):
721 return -1
722 return self.rows[self.pos]
723
724 def FindThread(self):
725 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
726 row = self.FindValue()
727 elif len(self.rows):
728 if self.direction > 0:
729 self.pos += 1
730 if self.pos >= len(self.rows):
731 self.pos = 0
732 else:
733 self.pos -= 1
734 if self.pos < 0:
735 self.pos = len(self.rows) - 1
736 row = self.rows[self.pos]
737 else:
738 row = -1
739 return (True, row)
740
741 def Find(self, value, direction, pattern, context, callback):
742 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
743 # Use a thread so the UI is not blocked
744 thread = Thread(self.FindThread)
745 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
746 thread.start()
747
748 def FindDone(self, thread, callback, row):
749 callback(row)
750
751# Number of database records to fetch in one go
752
753glb_chunk_sz = 10000
754
755# size of pickled integer big enough for record size
756
757glb_nsz = 8
758
759# Background process for SQL data fetcher
760
761class SQLFetcherProcess():
762
763 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
764 # Need a unique connection name
765 conn_name = "SQLFetcher" + str(os.getpid())
766 self.db, dbname = dbref.Open(conn_name)
767 self.sql = sql
768 self.buffer = buffer
769 self.head = head
770 self.tail = tail
771 self.fetch_count = fetch_count
772 self.fetching_done = fetching_done
773 self.process_target = process_target
774 self.wait_event = wait_event
775 self.fetched_event = fetched_event
776 self.prep = prep
777 self.query = QSqlQuery(self.db)
778 self.query_limit = 0 if "$$last_id$$" in sql else 2
779 self.last_id = -1
780 self.fetched = 0
781 self.more = True
782 self.local_head = self.head.value
783 self.local_tail = self.tail.value
784
785 def Select(self):
786 if self.query_limit:
787 if self.query_limit == 1:
788 return
789 self.query_limit -= 1
790 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
791 QueryExec(self.query, stmt)
792
793 def Next(self):
794 if not self.query.next():
795 self.Select()
796 if not self.query.next():
797 return None
798 self.last_id = self.query.value(0)
799 return self.prep(self.query)
800
801 def WaitForTarget(self):
802 while True:
803 self.wait_event.clear()
804 target = self.process_target.value
805 if target > self.fetched or target < 0:
806 break
807 self.wait_event.wait()
808 return target
809
810 def HasSpace(self, sz):
811 if self.local_tail <= self.local_head:
812 space = len(self.buffer) - self.local_head
813 if space > sz:
814 return True
815 if space >= glb_nsz:
816 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
817 nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
818 self.buffer[self.local_head : self.local_head + len(nd)] = nd
819 self.local_head = 0
820 if self.local_tail - self.local_head > sz:
821 return True
822 return False
823
824 def WaitForSpace(self, sz):
825 if self.HasSpace(sz):
826 return
827 while True:
828 self.wait_event.clear()
829 self.local_tail = self.tail.value
830 if self.HasSpace(sz):
831 return
832 self.wait_event.wait()
833
834 def AddToBuffer(self, obj):
835 d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
836 n = len(d)
837 nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
838 sz = n + glb_nsz
839 self.WaitForSpace(sz)
840 pos = self.local_head
841 self.buffer[pos : pos + len(nd)] = nd
842 self.buffer[pos + glb_nsz : pos + sz] = d
843 self.local_head += sz
844
845 def FetchBatch(self, batch_size):
846 fetched = 0
847 while batch_size > fetched:
848 obj = self.Next()
849 if obj is None:
850 self.more = False
851 break
852 self.AddToBuffer(obj)
853 fetched += 1
854 if fetched:
855 self.fetched += fetched
856 with self.fetch_count.get_lock():
857 self.fetch_count.value += fetched
858 self.head.value = self.local_head
859 self.fetched_event.set()
860
861 def Run(self):
862 while self.more:
863 target = self.WaitForTarget()
864 if target < 0:
865 break
866 batch_size = min(glb_chunk_sz, target - self.fetched)
867 self.FetchBatch(batch_size)
868 self.fetching_done.value = True
869 self.fetched_event.set()
870
871def SQLFetcherFn(*x):
872 process = SQLFetcherProcess(*x)
873 process.Run()
874
875# SQL data fetcher
876
877class SQLFetcher(QObject):
878
879 done = Signal(object)
880
881 def __init__(self, glb, sql, prep, process_data, parent=None):
882 super(SQLFetcher, self).__init__(parent)
883 self.process_data = process_data
884 self.more = True
885 self.target = 0
886 self.last_target = 0
887 self.fetched = 0
888 self.buffer_size = 16 * 1024 * 1024
889 self.buffer = Array(c_char, self.buffer_size, lock=False)
890 self.head = Value(c_longlong)
891 self.tail = Value(c_longlong)
892 self.local_tail = 0
893 self.fetch_count = Value(c_longlong)
894 self.fetching_done = Value(c_bool)
895 self.last_count = 0
896 self.process_target = Value(c_longlong)
897 self.wait_event = Event()
898 self.fetched_event = Event()
899 glb.AddInstanceToShutdownOnExit(self)
900 self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep))
901 self.process.start()
902 self.thread = Thread(self.Thread)
903 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
904 self.thread.start()
905
906 def Shutdown(self):
907 # Tell the thread and process to exit
908 self.process_target.value = -1
909 self.wait_event.set()
910 self.more = False
911 self.fetching_done.value = True
912 self.fetched_event.set()
913
914 def Thread(self):
915 if not self.more:
916 return True, 0
917 while True:
918 self.fetched_event.clear()
919 fetch_count = self.fetch_count.value
920 if fetch_count != self.last_count:
921 break
922 if self.fetching_done.value:
923 self.more = False
924 return True, 0
925 self.fetched_event.wait()
926 count = fetch_count - self.last_count
927 self.last_count = fetch_count
928 self.fetched += count
929 return False, count
930
931 def Fetch(self, nr):
932 if not self.more:
933 # -1 inidcates there are no more
934 return -1
935 result = self.fetched
936 extra = result + nr - self.target
937 if extra > 0:
938 self.target += extra
939 # process_target < 0 indicates shutting down
940 if self.process_target.value >= 0:
941 self.process_target.value = self.target
942 self.wait_event.set()
943 return result
944
945 def RemoveFromBuffer(self):
946 pos = self.local_tail
947 if len(self.buffer) - pos < glb_nsz:
948 pos = 0
949 n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
950 if n == 0:
951 pos = 0
952 n = cPickle.loads(self.buffer[0 : glb_nsz])
953 pos += glb_nsz
954 obj = cPickle.loads(self.buffer[pos : pos + n])
955 self.local_tail = pos + n
956 return obj
957
958 def ProcessData(self, count):
959 for i in xrange(count):
960 obj = self.RemoveFromBuffer()
961 self.process_data(obj)
962 self.tail.value = self.local_tail
963 self.wait_event.set()
964 self.done.emit(count)
965
966# Fetch more records bar
967
968class FetchMoreRecordsBar():
969
970 def __init__(self, model, parent):
971 self.model = model
972
973 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
974 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
975
976 self.fetch_count = QSpinBox()
977 self.fetch_count.setRange(1, 1000000)
978 self.fetch_count.setValue(10)
979 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
980
981 self.fetch = QPushButton("Go!")
982 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
983 self.fetch.released.connect(self.FetchMoreRecords)
984
985 self.progress = QProgressBar()
986 self.progress.setRange(0, 100)
987 self.progress.hide()
988
989 self.done_label = QLabel("All records fetched")
990 self.done_label.hide()
991
992 self.spacer = QLabel("")
993
994 self.close_button = QToolButton()
995 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
996 self.close_button.released.connect(self.Deactivate)
997
998 self.hbox = QHBoxLayout()
999 self.hbox.setContentsMargins(0, 0, 0, 0)
1000
1001 self.hbox.addWidget(self.label)
1002 self.hbox.addWidget(self.fetch_count)
1003 self.hbox.addWidget(self.fetch)
1004 self.hbox.addWidget(self.spacer)
1005 self.hbox.addWidget(self.progress)
1006 self.hbox.addWidget(self.done_label)
1007 self.hbox.addWidget(self.close_button)
1008
1009 self.bar = QWidget()
1010 self.bar.setLayout(self.hbox);
1011 self.bar.show()
1012
1013 self.in_progress = False
1014 self.model.progress.connect(self.Progress)
1015
1016 self.done = False
1017
1018 if not model.HasMoreRecords():
1019 self.Done()
1020
1021 def Widget(self):
1022 return self.bar
1023
1024 def Activate(self):
1025 self.bar.show()
1026 self.fetch.setFocus()
1027
1028 def Deactivate(self):
1029 self.bar.hide()
1030
1031 def Enable(self, enable):
1032 self.fetch.setEnabled(enable)
1033 self.fetch_count.setEnabled(enable)
1034
1035 def Busy(self):
1036 self.Enable(False)
1037 self.fetch.hide()
1038 self.spacer.hide()
1039 self.progress.show()
1040
1041 def Idle(self):
1042 self.in_progress = False
1043 self.Enable(True)
1044 self.progress.hide()
1045 self.fetch.show()
1046 self.spacer.show()
1047
1048 def Target(self):
1049 return self.fetch_count.value() * glb_chunk_sz
1050
1051 def Done(self):
1052 self.done = True
1053 self.Idle()
1054 self.label.hide()
1055 self.fetch_count.hide()
1056 self.fetch.hide()
1057 self.spacer.hide()
1058 self.done_label.show()
1059
1060 def Progress(self, count):
1061 if self.in_progress:
1062 if count:
1063 percent = ((count - self.start) * 100) / self.Target()
1064 if percent >= 100:
1065 self.Idle()
1066 else:
1067 self.progress.setValue(percent)
1068 if not count:
1069 # Count value of zero means no more records
1070 self.Done()
1071
1072 def FetchMoreRecords(self):
1073 if self.done:
1074 return
1075 self.progress.setValue(0)
1076 self.Busy()
1077 self.in_progress = True
1078 self.start = self.model.FetchMoreRecords(self.Target())
1079
1080# SQL data preparation
1081
1082def SQLTableDataPrep(query, count):
1083 data = []
1084 for i in xrange(count):
1085 data.append(query.value(i))
1086 return data
1087
1088# SQL table data model item
1089
1090class SQLTableItem():
1091
1092 def __init__(self, row, data):
1093 self.row = row
1094 self.data = data
1095
1096 def getData(self, column):
1097 return self.data[column]
1098
1099# SQL table data model
1100
1101class SQLTableModel(TableModel):
1102
1103 progress = Signal(object)
1104
1105 def __init__(self, glb, sql, column_count, parent=None):
1106 super(SQLTableModel, self).__init__(parent)
1107 self.glb = glb
1108 self.more = True
1109 self.populated = 0
1110 self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
1111 self.fetcher.done.connect(self.Update)
1112 self.fetcher.Fetch(glb_chunk_sz)
1113
1114 def DisplayData(self, item, index):
1115 self.FetchIfNeeded(item.row)
1116 return item.getData(index.column())
1117
1118 def AddSample(self, data):
1119 child = SQLTableItem(self.populated, data)
1120 self.child_items.append(child)
1121 self.populated += 1
1122
1123 def Update(self, fetched):
1124 if not fetched:
1125 self.more = False
1126 self.progress.emit(0)
1127 child_count = self.child_count
1128 count = self.populated - child_count
1129 if count > 0:
1130 parent = QModelIndex()
1131 self.beginInsertRows(parent, child_count, child_count + count - 1)
1132 self.insertRows(child_count, count, parent)
1133 self.child_count += count
1134 self.endInsertRows()
1135 self.progress.emit(self.child_count)
1136
1137 def FetchMoreRecords(self, count):
1138 current = self.child_count
1139 if self.more:
1140 self.fetcher.Fetch(count)
1141 else:
1142 self.progress.emit(0)
1143 return current
1144
1145 def HasMoreRecords(self):
1146 return self.more
1147
1148# SQL automatic table data model
1149
1150class SQLAutoTableModel(SQLTableModel):
1151
1152 def __init__(self, glb, table_name, parent=None):
1153 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1154 if table_name == "comm_threads_view":
1155 # For now, comm_threads_view has no id column
1156 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1157 self.column_headers = []
1158 query = QSqlQuery(glb.db)
1159 if glb.dbref.is_sqlite3:
1160 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1161 while query.next():
1162 self.column_headers.append(query.value(1))
1163 if table_name == "sqlite_master":
1164 sql = "SELECT * FROM " + table_name
1165 else:
1166 if table_name[:19] == "information_schema.":
1167 sql = "SELECT * FROM " + table_name
1168 select_table_name = table_name[19:]
1169 schema = "information_schema"
1170 else:
1171 select_table_name = table_name
1172 schema = "public"
1173 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1174 while query.next():
1175 self.column_headers.append(query.value(0))
1176 super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
1177
1178 def columnCount(self, parent=None):
1179 return len(self.column_headers)
1180
1181 def columnHeader(self, column):
1182 return self.column_headers[column]
1183
1184# Base class for custom ResizeColumnsToContents
1185
1186class ResizeColumnsToContentsBase(QObject):
1187
1188 def __init__(self, parent=None):
1189 super(ResizeColumnsToContentsBase, self).__init__(parent)
1190
1191 def ResizeColumnToContents(self, column, n):
1192 # Using the view's resizeColumnToContents() here is extrememly slow
1193 # so implement a crude alternative
1194 font = self.view.font()
1195 metrics = QFontMetrics(font)
1196 max = 0
1197 for row in xrange(n):
1198 val = self.data_model.child_items[row].data[column]
1199 len = metrics.width(str(val) + "MM")
1200 max = len if len > max else max
1201 val = self.data_model.columnHeader(column)
1202 len = metrics.width(str(val) + "MM")
1203 max = len if len > max else max
1204 self.view.setColumnWidth(column, max)
1205
1206 def ResizeColumnsToContents(self):
1207 n = min(self.data_model.child_count, 100)
1208 if n < 1:
1209 # No data yet, so connect a signal to notify when there is
1210 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
1211 return
1212 columns = self.data_model.columnCount()
1213 for i in xrange(columns):
1214 self.ResizeColumnToContents(i, n)
1215
1216 def UpdateColumnWidths(self, *x):
1217 # This only needs to be done once, so disconnect the signal now
1218 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
1219 self.ResizeColumnsToContents()
1220
1221# Table window
1222
1223class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
1224
1225 def __init__(self, glb, table_name, parent=None):
1226 super(TableWindow, self).__init__(parent)
1227
1228 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
1229
1230 self.model = QSortFilterProxyModel()
1231 self.model.setSourceModel(self.data_model)
1232
1233 self.view = QTableView()
1234 self.view.setModel(self.model)
1235 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1236 self.view.verticalHeader().setVisible(False)
1237 self.view.sortByColumn(-1, Qt.AscendingOrder)
1238 self.view.setSortingEnabled(True)
1239
1240 self.ResizeColumnsToContents()
1241
1242 self.find_bar = FindBar(self, self, True)
1243
1244 self.finder = ChildDataItemFinder(self.data_model)
1245
1246 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
1247
1248 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1249
1250 self.setWidget(self.vbox.Widget())
1251
1252 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
1253
1254 def Find(self, value, direction, pattern, context):
1255 self.view.setFocus()
1256 self.find_bar.Busy()
1257 self.finder.Find(value, direction, pattern, context, self.FindDone)
1258
1259 def FindDone(self, row):
1260 self.find_bar.Idle()
1261 if row >= 0:
1262 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1263 else:
1264 self.find_bar.NotFound()
1265
1266# Table list
1267
1268def GetTableList(glb):
1269 tables = []
1270 query = QSqlQuery(glb.db)
1271 if glb.dbref.is_sqlite3:
1272 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
1273 else:
1274 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
1275 while query.next():
1276 tables.append(query.value(0))
1277 if glb.dbref.is_sqlite3:
1278 tables.append("sqlite_master")
1279 else:
1280 tables.append("information_schema.tables")
1281 tables.append("information_schema.views")
1282 tables.append("information_schema.columns")
1283 return tables
1284
623# Action Definition 1285# Action Definition
624 1286
625def CreateAction(label, tip, callback, parent=None, shortcut=None): 1287def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -779,12 +1441,15 @@ class MainWindow(QMainWindow):
779 1441
780 edit_menu = menu.addMenu("&Edit") 1442 edit_menu = menu.addMenu("&Edit")
781 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) 1443 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
1444 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
782 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) 1445 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
783 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) 1446 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
784 1447
785 reports_menu = menu.addMenu("&Reports") 1448 reports_menu = menu.addMenu("&Reports")
786 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) 1449 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
787 1450
1451 self.TableMenu(GetTableList(glb), menu)
1452
788 self.window_menu = WindowMenu(self.mdi_area, menu) 1453 self.window_menu = WindowMenu(self.mdi_area, menu)
789 1454
790 def Find(self): 1455 def Find(self):
@@ -795,6 +1460,14 @@ class MainWindow(QMainWindow):
795 except: 1460 except:
796 pass 1461 pass
797 1462
1463 def FetchMoreRecords(self):
1464 win = self.mdi_area.activeSubWindow()
1465 if win:
1466 try:
1467 win.fetch_bar.Activate()
1468 except:
1469 pass
1470
798 def ShrinkFont(self): 1471 def ShrinkFont(self):
799 win = self.mdi_area.activeSubWindow() 1472 win = self.mdi_area.activeSubWindow()
800 ShrinkFont(win.view) 1473 ShrinkFont(win.view)
@@ -803,9 +1476,17 @@ class MainWindow(QMainWindow):
803 win = self.mdi_area.activeSubWindow() 1476 win = self.mdi_area.activeSubWindow()
804 EnlargeFont(win.view) 1477 EnlargeFont(win.view)
805 1478
1479 def TableMenu(self, tables, menu):
1480 table_menu = menu.addMenu("&Tables")
1481 for table in tables:
1482 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
1483
806 def NewCallGraph(self): 1484 def NewCallGraph(self):
807 CallGraphWindow(self.glb, self) 1485 CallGraphWindow(self.glb, self)
808 1486
1487 def NewTableView(self, table_name):
1488 TableWindow(self.glb, table_name, self)
1489
809# Global data 1490# Global data
810 1491
811class Glb(): 1492class Glb():
@@ -816,6 +1497,18 @@ class Glb():
816 self.dbname = dbname 1497 self.dbname = dbname
817 self.app = None 1498 self.app = None
818 self.mainwindow = None 1499 self.mainwindow = None
1500 self.instances_to_shutdown_on_exit = weakref.WeakSet()
1501
1502 def AddInstanceToShutdownOnExit(self, instance):
1503 self.instances_to_shutdown_on_exit.add(instance)
1504
1505 # Shutdown any background processes or threads
1506 def ShutdownInstances(self):
1507 for x in self.instances_to_shutdown_on_exit:
1508 try:
1509 x.Shutdown()
1510 except:
1511 pass
819 1512
820# Database reference 1513# Database reference
821 1514
@@ -880,6 +1573,7 @@ def Main():
880 glb.mainwindow = mainwindow 1573 glb.mainwindow = mainwindow
881 mainwindow.show() 1574 mainwindow.show()
882 err = app.exec_() 1575 err = app.exec_()
1576 glb.ShutdownInstances()
883 db.close() 1577 db.close()
884 sys.exit(err) 1578 sys.exit(err)
885 1579