2015-07-17 16:33:45 +00:00
|
|
|
#!/usr/bin/python2
|
2018-10-01 06:28:35 +00:00
|
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
# exported-sql-viewer.py: view data from sql database
|
|
|
|
# Copyright (c) 2014-2018, Intel Corporation.
|
2015-07-17 16:33:45 +00:00
|
|
|
|
2017-08-03 08:31:30 +00:00
|
|
|
# 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.
|
2015-07-17 16:33:45 +00:00
|
|
|
#
|
2017-08-03 08:31:30 +00:00
|
|
|
# Following on from the example in the export scripts, a
|
2015-07-17 16:33:45 +00:00
|
|
|
# call-graph can be displayed for the pt_example database like this:
|
|
|
|
#
|
2017-08-03 08:31:29 +00:00
|
|
|
# python tools/perf/scripts/python/call-graph-from-sql.py pt_example
|
2015-07-17 16:33:45 +00:00
|
|
|
#
|
2017-08-03 08:31:30 +00:00
|
|
|
# Note that for PostgreSQL, this script supports connecting to remote databases
|
|
|
|
# by setting hostname, port, username, password, and dbname e.g.
|
2015-07-17 16:33:45 +00:00
|
|
|
#
|
2017-08-03 08:31:29 +00:00
|
|
|
# python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
|
2015-07-17 16:33:45 +00:00
|
|
|
#
|
|
|
|
# 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 *
|
|
|
|
|
2018-10-01 06:28:44 +00:00
|
|
|
# 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())
|
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
# Tree data model
|
|
|
|
|
2015-07-17 16:33:45 +00:00
|
|
|
class TreeModel(QAbstractItemModel):
|
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
def __init__(self, root, parent=None):
|
2015-07-17 16:33:45 +00:00
|
|
|
super(TreeModel, self).__init__(parent)
|
2018-10-01 06:28:43 +00:00
|
|
|
self.root = root
|
|
|
|
self.last_row_read = 0
|
2015-07-17 16:33:45 +00:00
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
def Item(self, parent):
|
2015-07-17 16:33:45 +00:00
|
|
|
if parent.isValid():
|
2018-10-01 06:28:43 +00:00
|
|
|
return parent.internalPointer()
|
2015-07-17 16:33:45 +00:00
|
|
|
else:
|
2018-10-01 06:28:43 +00:00
|
|
|
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()
|
2015-07-17 16:33:45 +00:00
|
|
|
|
|
|
|
def headerData(self, section, orientation, role):
|
|
|
|
if role == Qt.TextAlignmentRole:
|
2018-10-01 06:28:43 +00:00
|
|
|
return self.columnAlignment(section)
|
2015-07-17 16:33:45 +00:00
|
|
|
if role != Qt.DisplayRole:
|
|
|
|
return None
|
|
|
|
if orientation != Qt.Horizontal:
|
|
|
|
return None
|
2018-10-01 06:28:43 +00:00
|
|
|
return self.columnHeader(section)
|
2015-07-17 16:33:45 +00:00
|
|
|
|
|
|
|
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):
|
2018-10-01 06:28:43 +00:00
|
|
|
child_item = self.Item(parent).getChildItem(row)
|
2015-07-17 16:33:45 +00:00
|
|
|
return self.createIndex(row, column, child_item)
|
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
def DisplayData(self, item, index):
|
|
|
|
return item.getData(index.column())
|
|
|
|
|
|
|
|
def columnAlignment(self, column):
|
|
|
|
return Qt.AlignLeft
|
|
|
|
|
|
|
|
def columnFont(self, column):
|
|
|
|
return None
|
|
|
|
|
2015-07-17 16:33:45 +00:00
|
|
|
def data(self, index, role):
|
|
|
|
if role == Qt.TextAlignmentRole:
|
2018-10-01 06:28:43 +00:00
|
|
|
return self.columnAlignment(index.column())
|
|
|
|
if role == Qt.FontRole:
|
|
|
|
return self.columnFont(index.column())
|
2015-07-17 16:33:45 +00:00
|
|
|
if role != Qt.DisplayRole:
|
|
|
|
return None
|
2018-10-01 06:28:43 +00:00
|
|
|
item = index.internalPointer()
|
|
|
|
return self.DisplayData(item, index)
|
|
|
|
|
2018-10-01 06:28:45 +00:00
|
|
|
# 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
|
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
# Context-sensitive call graph data model
|
|
|
|
|
|
|
|
class CallGraphModel(TreeModel):
|
|
|
|
|
|
|
|
def __init__(self, glb, parent=None):
|
2018-10-01 06:28:45 +00:00
|
|
|
super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
|
2018-10-01 06:28:43 +00:00
|
|
|
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
|
2015-07-17 16:33:45 +00:00
|
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
|
|
|
2018-10-01 06:28:41 +00:00
|
|
|
def __init__(self, glb, parent=None):
|
2015-07-17 16:33:45 +00:00
|
|
|
super(MainWindow, self).__init__(parent)
|
|
|
|
|
2018-10-01 06:28:41 +00:00
|
|
|
self.glb = glb
|
|
|
|
|
|
|
|
self.setWindowTitle("Call Graph: " + glb.dbname)
|
2015-07-17 16:33:45 +00:00
|
|
|
self.move(100, 100)
|
|
|
|
self.resize(800, 600)
|
2018-10-01 06:28:38 +00:00
|
|
|
self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
|
2018-10-01 06:28:37 +00:00
|
|
|
self.setMinimumSize(200, 100)
|
2015-07-17 16:33:45 +00:00
|
|
|
|
2018-10-01 06:28:43 +00:00
|
|
|
self.model = CallGraphModel(glb)
|
2015-07-17 16:33:45 +00:00
|
|
|
|
|
|
|
self.view = QTreeView()
|
|
|
|
self.view.setModel(self.model)
|
|
|
|
|
perf scripts python: call-graph-from-sql.py: Provide better default column sizes
Set initial column sizes to improve initial display.
Committer testing:
Extended instructions on testing this, using the sqlite variant:
Make sure you have the SQLite glue for python+Qt installed, on fedora 27
I used:
# dnf install python-pyside
Collect some PT samples, say 5-secs worth, system wide:
# perf record -r 10 -e intel_pt//u -a sleep 5
[ perf record: Woken up 49 times to write data ]
[ perf record: Captured and wrote 96.131 MB perf.data ]
This results in this perf.data file:
# ls -larth perf.data
-rw-------. 1 root root 97M Oct 23 10:11 perf.data
With the following attributes:
# perf evlist -v
intel_pt//u: type: 8, size: 112, config: 0x300e601, { sample_period, sample_freq }: 1, sample_type: IP|TID|TIME|CPU|IDENTIFIER, read_format: ID, disabled: 1, inherit: 1, exclude_kernel: 1, exclude_hv: 1, sample_id_all: 1
dummy:u: type: 1, size: 112, config: 0x9, { sample_period, sample_freq }: 1, sample_type: IP|TID|TIME|CPU|IDENTIFIER, read_format: ID, inherit: 1, exclude_kernel: 1, exclude_hv: 1, mmap: 1, comm: 1, task: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, context_switch: 1
#
Then generate the "pt_example" tables using:
# perf script -s ~/libexec/perf-core/scripts/python/export-to-sqlite.py pt_example branches calls
2018-10-23 10:56:59.177711 Creating database...
2018-10-23 10:56:59.195842 Writing records...
instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x263984516750 code 5: Failed to get instruction
instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e116fd20 code 6: Trace doesn't match instruction
instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e162c9ee code 6: Trace doesn't match instruction
instruction trace error type 1 cpu 2 pid 1644 tid 1644 ip 0x7f26e9ce831a code 6: Trace doesn't match instruction
<SNIP>
instruction trace error type 1 cpu 0 pid 1644 tid 1644 ip 0x7f26e13d07b4 code 6: Trace doesn't match instruction
Warning:
132 instruction trace errors
2018-10-23 11:25:25.015717 Adding indexes
2018-10-23 11:25:28.788061 Done
#
In my example, that perf.data file generated this db:
# file pt_example
pt_example: SQLite 3.x database, last written using SQLite version 3020001
[root@seventh perf]# ls -lah pt_example
-rw-r--r--. 1 root root 6.6G Oct 23 11:25 pt_example
#
Then use this python script to use that db and provide a GUI:
$ python tools/perf/scripts/python/call-graph-from-sql.py pt_example branches calls
I compared the column widths before this patch and after applying it,
the visual results match the patch intent.
The following patches will refer to this set of instructions in the "Committer
Testing" section.
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-3-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-10-01 06:28:36 +00:00
|
|
|
for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
|
|
|
|
self.view.setColumnWidth(c, w)
|
|
|
|
|
2015-07-17 16:33:45 +00:00
|
|
|
self.setCentralWidget(self.view)
|
|
|
|
|
2018-10-01 06:28:41 +00:00
|
|
|
# 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
|
|
|
|
|
2018-10-01 06:28:40 +00:00
|
|
|
# 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
|
|
|
|
|
2018-10-01 06:28:39 +00:00
|
|
|
# Main
|
|
|
|
|
|
|
|
def Main():
|
2015-07-17 16:33:45 +00:00
|
|
|
if (len(sys.argv) < 2):
|
2017-08-03 08:31:29 +00:00
|
|
|
print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
|
2015-07-17 16:33:45 +00:00
|
|
|
raise Exception("Too few arguments")
|
|
|
|
|
|
|
|
dbname = sys.argv[1]
|
|
|
|
|
2017-08-03 08:31:30 +00:00
|
|
|
is_sqlite3 = False
|
|
|
|
try:
|
|
|
|
f = open(dbname)
|
|
|
|
if f.read(15) == "SQLite format 3":
|
|
|
|
is_sqlite3 = True
|
|
|
|
f.close()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2018-10-01 06:28:40 +00:00
|
|
|
dbref = DBRef(is_sqlite3, dbname)
|
|
|
|
db, dbname = dbref.Open("main")
|
2018-10-01 06:28:41 +00:00
|
|
|
glb = Glb(dbref, db, dbname)
|
2015-07-17 16:33:45 +00:00
|
|
|
app = QApplication(sys.argv)
|
2018-10-01 06:28:41 +00:00
|
|
|
glb.app = app
|
|
|
|
mainwindow = MainWindow(glb)
|
|
|
|
glb.mainwindow = mainwindow
|
|
|
|
mainwindow.show()
|
2015-07-17 16:33:45 +00:00
|
|
|
err = app.exec_()
|
|
|
|
db.close()
|
|
|
|
sys.exit(err)
|
2018-10-01 06:28:39 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
Main()
|