]> Git Repo - linux.git/blobdiff - tools/perf/scripts/python/exported-sql-viewer.py
Merge tag 'media/v4.20-4' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[linux.git] / tools / perf / scripts / python / exported-sql-viewer.py
index ef822d850109352110a4fd14a0f27e3572d37254..f278ce5ebab76640de4eb61ee8661040d0ac092a 100755 (executable)
 #      'Branch Count' is the total number of branches for that function and all
 #       functions that it calls
 
+# There is also a "All branches" report, which displays branches and
+# possibly disassembly.  However, presently, the only supported disassembler is
+# Intel XED, and additionally the object code must be present in perf build ID
+# cache. To use Intel XED, libxed.so must be present. To build and install
+# libxed.so:
+#            git clone https://github.com/intelxed/mbuild.git mbuild
+#            git clone https://github.com/intelxed/xed
+#            cd xed
+#            ./mfile.py --share
+#            sudo ./mfile.py --prefix=/usr/local install
+#            sudo ldconfig
+#
+# Example report:
+#
+# Time           CPU  Command  PID    TID    Branch Type            In Tx  Branch
+# 8107675239590  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
+#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
+# 8107675239899  2    ls       22011  22011  hardware interrupt     No         7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
+# 8107675241900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
+#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
+#                                                                              7fab593ea263 e8 c8 06 00 00                                  callq  0x7fab593ea930
+# 8107675241900  2    ls       22011  22011  call                   No         7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
+#                                                                              7fab593ea930 55                                              pushq  %rbp
+#                                                                              7fab593ea931 48 89 e5                                        mov %rsp, %rbp
+#                                                                              7fab593ea934 41 57                                           pushq  %r15
+#                                                                              7fab593ea936 41 56                                           pushq  %r14
+#                                                                              7fab593ea938 41 55                                           pushq  %r13
+#                                                                              7fab593ea93a 41 54                                           pushq  %r12
+#                                                                              7fab593ea93c 53                                              pushq  %rbx
+#                                                                              7fab593ea93d 48 89 fb                                        mov %rdi, %rbx
+#                                                                              7fab593ea940 48 83 ec 68                                     sub $0x68, %rsp
+#                                                                              7fab593ea944 0f 31                                           rdtsc
+#                                                                              7fab593ea946 48 c1 e2 20                                     shl $0x20, %rdx
+#                                                                              7fab593ea94a 89 c0                                           mov %eax, %eax
+#                                                                              7fab593ea94c 48 09 c2                                        or %rax, %rdx
+#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
+# 8107675242232  2    ls       22011  22011  hardware interrupt     No         7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
+# 8107675242900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
+#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
+#                                                                              7fab593ea956 48 89 15 3b 13 22 00                            movq  %rdx, 0x22133b(%rip)
+# 8107675243232  2    ls       22011  22011  hardware interrupt     No         7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
+
 import sys
 import weakref
 import threading
@@ -62,11 +104,29 @@ from multiprocessing import Process, Array, Value, Event
 
 # Data formatting helpers
 
+def tohex(ip):
+       if ip < 0:
+               ip += 1 << 64
+       return "%x" % ip
+
+def offstr(offset):
+       if offset:
+               return "+0x%x" % offset
+       return ""
+
 def dsoname(name):
        if name == "[kernel.kallsyms]":
                return "[kernel]"
        return name
 
+def findnth(s, sub, n, offs=0):
+       pos = s.find(sub)
+       if pos < 0:
+               return pos
+       if n <= 1:
+               return offs + pos
+       return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
+
 # Percent to one decimal place
 
 def PercentToOneDP(n, d):
@@ -1077,6 +1137,662 @@ class FetchMoreRecordsBar():
                self.in_progress = True
                self.start = self.model.FetchMoreRecords(self.Target())
 
+# Brance data model level two item
+
+class BranchLevelTwoItem():
+
+       def __init__(self, row, text, parent_item):
+               self.row = row
+               self.parent_item = parent_item
+               self.data = [""] * 8
+               self.data[7] = text
+               self.level = 2
+
+       def getParentItem(self):
+               return self.parent_item
+
+       def getRow(self):
+               return self.row
+
+       def childCount(self):
+               return 0
+
+       def hasChildren(self):
+               return False
+
+       def getData(self, column):
+               return self.data[column]
+
+# Brance data model level one item
+
+class BranchLevelOneItem():
+
+       def __init__(self, glb, row, data, parent_item):
+               self.glb = glb
+               self.row = row
+               self.parent_item = parent_item
+               self.child_count = 0
+               self.child_items = []
+               self.data = data[1:]
+               self.dbid = data[0]
+               self.level = 1
+               self.query_done = False
+
+       def getChildItem(self, row):
+               return self.child_items[row]
+
+       def getParentItem(self):
+               return self.parent_item
+
+       def getRow(self):
+               return self.row
+
+       def Select(self):
+               self.query_done = True
+
+               if not self.glb.have_disassembler:
+                       return
+
+               query = QSqlQuery(self.glb.db)
+
+               QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
+                                 " FROM samples"
+                                 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
+                                 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
+                                 " WHERE samples.id = " + str(self.dbid))
+               if not query.next():
+                       return
+               cpu = query.value(0)
+               dso = query.value(1)
+               sym = query.value(2)
+               if dso == 0 or sym == 0:
+                       return
+               off = query.value(3)
+               short_name = query.value(4)
+               long_name = query.value(5)
+               build_id = query.value(6)
+               sym_start = query.value(7)
+               ip = query.value(8)
+
+               QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
+                                 " FROM samples"
+                                 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
+                                 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
+                                 " ORDER BY samples.id"
+                                 " LIMIT 1")
+               if not query.next():
+                       return
+               if query.value(0) != dso:
+                       # Cannot disassemble from one dso to another
+                       return
+               bsym = query.value(1)
+               boff = query.value(2)
+               bsym_start = query.value(3)
+               if bsym == 0:
+                       return
+               tot = bsym_start + boff + 1 - sym_start - off
+               if tot <= 0 or tot > 16384:
+                       return
+
+               inst = self.glb.disassembler.Instruction()
+               f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
+               if not f:
+                       return
+               mode = 0 if Is64Bit(f) else 1
+               self.glb.disassembler.SetMode(inst, mode)
+
+               buf_sz = tot + 16
+               buf = create_string_buffer(tot + 16)
+               f.seek(sym_start + off)
+               buf.value = f.read(buf_sz)
+               buf_ptr = addressof(buf)
+               i = 0
+               while tot > 0:
+                       cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
+                       if cnt:
+                               byte_str = tohex(ip).rjust(16)
+                               for k in xrange(cnt):
+                                       byte_str += " %02x" % ord(buf[i])
+                                       i += 1
+                               while k < 15:
+                                       byte_str += "   "
+                                       k += 1
+                               self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
+                               self.child_count += 1
+                       else:
+                               return
+                       buf_ptr += cnt
+                       tot -= cnt
+                       buf_sz -= cnt
+                       ip += cnt
+
+       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]
+
+# Brance data model root item
+
+class BranchRootItem():
+
+       def __init__(self):
+               self.child_count = 0
+               self.child_items = []
+               self.level = 0
+
+       def getChildItem(self, row):
+               return self.child_items[row]
+
+       def getParentItem(self):
+               return None
+
+       def getRow(self):
+               return 0
+
+       def childCount(self):
+               return self.child_count
+
+       def hasChildren(self):
+               return self.child_count > 0
+
+       def getData(self, column):
+               return ""
+
+# Branch data preparation
+
+def BranchDataPrep(query):
+       data = []
+       for i in xrange(0, 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)) + ")")
+       return data
+
+# Branch data model
+
+class BranchModel(TreeModel):
+
+       progress = Signal(object)
+
+       def __init__(self, glb, event_id, where_clause, parent=None):
+               super(BranchModel, self).__init__(BranchRootItem(), parent)
+               self.glb = glb
+               self.event_id = event_id
+               self.more = True
+               self.populated = 0
+               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"
+                       " FROM samples"
+                       " INNER JOIN comms ON comm_id = comms.id"
+                       " INNER JOIN threads ON thread_id = threads.id"
+                       " INNER JOIN branch_types ON branch_type = branch_types.id"
+                       " INNER JOIN symbols ON symbol_id = symbols.id"
+                       " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
+                       " INNER JOIN dsos ON samples.dso_id = dsos.id"
+                       " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
+                       " WHERE samples.id > $$last_id$$" + where_clause +
+                       " AND evsel_id = " + str(self.event_id) +
+                       " ORDER BY samples.id"
+                       " LIMIT " + str(glb_chunk_sz))
+               self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
+               self.fetcher.done.connect(self.Update)
+               self.fetcher.Fetch(glb_chunk_sz)
+
+       def columnCount(self, parent=None):
+               return 8
+
+       def columnHeader(self, column):
+               return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
+
+       def columnFont(self, column):
+               if column != 7:
+                       return None
+               return QFont("Monospace")
+
+       def DisplayData(self, item, index):
+               if item.level == 1:
+                       self.FetchIfNeeded(item.row)
+               return item.getData(index.column())
+
+       def AddSample(self, data):
+               child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
+               self.root.child_items.append(child)
+               self.populated += 1
+
+       def Update(self, fetched):
+               if not fetched:
+                       self.more = False
+                       self.progress.emit(0)
+               child_count = self.root.child_count
+               count = self.populated - child_count
+               if count > 0:
+                       parent = QModelIndex()
+                       self.beginInsertRows(parent, child_count, child_count + count - 1)
+                       self.insertRows(child_count, count, parent)
+                       self.root.child_count += count
+                       self.endInsertRows()
+                       self.progress.emit(self.root.child_count)
+
+       def FetchMoreRecords(self, count):
+               current = self.root.child_count
+               if self.more:
+                       self.fetcher.Fetch(count)
+               else:
+                       self.progress.emit(0)
+               return current
+
+       def HasMoreRecords(self):
+               return self.more
+
+# Branch window
+
+class BranchWindow(QMdiSubWindow):
+
+       def __init__(self, glb, event_id, name, where_clause, parent=None):
+               super(BranchWindow, self).__init__(parent)
+
+               model_name = "Branch Events " + str(event_id)
+               if len(where_clause):
+                       model_name = where_clause + " " + model_name
+
+               self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause))
+
+               self.view = QTreeView()
+               self.view.setUniformRowHeights(True)
+               self.view.setModel(self.model)
+
+               self.ResizeColumnsToContents()
+
+               self.find_bar = FindBar(self, self, True)
+
+               self.finder = ChildDataItemFinder(self.model.root)
+
+               self.fetch_bar = FetchMoreRecordsBar(self.model, self)
+
+               self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
+
+               self.setWidget(self.vbox.Widget())
+
+               AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events")
+
+       def ResizeColumnToContents(self, column, n):
+               # Using the view's resizeColumnToContents() here is extrememly slow
+               # so implement a crude alternative
+               mm = "MM" if column else "MMMM"
+               font = self.view.font()
+               metrics = QFontMetrics(font)
+               max = 0
+               for row in xrange(n):
+                       val = self.model.root.child_items[row].data[column]
+                       len = metrics.width(str(val) + mm)
+                       max = len if len > max else max
+               val = self.model.columnHeader(column)
+               len = metrics.width(str(val) + mm)
+               max = len if len > max else max
+               self.view.setColumnWidth(column, max)
+
+       def ResizeColumnsToContents(self):
+               n = min(self.model.root.child_count, 100)
+               if n < 1:
+                       # No data yet, so connect a signal to notify when there is
+                       self.model.rowsInserted.connect(self.UpdateColumnWidths)
+                       return
+               columns = self.model.columnCount()
+               for i in xrange(columns):
+                       self.ResizeColumnToContents(i, n)
+
+       def UpdateColumnWidths(self, *x):
+               # This only needs to be done once, so disconnect the signal now
+               self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
+               self.ResizeColumnsToContents()
+
+       def Find(self, value, direction, pattern, context):
+               self.view.setFocus()
+               self.find_bar.Busy()
+               self.finder.Find(value, direction, pattern, context, self.FindDone)
+
+       def FindDone(self, row):
+               self.find_bar.Idle()
+               if row >= 0:
+                       self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
+               else:
+                       self.find_bar.NotFound()
+
+# Dialog data item converted and validated using a SQL table
+
+class SQLTableDialogDataItem():
+
+       def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
+               self.glb = glb
+               self.label = label
+               self.placeholder_text = placeholder_text
+               self.table_name = table_name
+               self.match_column = match_column
+               self.column_name1 = column_name1
+               self.column_name2 = column_name2
+               self.parent = parent
+
+               self.value = ""
+
+               self.widget = QLineEdit()
+               self.widget.editingFinished.connect(self.Validate)
+               self.widget.textChanged.connect(self.Invalidate)
+               self.red = False
+               self.error = ""
+               self.validated = True
+
+               self.last_id = 0
+               self.first_time = 0
+               self.last_time = 2 ** 64
+               if self.table_name == "<timeranges>":
+                       query = QSqlQuery(self.glb.db)
+                       QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
+                       if query.next():
+                               self.last_id = int(query.value(0))
+                               self.last_time = int(query.value(1))
+                       QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
+                       if query.next():
+                               self.first_time = int(query.value(0))
+                       if placeholder_text:
+                               placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
+
+               if placeholder_text:
+                       self.widget.setPlaceholderText(placeholder_text)
+
+       def ValueToIds(self, value):
+               ids = []
+               query = QSqlQuery(self.glb.db)
+               stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
+               ret = query.exec_(stmt)
+               if ret:
+                       while query.next():
+                               ids.append(str(query.value(0)))
+               return ids
+
+       def IdBetween(self, query, lower_id, higher_id, order):
+               QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
+               if query.next():
+                       return True, int(query.value(0))
+               else:
+                       return False, 0
+
+       def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
+               query = QSqlQuery(self.glb.db)
+               while True:
+                       next_id = int((lower_id + higher_id) / 2)
+                       QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
+                       if not query.next():
+                               ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
+                               if not ok:
+                                       ok, dbid = self.IdBetween(query, next_id, higher_id, "")
+                                       if not ok:
+                                               return str(higher_id)
+                               next_id = dbid
+                               QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
+                       next_time = int(query.value(0))
+                       if get_floor:
+                               if target_time > next_time:
+                                       lower_id = next_id
+                               else:
+                                       higher_id = next_id
+                               if higher_id <= lower_id + 1:
+                                       return str(higher_id)
+                       else:
+                               if target_time >= next_time:
+                                       lower_id = next_id
+                               else:
+                                       higher_id = next_id
+                               if higher_id <= lower_id + 1:
+                                       return str(lower_id)
+
+       def ConvertRelativeTime(self, val):
+               print "val ", val
+               mult = 1
+               suffix = val[-2:]
+               if suffix == "ms":
+                       mult = 1000000
+               elif suffix == "us":
+                       mult = 1000
+               elif suffix == "ns":
+                       mult = 1
+               else:
+                       return val
+               val = val[:-2].strip()
+               if not self.IsNumber(val):
+                       return val
+               val = int(val) * mult
+               if val >= 0:
+                       val += self.first_time
+               else:
+                       val += self.last_time
+               return str(val)
+
+       def ConvertTimeRange(self, vrange):
+               print "vrange ", vrange
+               if vrange[0] == "":
+                       vrange[0] = str(self.first_time)
+               if vrange[1] == "":
+                       vrange[1] = str(self.last_time)
+               vrange[0] = self.ConvertRelativeTime(vrange[0])
+               vrange[1] = self.ConvertRelativeTime(vrange[1])
+               print "vrange2 ", vrange
+               if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
+                       return False
+               print "ok1"
+               beg_range = max(int(vrange[0]), self.first_time)
+               end_range = min(int(vrange[1]), self.last_time)
+               if beg_range > self.last_time or end_range < self.first_time:
+                       return False
+               print "ok2"
+               vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
+               vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
+               print "vrange3 ", vrange
+               return True
+
+       def AddTimeRange(self, value, ranges):
+               print "value ", value
+               n = value.count("-")
+               if n == 1:
+                       pass
+               elif n == 2:
+                       if value.split("-")[1].strip() == "":
+                               n = 1
+               elif n == 3:
+                       n = 2
+               else:
+                       return False
+               pos = findnth(value, "-", n)
+               vrange = [value[:pos].strip() ,value[pos+1:].strip()]
+               if self.ConvertTimeRange(vrange):
+                       ranges.append(vrange)
+                       return True
+               return False
+
+       def InvalidValue(self, value):
+               self.value = ""
+               palette = QPalette()
+               palette.setColor(QPalette.Text,Qt.red)
+               self.widget.setPalette(palette)
+               self.red = True
+               self.error = self.label + " invalid value '" + value + "'"
+               self.parent.ShowMessage(self.error)
+
+       def IsNumber(self, value):
+               try:
+                       x = int(value)
+               except:
+                       x = 0
+               return str(x) == value
+
+       def Invalidate(self):
+               self.validated = False
+
+       def Validate(self):
+               input_string = self.widget.text()
+               self.validated = True
+               if self.red:
+                       palette = QPalette()
+                       self.widget.setPalette(palette)
+                       self.red = False
+               if not len(input_string.strip()):
+                       self.error = ""
+                       self.value = ""
+                       return
+               if self.table_name == "<timeranges>":
+                       ranges = []
+                       for value in [x.strip() for x in input_string.split(",")]:
+                               if not self.AddTimeRange(value, ranges):
+                                       return self.InvalidValue(value)
+                       ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
+                       self.value = " OR ".join(ranges)
+               elif self.table_name == "<ranges>":
+                       singles = []
+                       ranges = []
+                       for value in [x.strip() for x in input_string.split(",")]:
+                               if "-" in value:
+                                       vrange = value.split("-")
+                                       if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
+                                               return self.InvalidValue(value)
+                                       ranges.append(vrange)
+                               else:
+                                       if not self.IsNumber(value):
+                                               return self.InvalidValue(value)
+                                       singles.append(value)
+                       ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
+                       if len(singles):
+                               ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")")
+                       self.value = " OR ".join(ranges)
+               elif self.table_name:
+                       all_ids = []
+                       for value in [x.strip() for x in input_string.split(",")]:
+                               ids = self.ValueToIds(value)
+                               if len(ids):
+                                       all_ids.extend(ids)
+                               else:
+                                       return self.InvalidValue(value)
+                       self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
+                       if self.column_name2:
+                               self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
+               else:
+                       self.value = input_string.strip()
+               self.error = ""
+               self.parent.ClearMessage()
+
+       def IsValid(self):
+               if not self.validated:
+                       self.Validate()
+               if len(self.error):
+                       self.parent.ShowMessage(self.error)
+                       return False
+               return True
+
+# Selected branch report creation dialog
+
+class SelectedBranchDialog(QDialog):
+
+       def __init__(self, glb, parent=None):
+               super(SelectedBranchDialog, self).__init__(parent)
+
+               self.glb = glb
+
+               self.name = ""
+               self.where_clause = ""
+
+               self.setWindowTitle("Selected Branches")
+               self.setMinimumWidth(600)
+
+               items = (
+                       ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""),
+                       ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""),
+                       ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""),
+                       ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""),
+                       ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""),
+                       ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""),
+                       ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"),
+                       ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"),
+                       ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""),
+                       )
+               self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items]
+
+               self.grid = QGridLayout()
+
+               for row in xrange(len(self.data_items)):
+                       self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
+                       self.grid.addWidget(self.data_items[row].widget, row, 1)
+
+               self.status = QLabel()
+
+               self.ok_button = QPushButton("Ok", self)
+               self.ok_button.setDefault(True)
+               self.ok_button.released.connect(self.Ok)
+               self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+
+               self.cancel_button = QPushButton("Cancel", self)
+               self.cancel_button.released.connect(self.reject)
+               self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+
+               self.hbox = QHBoxLayout()
+               #self.hbox.addStretch()
+               self.hbox.addWidget(self.status)
+               self.hbox.addWidget(self.ok_button)
+               self.hbox.addWidget(self.cancel_button)
+
+               self.vbox = QVBoxLayout()
+               self.vbox.addLayout(self.grid)
+               self.vbox.addLayout(self.hbox)
+
+               self.setLayout(self.vbox);
+
+       def Ok(self):
+               self.name = self.data_items[0].value
+               if not self.name:
+                       self.ShowMessage("Report name is required")
+                       return
+               for d in self.data_items:
+                       if not d.IsValid():
+                               return
+               for d in self.data_items[1:]:
+                       if len(d.value):
+                               if len(self.where_clause):
+                                       self.where_clause += " AND "
+                               self.where_clause += d.value
+               if len(self.where_clause):
+                       self.where_clause = " AND ( " + self.where_clause + " ) "
+               else:
+                       self.ShowMessage("No selection")
+                       return
+               self.accept()
+
+       def ShowMessage(self, msg):
+               self.status.setText("<font color=#FF0000>" + msg)
+
+       def ClearMessage(self):
+               self.status.setText("")
+
+# Event list
+
+def GetEventList(db):
+       events = []
+       query = QSqlQuery(db)
+       QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
+       while query.next():
+               events.append(query.value(0))
+       return events
+
 # SQL data preparation
 
 def SQLTableDataPrep(query, count):
@@ -1259,7 +1975,7 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
        def FindDone(self, row):
                self.find_bar.Idle()
                if row >= 0:
-                       self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
+                       self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
                else:
                        self.find_bar.NotFound()
 
@@ -1368,6 +2084,149 @@ class WindowMenu():
        def setActiveSubWindow(self, nr):
                self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
 
+# Help text
+
+glb_help_text = """
+<h1>Contents</h1>
+<style>
+p.c1 {
+    text-indent: 40px;
+}
+p.c2 {
+    text-indent: 80px;
+}
+}
+</style>
+<p class=c1><a href=#reports>1. Reports</a></p>
+<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
+<p class=c2><a href=#allbranches>1.2 All branches</a></p>
+<p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
+<p class=c1><a href=#tables>2. Tables</a></p>
+<h1 id=reports>1. Reports</h1>
+<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
+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:
+<pre>
+                                         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
+</pre>
+<h3>Points to note:</h3>
+<ul>
+<li>The top level is a command name (comm)</li>
+<li>The next level is a thread (pid:tid)</li>
+<li>Subsequent levels are functions</li>
+<li>'Count' is the number of calls</li>
+<li>'Time' is the elapsed time until the function returns</li>
+<li>Percentages are relative to the level above</li>
+<li>'Branch Count' is the total number of branches for that function and all functions that it calls
+</ul>
+<h3>Find</h3>
+Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
+The pattern matching symbols are ? for any character and * for zero or more characters.
+<h2 id=allbranches>1.2 All branches</h2>
+The All branches report displays all branches in chronological order.
+Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
+<h3>Disassembly</h3>
+Open a branch to display disassembly. This only works if:
+<ol>
+<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
+<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
+The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
+One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
+or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
+</ol>
+<h4 id=xed>Intel XED Setup</h4>
+To use Intel XED, libxed.so must be present.  To build and install libxed.so:
+<pre>
+git clone https://github.com/intelxed/mbuild.git mbuild
+git clone https://github.com/intelxed/xed
+cd xed
+./mfile.py --share
+sudo ./mfile.py --prefix=/usr/local install
+sudo ldconfig
+</pre>
+<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.
+All columns are searched, but only currently fetched rows are searched.
+<h2 id=selectedbranches>1.3 Selected branches</h2>
+This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
+by various selection criteria. A dialog box displays available criteria which are AND'ed together.
+<h3>1.3.1 Time ranges</h3>
+The time ranges hint text shows the total time range. Relative time ranges can also be entered in
+ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
+<pre>
+       81073085947329-81073085958238   From 81073085947329 to 81073085958238
+       100us-200us             From 100us to 200us
+       10ms-                   From 10ms to the end
+       -100ns                  The first 100ns
+       -10ms-                  The last 10ms
+</pre>
+N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
+<h1 id=tables>2. Tables</h1>
+The Tables menu shows all tables and views in the database. Most tables have an associated view
+which displays the information in a more friendly way. Not all data for large tables is fetched
+immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
+but that can be slow for large tables.
+<p>There are also tables of database meta-information.
+For SQLite3 databases, the sqlite_master table is included.
+For PostgreSQL databases, information_schema.tables/views/columns are included.
+<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.
+All columns are searched, but only currently fetched rows are searched.
+<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
+will go to the next/previous result in id order, instead of display order.
+"""
+
+# Help window
+
+class HelpWindow(QMdiSubWindow):
+
+       def __init__(self, glb, parent=None):
+               super(HelpWindow, self).__init__(parent)
+
+               self.text = QTextBrowser()
+               self.text.setHtml(glb_help_text)
+               self.text.setReadOnly(True)
+               self.text.setOpenExternalLinks(True)
+
+               self.setWidget(self.text)
+
+               AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
+
+# Main window that only displays the help text
+
+class HelpOnlyWindow(QMainWindow):
+
+       def __init__(self, parent=None):
+               super(HelpOnlyWindow, self).__init__(parent)
+
+               self.setMinimumSize(200, 100)
+               self.resize(800, 600)
+               self.setWindowTitle("Exported SQL Viewer Help")
+               self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
+
+               self.text = QTextBrowser()
+               self.text.setHtml(glb_help_text)
+               self.text.setReadOnly(True)
+               self.text.setOpenExternalLinks(True)
+
+               self.setCentralWidget(self.text)
+
 # Font resize
 
 def ResizeFont(widget, diff):
@@ -1448,10 +2307,15 @@ class MainWindow(QMainWindow):
                reports_menu = menu.addMenu("&Reports")
                reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
 
+               self.EventMenu(GetEventList(glb.db), reports_menu)
+
                self.TableMenu(GetTableList(glb), menu)
 
                self.window_menu = WindowMenu(self.mdi_area, menu)
 
+               help_menu = menu.addMenu("&Help")
+               help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
+
        def Find(self):
                win = self.mdi_area.activeSubWindow()
                if win:
@@ -1476,6 +2340,22 @@ class MainWindow(QMainWindow):
                win = self.mdi_area.activeSubWindow()
                EnlargeFont(win.view)
 
+       def EventMenu(self, events, reports_menu):
+               branches_events = 0
+               for event in events:
+                       event = event.split(":")[0]
+                       if event == "branches":
+                               branches_events += 1
+               dbid = 0
+               for event in events:
+                       dbid += 1
+                       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))
+                               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))
+
        def TableMenu(self, tables, menu):
                table_menu = menu.addMenu("&Tables")
                for table in tables:
@@ -1484,9 +2364,126 @@ class MainWindow(QMainWindow):
        def NewCallGraph(self):
                CallGraphWindow(self.glb, self)
 
+       def NewBranchView(self, event_id):
+               BranchWindow(self.glb, event_id, "", "", self)
+
+       def NewSelectedBranchView(self, event_id):
+               dialog = SelectedBranchDialog(self.glb, self)
+               ret = dialog.exec_()
+               if ret:
+                       BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self)
+
        def NewTableView(self, table_name):
                TableWindow(self.glb, table_name, self)
 
+       def Help(self):
+               HelpWindow(self.glb, self)
+
+# XED Disassembler
+
+class xed_state_t(Structure):
+
+       _fields_ = [
+               ("mode", c_int),
+               ("width", c_int)
+       ]
+
+class XEDInstruction():
+
+       def __init__(self, libxed):
+               # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
+               xedd_t = c_byte * 512
+               self.xedd = xedd_t()
+               self.xedp = addressof(self.xedd)
+               libxed.xed_decoded_inst_zero(self.xedp)
+               self.state = xed_state_t()
+               self.statep = addressof(self.state)
+               # Buffer for disassembled instruction text
+               self.buffer = create_string_buffer(256)
+               self.bufferp = addressof(self.buffer)
+
+class LibXED():
+
+       def __init__(self):
+               try:
+                       self.libxed = CDLL("libxed.so")
+               except:
+                       self.libxed = None
+               if not self.libxed:
+                       self.libxed = CDLL("/usr/local/lib/libxed.so")
+
+               self.xed_tables_init = self.libxed.xed_tables_init
+               self.xed_tables_init.restype = None
+               self.xed_tables_init.argtypes = []
+
+               self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
+               self.xed_decoded_inst_zero.restype = None
+               self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
+
+               self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
+               self.xed_operand_values_set_mode.restype = None
+               self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
+
+               self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
+               self.xed_decoded_inst_zero_keep_mode.restype = None
+               self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
+
+               self.xed_decode = self.libxed.xed_decode
+               self.xed_decode.restype = c_int
+               self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
+
+               self.xed_format_context = self.libxed.xed_format_context
+               self.xed_format_context.restype = c_uint
+               self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
+
+               self.xed_tables_init()
+
+       def Instruction(self):
+               return XEDInstruction(self)
+
+       def SetMode(self, inst, mode):
+               if mode:
+                       inst.state.mode = 4 # 32-bit
+                       inst.state.width = 4 # 4 bytes
+               else:
+                       inst.state.mode = 1 # 64-bit
+                       inst.state.width = 8 # 8 bytes
+               self.xed_operand_values_set_mode(inst.xedp, inst.statep)
+
+       def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
+               self.xed_decoded_inst_zero_keep_mode(inst.xedp)
+               err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
+               if err:
+                       return 0, ""
+               # Use AT&T mode (2), alternative is Intel (3)
+               ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
+               if not ok:
+                       return 0, ""
+               # Return instruction length and the disassembled instruction text
+               # For now, assume the length is in byte 166
+               return inst.xedd[166], inst.buffer.value
+
+def TryOpen(file_name):
+       try:
+               return open(file_name, "rb")
+       except:
+               return None
+
+def Is64Bit(f):
+       result = sizeof(c_void_p)
+       # ELF support only
+       pos = f.tell()
+       f.seek(0)
+       header = f.read(7)
+       f.seek(pos)
+       magic = header[0:4]
+       eclass = ord(header[4])
+       encoding = ord(header[5])
+       version = ord(header[6])
+       if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
+               result = True if eclass == 2 else False
+       return result
+
 # Global data
 
 class Glb():
@@ -1495,9 +2492,40 @@ class Glb():
                self.dbref = dbref
                self.db = db
                self.dbname = dbname
+               self.home_dir = os.path.expanduser("~")
+               self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
+               if self.buildid_dir:
+                       self.buildid_dir += "/.build-id/"
+               else:
+                       self.buildid_dir = self.home_dir + "/.debug/.build-id/"
                self.app = None
                self.mainwindow = None
                self.instances_to_shutdown_on_exit = weakref.WeakSet()
+               try:
+                       self.disassembler = LibXED()
+                       self.have_disassembler = True
+               except:
+                       self.have_disassembler = False
+
+       def FileFromBuildId(self, build_id):
+               file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
+               return TryOpen(file_name)
+
+       def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
+               # Assume current machine i.e. no support for virtualization
+               if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
+                       file_name = os.getenv("PERF_KCORE")
+                       f = TryOpen(file_name) if file_name else None
+                       if f:
+                               return f
+                       # For now, no special handling if long_name is /proc/kcore
+                       f = TryOpen(long_name)
+                       if f:
+                               return f
+               f = self.FileFromBuildId(build_id)
+               if f:
+                       return f
+               return None
 
        def AddInstanceToShutdownOnExit(self, instance):
                self.instances_to_shutdown_on_exit.add(instance)
@@ -1550,10 +2578,16 @@ class DBRef():
 
 def Main():
        if (len(sys.argv) < 2):
-               print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
+               print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
                raise Exception("Too few arguments")
 
        dbname = sys.argv[1]
+       if dbname == "--help-only":
+               app = QApplication(sys.argv)
+               mainwindow = HelpOnlyWindow()
+               mainwindow.show()
+               err = app.exec_()
+               sys.exit(err)
 
        is_sqlite3 = False
        try:
This page took 0.071518 seconds and 4 git commands to generate.