All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements
@ 2019-05-03 12:08 Adrian Hunter
  2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
                   ` (5 more replies)
  0 siblings, 6 replies; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

Hi

Here are a some minor improvememnts to exported-sql-viewer.py
The patches go on top of the pyside2 support patches, here:

	https://lore.kernel.org/lkml/20190412113830.4126-1-adrian.hunter@intel.com


Adrian Hunter (6):
      perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font
      perf scripts python: exported-sql-viewer.py: Move view creation
      perf scripts python: exported-sql-viewer.py: Add tree level
      perf scripts python: exported-sql-viewer.py: Add copy to clipboard
      perf scripts python: exported-sql-viewer.py: Add context menu
      perf scripts python: exported-sql-viewer.py: Add 'About' dialog box

 tools/perf/scripts/python/exported-sql-viewer.py | 340 ++++++++++++++++++++++-
 1 file changed, 333 insertions(+), 7 deletions(-)


Regards
Adrian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-13 19:57   ` Arnaldo Carvalho de Melo
  2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
  2019-05-03 12:08 ` [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation Adrian Hunter
                   ` (4 subsequent siblings)
  5 siblings, 2 replies; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

Fix the following error if shrink / enlarge font is used with the help
window.

  Traceback (most recent call last):
    File "tools/perf/scripts/python/exported-sql-viewer.py", line 2791, in ShrinkFont
      ShrinkFont(win.view)
  AttributeError: 'HelpWindow' object has no attribute 'view'

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index c586abfb2b46..289e8dbd1444 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -2770,6 +2770,14 @@ class MainWindow(QMainWindow):
 		help_menu = menu.addMenu("&Help")
 		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
 
+	def Try(self, fn):
+		win = self.mdi_area.activeSubWindow()
+		if win:
+			try:
+				fn(win.view)
+			except:
+				pass
+
 	def Find(self):
 		win = self.mdi_area.activeSubWindow()
 		if win:
@@ -2787,12 +2795,10 @@ class MainWindow(QMainWindow):
 				pass
 
 	def ShrinkFont(self):
-		win = self.mdi_area.activeSubWindow()
-		ShrinkFont(win.view)
+		self.Try(ShrinkFont)
 
 	def EnlargeFont(self):
-		win = self.mdi_area.activeSubWindow()
-		EnlargeFont(win.view)
+		self.Try(EnlargeFont)
 
 	def EventMenu(self, events, reports_menu):
 		branches_events = 0
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
  2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-18  8:54   ` [tip:perf/core] " tip-bot for Adrian Hunter
  2019-05-03 12:08 ` [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level Adrian Hunter
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

As preparation for adding support for copying to clipboard, create view in
TreeWindowBase instead of derived classes.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 289e8dbd1444..73fc02d6d1e4 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -891,9 +891,10 @@ class TreeWindowBase(QMdiSubWindow):
 		super(TreeWindowBase, self).__init__(parent)
 
 		self.model = None
-		self.view = None
 		self.find_bar = None
 
+		self.view = QTreeView()
+
 	def DisplayFound(self, ids):
 		if not len(ids):
 			return False
@@ -935,7 +936,6 @@ class CallGraphWindow(TreeWindowBase):
 
 		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
 
-		self.view = QTreeView()
 		self.view.setModel(self.model)
 
 		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
@@ -958,7 +958,6 @@ class CallTreeWindow(TreeWindowBase):
 
 		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
 
-		self.view = QTreeView()
 		self.view.setModel(self.model)
 
 		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
  2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
  2019-05-03 12:08 ` [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
  2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

As preparation for adding support for copying to clipboard, keep track of
what level each item is in tree items.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 73fc02d6d1e4..c0fb88d440ba 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -470,6 +470,10 @@ class CallGraphLevelItemBase(object):
 		self.query_done = False;
 		self.child_count = 0
 		self.child_items = []
+		if parent_item:
+			self.level = parent_item.level + 1
+		else:
+			self.level = 0
 
 	def getChildItem(self, row):
 		return self.child_items[row]
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
                   ` (2 preceding siblings ...)
  2019-05-03 12:08 ` [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-13 20:03   ` Arnaldo Carvalho de Melo
  2019-05-18  8:56   ` [tip:perf/core] " tip-bot for Adrian Hunter
  2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
  2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
  5 siblings, 2 replies; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

Add support for copying to clipboard. Two menu options are added to copy the
selected rows / columns with normal spacing, or as comma-separated-values.
In the case of trees, only entire rows can be copied.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 .../scripts/python/exported-sql-viewer.py     | 217 ++++++++++++++++++
 1 file changed, 217 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index c0fb88d440ba..5804d9705ab7 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -898,6 +898,8 @@ class TreeWindowBase(QMdiSubWindow):
 		self.find_bar = None
 
 		self.view = QTreeView()
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 
 	def DisplayFound(self, ids):
 		if not len(ids):
@@ -1666,6 +1668,8 @@ class BranchWindow(QMdiSubWindow):
 
 		self.view = QTreeView()
 		self.view.setUniformRowHeights(True)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 		self.view.setModel(self.model)
 
 		self.ResizeColumnsToContents()
@@ -2278,6 +2282,207 @@ class ResizeColumnsToContentsBase(QObject):
 		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
 		self.ResizeColumnsToContents()
 
+# Convert value to CSV
+
+def ToCSValue(val):
+	if '"' in val:
+		val = val.replace('"', '""')
+	if "," in val or '"' in val:
+		val = '"' + val + '"'
+	return val
+
+# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
+
+glb_max_cols = 1000
+
+def RowColumnKey(a):
+	return a.row() * glb_max_cols + a.column()
+
+# Copy selected table cells to clipboard
+
+def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
+	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
+	idx_cnt = len(indexes)
+	if not idx_cnt:
+		return
+	if idx_cnt == 1:
+		with_hdr=False
+	min_row = indexes[0].row()
+	max_row = indexes[0].row()
+	min_col = indexes[0].column()
+	max_col = indexes[0].column()
+	for i in indexes:
+		min_row = min(min_row, i.row())
+		max_row = max(max_row, i.row())
+		min_col = min(min_col, i.column())
+		max_col = max(max_col, i.column())
+	if max_col > glb_max_cols:
+		raise RuntimeError("glb_max_cols is too low")
+	max_width = [0] * (1 + max_col - min_col)
+	for i in indexes:
+		c = i.column() - min_col
+		max_width[c] = max(max_width[c], len(str(i.data())))
+	text = ""
+	pad = ""
+	sep = ""
+	if with_hdr:
+		model = indexes[0].model()
+		for col in range(min_col, max_col + 1):
+			val = model.headerData(col, Qt.Horizontal)
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				c = col - min_col
+				max_width[c] = max(max_width[c], len(val))
+				width = max_width[c]
+				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
+				if align & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "  "
+		text += "\n"
+		pad = ""
+		sep = ""
+	last_row = min_row
+	for i in indexes:
+		if i.row() > last_row:
+			last_row = i.row()
+			text += "\n"
+			pad = ""
+			sep = ""
+		if as_csv:
+			text += sep + ToCSValue(str(i.data()))
+			sep = ","
+		else:
+			width = max_width[i.column() - min_col]
+			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+				val = str(i.data()).rjust(width)
+			else:
+				val = str(i.data())
+			text += pad + sep + val
+			pad = " " * (width - len(val))
+			sep = "  "
+	QApplication.clipboard().setText(text)
+
+def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
+	indexes = view.selectedIndexes()
+	if not len(indexes):
+		return
+
+	selection = view.selectionModel()
+
+	first = None
+	for i in indexes:
+		above = view.indexAbove(i)
+		if not selection.isSelected(above):
+			first = i
+			break
+
+	if first is None:
+		raise RuntimeError("CopyTreeCellsToClipboard internal error")
+
+	model = first.model()
+	row_cnt = 0
+	col_cnt = model.columnCount(first)
+	max_width = [0] * col_cnt
+
+	indent_sz = 2
+	indent_str = " " * indent_sz
+
+	expanded_mark_sz = 2
+	if sys.version_info[0] == 3:
+		expanded_mark = "\u25BC "
+		not_expanded_mark = "\u25B6 "
+	else:
+		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
+		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
+	leaf_mark = "  "
+
+	if not as_csv:
+		pos = first
+		while True:
+			row_cnt += 1
+			row = pos.row()
+			for c in range(col_cnt):
+				i = pos.sibling(row, c)
+				if c:
+					n = len(str(i.data()))
+				else:
+					n = len(str(i.data()).strip())
+					n += (i.internalPointer().level - 1) * indent_sz
+					n += expanded_mark_sz
+				max_width[c] = max(max_width[c], n)
+			pos = view.indexBelow(pos)
+			if not selection.isSelected(pos):
+				break
+
+	text = ""
+	pad = ""
+	sep = ""
+	if with_hdr:
+		for c in range(col_cnt):
+			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				max_width[c] = max(max_width[c], len(val))
+				width = max_width[c]
+				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
+				if align & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "   "
+		text += "\n"
+		pad = ""
+		sep = ""
+
+	pos = first
+	while True:
+		row = pos.row()
+		for c in range(col_cnt):
+			i = pos.sibling(row, c)
+			val = str(i.data())
+			if not c:
+				if model.hasChildren(i):
+					if view.isExpanded(i):
+						mark = expanded_mark
+					else:
+						mark = not_expanded_mark
+				else:
+					mark = leaf_mark
+				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				width = max_width[c]
+				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "   "
+		pos = view.indexBelow(pos)
+		if not selection.isSelected(pos):
+			break
+		text = text.rstrip() + "\n"
+		pad = ""
+		sep = ""
+
+	QApplication.clipboard().setText(text)
+
+def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
+	view.CopyCellsToClipboard(view, as_csv, with_hdr)
+
+def CopyCellsToClipboardHdr(view):
+	CopyCellsToClipboard(view, False, True)
+
+def CopyCellsToClipboardCSV(view):
+	CopyCellsToClipboard(view, True, True)
+
 # Table window
 
 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
@@ -2296,6 +2501,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.verticalHeader().setVisible(False)
 		self.view.sortByColumn(-1, Qt.AscendingOrder)
 		self.view.setSortingEnabled(True)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
 		self.ResizeColumnsToContents()
 
@@ -2412,6 +2619,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.setModel(self.model)
 		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
 		self.view.verticalHeader().setVisible(False)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
 		self.ResizeColumnsToContents()
 
@@ -2749,6 +2958,8 @@ class MainWindow(QMainWindow):
 		file_menu.addAction(CreateExitAction(glb.app, self))
 
 		edit_menu = menu.addMenu("&Edit")
+		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
+		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
 		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
 		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
 		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
@@ -2781,6 +2992,12 @@ class MainWindow(QMainWindow):
 			except:
 				pass
 
+	def CopyToClipboard(self):
+		self.Try(CopyCellsToClipboardHdr)
+
+	def CopyToClipboardCSV(self):
+		self.Try(CopyCellsToClipboardCSV)
+
 	def Find(self):
 		win = self.mdi_area.activeSubWindow()
 		if win:
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
                   ` (3 preceding siblings ...)
  2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-13 20:09   ` Arnaldo Carvalho de Melo
  2019-05-18  8:57   ` [tip:perf/core] " tip-bot for Adrian Hunter
  2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
  5 siblings, 2 replies; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

Add a context menu (right-click) that provides options for copying to
clipboard, including, for trees, the ability to copy only the cell under
the mouse pointer.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 .../scripts/python/exported-sql-viewer.py     | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 5804d9705ab7..421f3828ea43 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -901,6 +901,8 @@ class TreeWindowBase(QMdiSubWindow):
 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
 		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 
+		self.context_menu = TreeContextMenu(self.view)
+
 	def DisplayFound(self, ids):
 		if not len(ids):
 			return False
@@ -1674,6 +1676,8 @@ class BranchWindow(QMdiSubWindow):
 
 		self.ResizeColumnsToContents()
 
+		self.context_menu = TreeContextMenu(self.view)
+
 		self.find_bar = FindBar(self, self, True)
 
 		self.finder = ChildDataItemFinder(self.model.root)
@@ -2483,6 +2487,39 @@ def CopyCellsToClipboardHdr(view):
 def CopyCellsToClipboardCSV(view):
 	CopyCellsToClipboard(view, True, True)
 
+# Context menu
+
+class ContextMenu(object):
+
+	def __init__(self, view):
+		self.view = view
+		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
+
+	def ShowContextMenu(self, pos):
+		menu = QMenu(self.view)
+		self.AddActions(menu)
+		menu.exec_(self.view.mapToGlobal(pos))
+
+	def AddCopy(self, menu):
+		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
+		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
+
+	def AddActions(self, menu):
+		self.AddCopy(menu)
+
+class TreeContextMenu(ContextMenu):
+
+	def __init__(self, view):
+		super(TreeContextMenu, self).__init__(view)
+
+	def AddActions(self, menu):
+		i = self.view.currentIndex()
+		text = str(i.data()).strip()
+		if len(text):
+			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
+		self.AddCopy(menu)
+
 # Table window
 
 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
@@ -2506,6 +2543,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 
 		self.ResizeColumnsToContents()
 
+		self.context_menu = ContextMenu(self.view)
+
 		self.find_bar = FindBar(self, self, True)
 
 		self.finder = ChildDataItemFinder(self.data_model)
@@ -2622,6 +2661,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
 		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
+		self.context_menu = ContextMenu(self.view)
+
 		self.ResizeColumnsToContents()
 
 		self.find_bar = FindBar(self, self, True)
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box
  2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
                   ` (4 preceding siblings ...)
  2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
@ 2019-05-03 12:08 ` Adrian Hunter
  2019-05-13 20:12   ` Arnaldo Carvalho de Melo
  2019-05-18  8:58   ` [tip:perf/core] " tip-bot for Adrian Hunter
  5 siblings, 2 replies; 17+ messages in thread
From: Adrian Hunter @ 2019-05-03 12:08 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: Jiri Olsa, linux-kernel

With support for Python 2 or 3 and PySide 1 or 2 (Qt 4 or 5), it is useful
to see what versions are in use. Add an 'About' dialog box that displays
Python, PySide, Qt and database server (SQLite or PostgreSQL) version
numbers.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
 .../scripts/python/exported-sql-viewer.py     | 59 +++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 421f3828ea43..6fe553258ce5 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -2927,6 +2927,60 @@ class HelpOnlyWindow(QMainWindow):
 
 		self.setCentralWidget(self.text)
 
+# PostqreSQL server version
+
+def PostqreSQLServerVersion(db):
+	query = QSqlQuery(db)
+	QueryExec(query, "SELECT VERSION()")
+	if query.next():
+		v_str = query.value(0)
+		v_list = v_str.strip().split(" ")
+		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
+			return v_list[1]
+		return v_str
+	return "Unknown"
+
+# SQLite version
+
+def SQLiteVersion(db):
+	query = QSqlQuery(db)
+	QueryExec(query, "SELECT sqlite_version()")
+	if query.next():
+		return query.value(0)
+	return "Unknown"
+
+# About dialog
+
+class AboutDialog(QDialog):
+
+	def __init__(self, glb, parent=None):
+		super(AboutDialog, self).__init__(parent)
+
+		self.setWindowTitle("About Exported SQL Viewer")
+		self.setMinimumWidth(300)
+
+		pyside_version = "1" if pyside_version_1 else "2"
+
+		text = "<pre>"
+		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
+		text += "PySide version:     " + pyside_version + "\n"
+		text += "Qt version:         " + qVersion() + "\n"
+		if glb.dbref.is_sqlite3:
+			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
+		else:
+			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
+		text += "</pre>"
+
+		self.text = QTextBrowser()
+		self.text.setHtml(text)
+		self.text.setReadOnly(True)
+		self.text.setOpenExternalLinks(True)
+
+		self.vbox = QVBoxLayout()
+		self.vbox.addWidget(self.text)
+
+		self.setLayout(self.vbox);
+
 # Font resize
 
 def ResizeFont(widget, diff):
@@ -3024,6 +3078,7 @@ class MainWindow(QMainWindow):
 
 		help_menu = menu.addMenu("&Help")
 		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
+		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
 
 	def Try(self, fn):
 		win = self.mdi_area.activeSubWindow()
@@ -3109,6 +3164,10 @@ class MainWindow(QMainWindow):
 	def Help(self):
 		HelpWindow(self.glb, self)
 
+	def About(self):
+		dialog = AboutDialog(self.glb, self)
+		dialog.exec_()
+
 # XED Disassembler
 
 class xed_state_t(Structure):
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font
  2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
@ 2019-05-13 19:57   ` Arnaldo Carvalho de Melo
  2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2019-05-13 19:57 UTC (permalink / raw)
  To: Adrian Hunter; +Cc: Jiri Olsa, linux-kernel

Em Fri, May 03, 2019 at 03:08:23PM +0300, Adrian Hunter escreveu:
> Fix the following error if shrink / enlarge font is used with the help
> window.
> 
>   Traceback (most recent call last):
>     File "tools/perf/scripts/python/exported-sql-viewer.py", line 2791, in ShrinkFont
>       ShrinkFont(win.view)
>   AttributeError: 'HelpWindow' object has no attribute 'view'

Humm, this is kinda frustrating, I tried it and expected it to either
enlarge/shrink or tell me that that is not possible, and why :-\

Anyway, if you think this is the best approach, applied.

- Arnaldo
 
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
>  tools/perf/scripts/python/exported-sql-viewer.py | 14 ++++++++++----
>  1 file changed, 10 insertions(+), 4 deletions(-)
> 
> diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
> index c586abfb2b46..289e8dbd1444 100755
> --- a/tools/perf/scripts/python/exported-sql-viewer.py
> +++ b/tools/perf/scripts/python/exported-sql-viewer.py
> @@ -2770,6 +2770,14 @@ class MainWindow(QMainWindow):
>  		help_menu = menu.addMenu("&Help")
>  		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
>  
> +	def Try(self, fn):
> +		win = self.mdi_area.activeSubWindow()
> +		if win:
> +			try:
> +				fn(win.view)
> +			except:
> +				pass
> +
>  	def Find(self):
>  		win = self.mdi_area.activeSubWindow()
>  		if win:
> @@ -2787,12 +2795,10 @@ class MainWindow(QMainWindow):
>  				pass
>  
>  	def ShrinkFont(self):
> -		win = self.mdi_area.activeSubWindow()
> -		ShrinkFont(win.view)
> +		self.Try(ShrinkFont)
>  
>  	def EnlargeFont(self):
> -		win = self.mdi_area.activeSubWindow()
> -		EnlargeFont(win.view)
> +		self.Try(EnlargeFont)
>  
>  	def EventMenu(self, events, reports_menu):
>  		branches_events = 0
> -- 
> 2.17.1

-- 

- Arnaldo

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard
  2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
@ 2019-05-13 20:03   ` Arnaldo Carvalho de Melo
  2019-05-18  8:56   ` [tip:perf/core] " tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2019-05-13 20:03 UTC (permalink / raw)
  To: Adrian Hunter; +Cc: Jiri Olsa, linux-kernel, linux-perf-users

Em Fri, May 03, 2019 at 03:08:26PM +0300, Adrian Hunter escreveu:
> Add support for copying to clipboard. Two menu options are added to copy the
> selected rows / columns with normal spacing, or as comma-separated-values.
> In the case of trees, only entire rows can be copied.

Cool, it works:

    Comitter testing:

      $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

    Select the lines, press control+C and on the same terminal,
    press control+shift+V and voilà:

    Call Path                           Object           Count  Time (ns)  Time (%)  Branch Count  Branch Count (%)
      ▼ 14503:14503
        ▼ _start                        ld-2.28.so           1     156267     100.0         10602             100.0
            unknown                     unknown              1       2276       1.5             1               0.0
          ▼ _dl_start                   ld-2.28.so           1     137047      87.7         10088              95.2
            ▶ unknown                   unknown              4       4127       3.0             4               0.0
              _dl_setup_hash            ld-2.28.so           1          0       0.0             1               0.0
            ▶ _dl_sysdep_start          ld-2.28.so           1     131342      95.8          9981              98.9
          ▼ _dl_init                    ld-2.28.so           1       9142       5.9           326               3.1
            ▼ call_init.part.0          ld-2.28.so           3       9133      99.9           319              97.9
              ▶ _init                   libc-2.28.so         1       6877      75.3           110              34.5
              ▶ check_stdfiles_vtables  libc-2.28.so         1         76       0.8             2               0.6
              ▶ init_cacheinfo          libc-2.28.so         1       1991      21.8           197              61.8
          ▶ _start                      simple-retpoline     1       7457       4.8           182               1.7

 
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
>  .../scripts/python/exported-sql-viewer.py     | 217 ++++++++++++++++++
>  1 file changed, 217 insertions(+)
> 
> diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
> index c0fb88d440ba..5804d9705ab7 100755
> --- a/tools/perf/scripts/python/exported-sql-viewer.py
> +++ b/tools/perf/scripts/python/exported-sql-viewer.py
> @@ -898,6 +898,8 @@ class TreeWindowBase(QMdiSubWindow):
>  		self.find_bar = None
>  
>  		self.view = QTreeView()
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
>  
>  	def DisplayFound(self, ids):
>  		if not len(ids):
> @@ -1666,6 +1668,8 @@ class BranchWindow(QMdiSubWindow):
>  
>  		self.view = QTreeView()
>  		self.view.setUniformRowHeights(True)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
>  		self.view.setModel(self.model)
>  
>  		self.ResizeColumnsToContents()
> @@ -2278,6 +2282,207 @@ class ResizeColumnsToContentsBase(QObject):
>  		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
>  		self.ResizeColumnsToContents()
>  
> +# Convert value to CSV
> +
> +def ToCSValue(val):
> +	if '"' in val:
> +		val = val.replace('"', '""')
> +	if "," in val or '"' in val:
> +		val = '"' + val + '"'
> +	return val
> +
> +# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
> +
> +glb_max_cols = 1000
> +
> +def RowColumnKey(a):
> +	return a.row() * glb_max_cols + a.column()
> +
> +# Copy selected table cells to clipboard
> +
> +def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
> +	idx_cnt = len(indexes)
> +	if not idx_cnt:
> +		return
> +	if idx_cnt == 1:
> +		with_hdr=False
> +	min_row = indexes[0].row()
> +	max_row = indexes[0].row()
> +	min_col = indexes[0].column()
> +	max_col = indexes[0].column()
> +	for i in indexes:
> +		min_row = min(min_row, i.row())
> +		max_row = max(max_row, i.row())
> +		min_col = min(min_col, i.column())
> +		max_col = max(max_col, i.column())
> +	if max_col > glb_max_cols:
> +		raise RuntimeError("glb_max_cols is too low")
> +	max_width = [0] * (1 + max_col - min_col)
> +	for i in indexes:
> +		c = i.column() - min_col
> +		max_width[c] = max(max_width[c], len(str(i.data())))
> +	text = ""
> +	pad = ""
> +	sep = ""
> +	if with_hdr:
> +		model = indexes[0].model()
> +		for col in range(min_col, max_col + 1):
> +			val = model.headerData(col, Qt.Horizontal)
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				c = col - min_col
> +				max_width[c] = max(max_width[c], len(val))
> +				width = max_width[c]
> +				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
> +				if align & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "  "
> +		text += "\n"
> +		pad = ""
> +		sep = ""
> +	last_row = min_row
> +	for i in indexes:
> +		if i.row() > last_row:
> +			last_row = i.row()
> +			text += "\n"
> +			pad = ""
> +			sep = ""
> +		if as_csv:
> +			text += sep + ToCSValue(str(i.data()))
> +			sep = ","
> +		else:
> +			width = max_width[i.column() - min_col]
> +			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
> +				val = str(i.data()).rjust(width)
> +			else:
> +				val = str(i.data())
> +			text += pad + sep + val
> +			pad = " " * (width - len(val))
> +			sep = "  "
> +	QApplication.clipboard().setText(text)
> +
> +def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	indexes = view.selectedIndexes()
> +	if not len(indexes):
> +		return
> +
> +	selection = view.selectionModel()
> +
> +	first = None
> +	for i in indexes:
> +		above = view.indexAbove(i)
> +		if not selection.isSelected(above):
> +			first = i
> +			break
> +
> +	if first is None:
> +		raise RuntimeError("CopyTreeCellsToClipboard internal error")
> +
> +	model = first.model()
> +	row_cnt = 0
> +	col_cnt = model.columnCount(first)
> +	max_width = [0] * col_cnt
> +
> +	indent_sz = 2
> +	indent_str = " " * indent_sz
> +
> +	expanded_mark_sz = 2
> +	if sys.version_info[0] == 3:
> +		expanded_mark = "\u25BC "
> +		not_expanded_mark = "\u25B6 "
> +	else:
> +		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
> +		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
> +	leaf_mark = "  "
> +
> +	if not as_csv:
> +		pos = first
> +		while True:
> +			row_cnt += 1
> +			row = pos.row()
> +			for c in range(col_cnt):
> +				i = pos.sibling(row, c)
> +				if c:
> +					n = len(str(i.data()))
> +				else:
> +					n = len(str(i.data()).strip())
> +					n += (i.internalPointer().level - 1) * indent_sz
> +					n += expanded_mark_sz
> +				max_width[c] = max(max_width[c], n)
> +			pos = view.indexBelow(pos)
> +			if not selection.isSelected(pos):
> +				break
> +
> +	text = ""
> +	pad = ""
> +	sep = ""
> +	if with_hdr:
> +		for c in range(col_cnt):
> +			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				max_width[c] = max(max_width[c], len(val))
> +				width = max_width[c]
> +				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
> +				if align & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "   "
> +		text += "\n"
> +		pad = ""
> +		sep = ""
> +
> +	pos = first
> +	while True:
> +		row = pos.row()
> +		for c in range(col_cnt):
> +			i = pos.sibling(row, c)
> +			val = str(i.data())
> +			if not c:
> +				if model.hasChildren(i):
> +					if view.isExpanded(i):
> +						mark = expanded_mark
> +					else:
> +						mark = not_expanded_mark
> +				else:
> +					mark = leaf_mark
> +				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				width = max_width[c]
> +				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "   "
> +		pos = view.indexBelow(pos)
> +		if not selection.isSelected(pos):
> +			break
> +		text = text.rstrip() + "\n"
> +		pad = ""
> +		sep = ""
> +
> +	QApplication.clipboard().setText(text)
> +
> +def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	view.CopyCellsToClipboard(view, as_csv, with_hdr)
> +
> +def CopyCellsToClipboardHdr(view):
> +	CopyCellsToClipboard(view, False, True)
> +
> +def CopyCellsToClipboardCSV(view):
> +	CopyCellsToClipboard(view, True, True)
> +
>  # Table window
>  
>  class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
> @@ -2296,6 +2501,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  		self.view.verticalHeader().setVisible(False)
>  		self.view.sortByColumn(-1, Qt.AscendingOrder)
>  		self.view.setSortingEnabled(True)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
>  
>  		self.ResizeColumnsToContents()
>  
> @@ -2412,6 +2619,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  		self.view.setModel(self.model)
>  		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
>  		self.view.verticalHeader().setVisible(False)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
>  
>  		self.ResizeColumnsToContents()
>  
> @@ -2749,6 +2958,8 @@ class MainWindow(QMainWindow):
>  		file_menu.addAction(CreateExitAction(glb.app, self))
>  
>  		edit_menu = menu.addMenu("&Edit")
> +		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
> +		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
>  		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
>  		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
>  		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
> @@ -2781,6 +2992,12 @@ class MainWindow(QMainWindow):
>  			except:
>  				pass
>  
> +	def CopyToClipboard(self):
> +		self.Try(CopyCellsToClipboardHdr)
> +
> +	def CopyToClipboardCSV(self):
> +		self.Try(CopyCellsToClipboardCSV)
> +
>  	def Find(self):
>  		win = self.mdi_area.activeSubWindow()
>  		if win:
> -- 
> 2.17.1

-- 

- Arnaldo

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu
  2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
@ 2019-05-13 20:09   ` Arnaldo Carvalho de Melo
  2019-05-18  8:57   ` [tip:perf/core] " tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2019-05-13 20:09 UTC (permalink / raw)
  To: Adrian Hunter; +Cc: Jiri Olsa, linux-kernel

Em Fri, May 03, 2019 at 03:08:27PM +0300, Adrian Hunter escreveu:
> Add a context menu (right-click) that provides options for copying to
> clipboard, including, for trees, the ability to copy only the cell under
> the mouse pointer.

Works as well:

    Committer testing:

      $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

      Simply right click and pick "Copy selection", that at this point has
      just the first line, not expanded, then see what was copied by pressing
      shift+control+v on a terminal:

    Call Path,Object,Count,Time (ns),Time (%),Branch Count,Branch Count (%)
    ▶ simple-retpolin,,,,,,

      Ditto after expanding, i.e. the selection continues to be just one
      line:

    Call Path           Object   Count   Time (ns)   Time (%)   Branch Count   Branch Count (%)
    ▼ simple-retpolin

       Now select all the lines with the mouse and control+shift+v again:

    Call Path                     Object             Count   Time (ns)   Time (%)   Branch Count   Branch Count (%)
      ▼ 14503:14503
        ▼ _start                  ld-2.28.so             1      156267      100.0          10602              100.0
          ▶ unknown               unknown                1        2276        1.5              1                0.0
          ▶ _dl_start             ld-2.28.so             1      137047       87.7          10088               95.2
          ▶ _dl_init              ld-2.28.so             1        9142        5.9            326                3.1
          ▼ _start                simple-retpoline       1        7457        4.8            182                1.7
            ▶ unknown             unknown                1         805       10.8              1                0.5
            ▶ __libc_start_main   libc-2.28.so           1        6347       85.1            179               98.4
 
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
>  .../scripts/python/exported-sql-viewer.py     | 41 +++++++++++++++++++
>  1 file changed, 41 insertions(+)
> 
> diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
> index 5804d9705ab7..421f3828ea43 100755
> --- a/tools/perf/scripts/python/exported-sql-viewer.py
> +++ b/tools/perf/scripts/python/exported-sql-viewer.py
> @@ -901,6 +901,8 @@ class TreeWindowBase(QMdiSubWindow):
>  		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
>  		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
>  
> +		self.context_menu = TreeContextMenu(self.view)
> +
>  	def DisplayFound(self, ids):
>  		if not len(ids):
>  			return False
> @@ -1674,6 +1676,8 @@ class BranchWindow(QMdiSubWindow):
>  
>  		self.ResizeColumnsToContents()
>  
> +		self.context_menu = TreeContextMenu(self.view)
> +
>  		self.find_bar = FindBar(self, self, True)
>  
>  		self.finder = ChildDataItemFinder(self.model.root)
> @@ -2483,6 +2487,39 @@ def CopyCellsToClipboardHdr(view):
>  def CopyCellsToClipboardCSV(view):
>  	CopyCellsToClipboard(view, True, True)
>  
> +# Context menu
> +
> +class ContextMenu(object):
> +
> +	def __init__(self, view):
> +		self.view = view
> +		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
> +		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
> +
> +	def ShowContextMenu(self, pos):
> +		menu = QMenu(self.view)
> +		self.AddActions(menu)
> +		menu.exec_(self.view.mapToGlobal(pos))
> +
> +	def AddCopy(self, menu):
> +		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
> +		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
> +
> +	def AddActions(self, menu):
> +		self.AddCopy(menu)
> +
> +class TreeContextMenu(ContextMenu):
> +
> +	def __init__(self, view):
> +		super(TreeContextMenu, self).__init__(view)
> +
> +	def AddActions(self, menu):
> +		i = self.view.currentIndex()
> +		text = str(i.data()).strip()
> +		if len(text):
> +			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
> +		self.AddCopy(menu)
> +
>  # Table window
>  
>  class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
> @@ -2506,6 +2543,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  
>  		self.ResizeColumnsToContents()
>  
> +		self.context_menu = ContextMenu(self.view)
> +
>  		self.find_bar = FindBar(self, self, True)
>  
>  		self.finder = ChildDataItemFinder(self.data_model)
> @@ -2622,6 +2661,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
>  		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
>  
> +		self.context_menu = ContextMenu(self.view)
> +
>  		self.ResizeColumnsToContents()
>  
>  		self.find_bar = FindBar(self, self, True)
> -- 
> 2.17.1

-- 

- Arnaldo

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box
  2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
@ 2019-05-13 20:12   ` Arnaldo Carvalho de Melo
  2019-05-18  8:58   ` [tip:perf/core] " tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: Arnaldo Carvalho de Melo @ 2019-05-13 20:12 UTC (permalink / raw)
  To: Adrian Hunter; +Cc: Jiri Olsa, linux-kernel

Em Fri, May 03, 2019 at 03:08:28PM +0300, Adrian Hunter escreveu:
> With support for Python 2 or 3 and PySide 1 or 2 (Qt 4 or 5), it is useful
> to see what versions are in use. Add an 'About' dialog box that displays
> Python, PySide, Qt and database server (SQLite or PostgreSQL) version
> numbers.

Works as well, here:

Committer testing:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

  Then go to 'Help', then 'About', select all the lines with the mouse
  press 'Control+C', then, on the same terminal press control+shift+V
  which shows my current environment:

Python version:     2.7.16
PySide version:     1
Qt version:         4.8.7
SQLite version:     3.26.0


-------------------

Patchkit applied, thanks.

- Arnaldo
 
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
>  .../scripts/python/exported-sql-viewer.py     | 59 +++++++++++++++++++
>  1 file changed, 59 insertions(+)
> 
> diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
> index 421f3828ea43..6fe553258ce5 100755
> --- a/tools/perf/scripts/python/exported-sql-viewer.py
> +++ b/tools/perf/scripts/python/exported-sql-viewer.py
> @@ -2927,6 +2927,60 @@ class HelpOnlyWindow(QMainWindow):
>  
>  		self.setCentralWidget(self.text)
>  
> +# PostqreSQL server version
> +
> +def PostqreSQLServerVersion(db):
> +	query = QSqlQuery(db)
> +	QueryExec(query, "SELECT VERSION()")
> +	if query.next():
> +		v_str = query.value(0)
> +		v_list = v_str.strip().split(" ")
> +		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
> +			return v_list[1]
> +		return v_str
> +	return "Unknown"
> +
> +# SQLite version
> +
> +def SQLiteVersion(db):
> +	query = QSqlQuery(db)
> +	QueryExec(query, "SELECT sqlite_version()")
> +	if query.next():
> +		return query.value(0)
> +	return "Unknown"
> +
> +# About dialog
> +
> +class AboutDialog(QDialog):
> +
> +	def __init__(self, glb, parent=None):
> +		super(AboutDialog, self).__init__(parent)
> +
> +		self.setWindowTitle("About Exported SQL Viewer")
> +		self.setMinimumWidth(300)
> +
> +		pyside_version = "1" if pyside_version_1 else "2"
> +
> +		text = "<pre>"
> +		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
> +		text += "PySide version:     " + pyside_version + "\n"
> +		text += "Qt version:         " + qVersion() + "\n"
> +		if glb.dbref.is_sqlite3:
> +			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
> +		else:
> +			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
> +		text += "</pre>"
> +
> +		self.text = QTextBrowser()
> +		self.text.setHtml(text)
> +		self.text.setReadOnly(True)
> +		self.text.setOpenExternalLinks(True)
> +
> +		self.vbox = QVBoxLayout()
> +		self.vbox.addWidget(self.text)
> +
> +		self.setLayout(self.vbox);
> +
>  # Font resize
>  
>  def ResizeFont(widget, diff):
> @@ -3024,6 +3078,7 @@ class MainWindow(QMainWindow):
>  
>  		help_menu = menu.addMenu("&Help")
>  		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
> +		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
>  
>  	def Try(self, fn):
>  		win = self.mdi_area.activeSubWindow()
> @@ -3109,6 +3164,10 @@ class MainWindow(QMainWindow):
>  	def Help(self):
>  		HelpWindow(self.glb, self)
>  
> +	def About(self):
> +		dialog = AboutDialog(self.glb, self)
> +		dialog.exec_()
> +
>  # XED Disassembler
>  
>  class xed_state_t(Structure):
> -- 
> 2.17.1

-- 

- Arnaldo

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Move view creation
  2019-05-03 12:08 ` [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation Adrian Hunter
@ 2019-05-18  8:54   ` tip-bot for Adrian Hunter
  0 siblings, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:54 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: jolsa, hpa, acme, mingo, adrian.hunter, tglx, linux-kernel

Commit-ID:  be6e747136a4dc8aad99259e47fd6f7362a43996
Gitweb:     https://git.kernel.org/tip/be6e747136a4dc8aad99259e47fd6f7362a43996
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:24 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Move view creation

As preparation for adding support for copying to clipboard, create view
in TreeWindowBase instead of derived classes.

Committer testing:

Tested using an old .db used to test some older patches:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

Nothing breaks.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-3-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 74ef92f1d19a..db4655168ab1 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -877,9 +877,10 @@ class TreeWindowBase(QMdiSubWindow):
 		super(TreeWindowBase, self).__init__(parent)
 
 		self.model = None
-		self.view = None
 		self.find_bar = None
 
+		self.view = QTreeView()
+
 	def DisplayFound(self, ids):
 		if not len(ids):
 			return False
@@ -921,7 +922,6 @@ class CallGraphWindow(TreeWindowBase):
 
 		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
 
-		self.view = QTreeView()
 		self.view.setModel(self.model)
 
 		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
@@ -944,7 +944,6 @@ class CallTreeWindow(TreeWindowBase):
 
 		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
 
-		self.view = QTreeView()
 		self.view.setModel(self.model)
 
 		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font
  2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
  2019-05-13 19:57   ` Arnaldo Carvalho de Melo
@ 2019-05-18  8:55   ` tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:55 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: mingo, tglx, adrian.hunter, linux-kernel, acme, hpa, jolsa

Commit-ID:  4b2084537e5f3b58337bce894391fb63bf3b0e28
Gitweb:     https://git.kernel.org/tip/4b2084537e5f3b58337bce894391fb63bf3b0e28
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:23 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font

Fix the following error if shrink / enlarge font is used with the help
window.

  Traceback (most recent call last):
    File "tools/perf/scripts/python/exported-sql-viewer.py", line 2791, in ShrinkFont
      ShrinkFont(win.view)
  AttributeError: 'HelpWindow' object has no attribute 'view'

Committer testing:

Before, matches above output:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db
  Traceback (most recent call last):
    File "/home/acme/libexec/perf-core/scripts/python/exported-sql-viewer.py", line 2780, in EnlargeFont
      EnlargeFont(win.view)
  AttributeError: 'HelpWindow' object has no attribute 'view'
  $

After:

No more tracebacks, but the fonts don't get enlarged, which is kinda
frustrating...

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-2-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index db4655168ab1..fd073e4af8da 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -2755,6 +2755,14 @@ class MainWindow(QMainWindow):
 		help_menu = menu.addMenu("&Help")
 		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
 
+	def Try(self, fn):
+		win = self.mdi_area.activeSubWindow()
+		if win:
+			try:
+				fn(win.view)
+			except:
+				pass
+
 	def Find(self):
 		win = self.mdi_area.activeSubWindow()
 		if win:
@@ -2772,12 +2780,10 @@ class MainWindow(QMainWindow):
 				pass
 
 	def ShrinkFont(self):
-		win = self.mdi_area.activeSubWindow()
-		ShrinkFont(win.view)
+		self.Try(ShrinkFont)
 
 	def EnlargeFont(self):
-		win = self.mdi_area.activeSubWindow()
-		EnlargeFont(win.view)
+		self.Try(EnlargeFont)
 
 	def EventMenu(self, events, reports_menu):
 		branches_events = 0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Add tree level
  2019-05-03 12:08 ` [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level Adrian Hunter
@ 2019-05-18  8:55   ` tip-bot for Adrian Hunter
  0 siblings, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:55 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: linux-kernel, mingo, acme, jolsa, tglx, hpa, adrian.hunter

Commit-ID:  3ac641f4acd66c109b74f108f8a61f2905702b10
Gitweb:     https://git.kernel.org/tip/3ac641f4acd66c109b74f108f8a61f2905702b10
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:25 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Add tree level

As preparation for adding support for copying to clipboard, keep track of
what level each item is in tree items.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-4-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index fd073e4af8da..48953257a1f0 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -456,6 +456,10 @@ class CallGraphLevelItemBase(object):
 		self.query_done = False;
 		self.child_count = 0
 		self.child_items = []
+		if parent_item:
+			self.level = parent_item.level + 1
+		else:
+			self.level = 0
 
 	def getChildItem(self, row):
 		return self.child_items[row]

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Add copy to clipboard
  2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
  2019-05-13 20:03   ` Arnaldo Carvalho de Melo
@ 2019-05-18  8:56   ` tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:56 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: jolsa, acme, adrian.hunter, mingo, tglx, hpa, linux-kernel

Commit-ID:  96c43b9a7ab3b70bc35d762f7b76082dfd118a6a
Gitweb:     https://git.kernel.org/tip/96c43b9a7ab3b70bc35d762f7b76082dfd118a6a
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:26 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Add copy to clipboard

Add support for copying to clipboard. Two menu options are added to copy the
selected rows / columns with normal spacing, or as comma-separated-values.
In the case of trees, only entire rows can be copied.

Comitter testing:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

Select the lines, press control+C and on the same terminal,
press control+shift+V and voilà:

Call Path                           Object           Count  Time (ns)  Time (%)  Branch Count  Branch Count (%)
  ▼ 14503:14503
    ▼ _start                        ld-2.28.so           1     156267     100.0         10602             100.0
        unknown                     unknown              1       2276       1.5             1               0.0
      ▼ _dl_start                   ld-2.28.so           1     137047      87.7         10088              95.2
        ▶ unknown                   unknown              4       4127       3.0             4               0.0
          _dl_setup_hash            ld-2.28.so           1          0       0.0             1               0.0
        ▶ _dl_sysdep_start          ld-2.28.so           1     131342      95.8          9981              98.9
      ▼ _dl_init                    ld-2.28.so           1       9142       5.9           326               3.1
        ▼ call_init.part.0          ld-2.28.so           3       9133      99.9           319              97.9
          ▶ _init                   libc-2.28.so         1       6877      75.3           110              34.5
          ▶ check_stdfiles_vtables  libc-2.28.so         1         76       0.8             2               0.6
          ▶ init_cacheinfo          libc-2.28.so         1       1991      21.8           197              61.8
      ▶ _start                      simple-retpoline     1       7457       4.8           182               1.7

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-5-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 217 +++++++++++++++++++++++
 1 file changed, 217 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index 48953257a1f0..baa2b220238a 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -884,6 +884,8 @@ class TreeWindowBase(QMdiSubWindow):
 		self.find_bar = None
 
 		self.view = QTreeView()
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 
 	def DisplayFound(self, ids):
 		if not len(ids):
@@ -1652,6 +1654,8 @@ class BranchWindow(QMdiSubWindow):
 
 		self.view = QTreeView()
 		self.view.setUniformRowHeights(True)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 		self.view.setModel(self.model)
 
 		self.ResizeColumnsToContents()
@@ -2264,6 +2268,207 @@ class ResizeColumnsToContentsBase(QObject):
 		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
 		self.ResizeColumnsToContents()
 
+# Convert value to CSV
+
+def ToCSValue(val):
+	if '"' in val:
+		val = val.replace('"', '""')
+	if "," in val or '"' in val:
+		val = '"' + val + '"'
+	return val
+
+# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
+
+glb_max_cols = 1000
+
+def RowColumnKey(a):
+	return a.row() * glb_max_cols + a.column()
+
+# Copy selected table cells to clipboard
+
+def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
+	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
+	idx_cnt = len(indexes)
+	if not idx_cnt:
+		return
+	if idx_cnt == 1:
+		with_hdr=False
+	min_row = indexes[0].row()
+	max_row = indexes[0].row()
+	min_col = indexes[0].column()
+	max_col = indexes[0].column()
+	for i in indexes:
+		min_row = min(min_row, i.row())
+		max_row = max(max_row, i.row())
+		min_col = min(min_col, i.column())
+		max_col = max(max_col, i.column())
+	if max_col > glb_max_cols:
+		raise RuntimeError("glb_max_cols is too low")
+	max_width = [0] * (1 + max_col - min_col)
+	for i in indexes:
+		c = i.column() - min_col
+		max_width[c] = max(max_width[c], len(str(i.data())))
+	text = ""
+	pad = ""
+	sep = ""
+	if with_hdr:
+		model = indexes[0].model()
+		for col in range(min_col, max_col + 1):
+			val = model.headerData(col, Qt.Horizontal)
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				c = col - min_col
+				max_width[c] = max(max_width[c], len(val))
+				width = max_width[c]
+				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
+				if align & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "  "
+		text += "\n"
+		pad = ""
+		sep = ""
+	last_row = min_row
+	for i in indexes:
+		if i.row() > last_row:
+			last_row = i.row()
+			text += "\n"
+			pad = ""
+			sep = ""
+		if as_csv:
+			text += sep + ToCSValue(str(i.data()))
+			sep = ","
+		else:
+			width = max_width[i.column() - min_col]
+			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+				val = str(i.data()).rjust(width)
+			else:
+				val = str(i.data())
+			text += pad + sep + val
+			pad = " " * (width - len(val))
+			sep = "  "
+	QApplication.clipboard().setText(text)
+
+def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
+	indexes = view.selectedIndexes()
+	if not len(indexes):
+		return
+
+	selection = view.selectionModel()
+
+	first = None
+	for i in indexes:
+		above = view.indexAbove(i)
+		if not selection.isSelected(above):
+			first = i
+			break
+
+	if first is None:
+		raise RuntimeError("CopyTreeCellsToClipboard internal error")
+
+	model = first.model()
+	row_cnt = 0
+	col_cnt = model.columnCount(first)
+	max_width = [0] * col_cnt
+
+	indent_sz = 2
+	indent_str = " " * indent_sz
+
+	expanded_mark_sz = 2
+	if sys.version_info[0] == 3:
+		expanded_mark = "\u25BC "
+		not_expanded_mark = "\u25B6 "
+	else:
+		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
+		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
+	leaf_mark = "  "
+
+	if not as_csv:
+		pos = first
+		while True:
+			row_cnt += 1
+			row = pos.row()
+			for c in range(col_cnt):
+				i = pos.sibling(row, c)
+				if c:
+					n = len(str(i.data()))
+				else:
+					n = len(str(i.data()).strip())
+					n += (i.internalPointer().level - 1) * indent_sz
+					n += expanded_mark_sz
+				max_width[c] = max(max_width[c], n)
+			pos = view.indexBelow(pos)
+			if not selection.isSelected(pos):
+				break
+
+	text = ""
+	pad = ""
+	sep = ""
+	if with_hdr:
+		for c in range(col_cnt):
+			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				max_width[c] = max(max_width[c], len(val))
+				width = max_width[c]
+				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
+				if align & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "   "
+		text += "\n"
+		pad = ""
+		sep = ""
+
+	pos = first
+	while True:
+		row = pos.row()
+		for c in range(col_cnt):
+			i = pos.sibling(row, c)
+			val = str(i.data())
+			if not c:
+				if model.hasChildren(i):
+					if view.isExpanded(i):
+						mark = expanded_mark
+					else:
+						mark = not_expanded_mark
+				else:
+					mark = leaf_mark
+				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
+			if as_csv:
+				text += sep + ToCSValue(val)
+				sep = ","
+			else:
+				width = max_width[c]
+				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
+					val = val.rjust(width)
+				text += pad + sep + val
+				pad = " " * (width - len(val))
+				sep = "   "
+		pos = view.indexBelow(pos)
+		if not selection.isSelected(pos):
+			break
+		text = text.rstrip() + "\n"
+		pad = ""
+		sep = ""
+
+	QApplication.clipboard().setText(text)
+
+def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
+	view.CopyCellsToClipboard(view, as_csv, with_hdr)
+
+def CopyCellsToClipboardHdr(view):
+	CopyCellsToClipboard(view, False, True)
+
+def CopyCellsToClipboardCSV(view):
+	CopyCellsToClipboard(view, True, True)
+
 # Table window
 
 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
@@ -2282,6 +2487,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.verticalHeader().setVisible(False)
 		self.view.sortByColumn(-1, Qt.AscendingOrder)
 		self.view.setSortingEnabled(True)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
 		self.ResizeColumnsToContents()
 
@@ -2398,6 +2605,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.setModel(self.model)
 		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
 		self.view.verticalHeader().setVisible(False)
+		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
+		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
 		self.ResizeColumnsToContents()
 
@@ -2735,6 +2944,8 @@ class MainWindow(QMainWindow):
 		file_menu.addAction(CreateExitAction(glb.app, self))
 
 		edit_menu = menu.addMenu("&Edit")
+		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
+		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
 		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
 		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
 		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
@@ -2767,6 +2978,12 @@ class MainWindow(QMainWindow):
 			except:
 				pass
 
+	def CopyToClipboard(self):
+		self.Try(CopyCellsToClipboardHdr)
+
+	def CopyToClipboardCSV(self):
+		self.Try(CopyCellsToClipboardCSV)
+
 	def Find(self):
 		win = self.mdi_area.activeSubWindow()
 		if win:

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Add context menu
  2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
  2019-05-13 20:09   ` Arnaldo Carvalho de Melo
@ 2019-05-18  8:57   ` tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:57 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: hpa, jolsa, linux-kernel, adrian.hunter, acme, tglx, mingo

Commit-ID:  9bc4e4bfe6169343a8f019cd5d7843a558b78363
Gitweb:     https://git.kernel.org/tip/9bc4e4bfe6169343a8f019cd5d7843a558b78363
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:27 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Add context menu

Add a context menu (right-click) that provides options for copying to
clipboard, including, for trees, the ability to copy only the cell under
the mouse pointer.

Committer testing:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

  Simply right click and pick "Copy selection", that at this point has
  just the first line, not expanded, then see what was copied by pressing
  shift+control+v on a terminal:

Call Path,Object,Count,Time (ns),Time (%),Branch Count,Branch Count (%)
▶ simple-retpolin,,,,,,

  Ditto after expanding, i.e. the selection continues to be just one
  line:

Call Path           Object   Count   Time (ns)   Time (%)   Branch Count   Branch Count (%)
▼ simple-retpolin

   Now select all the lines with the mouse and control+shift+v again:

Call Path                     Object             Count   Time (ns)   Time (%)   Branch Count   Branch Count (%)
  ▼ 14503:14503
    ▼ _start                  ld-2.28.so             1      156267      100.0          10602              100.0
      ▶ unknown               unknown                1        2276        1.5              1                0.0
      ▶ _dl_start             ld-2.28.so             1      137047       87.7          10088               95.2
      ▶ _dl_init              ld-2.28.so             1        9142        5.9            326                3.1
      ▼ _start                simple-retpoline       1        7457        4.8            182                1.7
        ▶ unknown             unknown                1         805       10.8              1                0.5
        ▶ __libc_start_main   libc-2.28.so           1        6347       85.1            179               98.4

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-6-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 41 ++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index baa2b220238a..affd80ebcae0 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -887,6 +887,8 @@ class TreeWindowBase(QMdiSubWindow):
 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
 		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
 
+		self.context_menu = TreeContextMenu(self.view)
+
 	def DisplayFound(self, ids):
 		if not len(ids):
 			return False
@@ -1660,6 +1662,8 @@ class BranchWindow(QMdiSubWindow):
 
 		self.ResizeColumnsToContents()
 
+		self.context_menu = TreeContextMenu(self.view)
+
 		self.find_bar = FindBar(self, self, True)
 
 		self.finder = ChildDataItemFinder(self.model.root)
@@ -2469,6 +2473,39 @@ def CopyCellsToClipboardHdr(view):
 def CopyCellsToClipboardCSV(view):
 	CopyCellsToClipboard(view, True, True)
 
+# Context menu
+
+class ContextMenu(object):
+
+	def __init__(self, view):
+		self.view = view
+		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
+
+	def ShowContextMenu(self, pos):
+		menu = QMenu(self.view)
+		self.AddActions(menu)
+		menu.exec_(self.view.mapToGlobal(pos))
+
+	def AddCopy(self, menu):
+		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
+		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
+
+	def AddActions(self, menu):
+		self.AddCopy(menu)
+
+class TreeContextMenu(ContextMenu):
+
+	def __init__(self, view):
+		super(TreeContextMenu, self).__init__(view)
+
+	def AddActions(self, menu):
+		i = self.view.currentIndex()
+		text = str(i.data()).strip()
+		if len(text):
+			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
+		self.AddCopy(menu)
+
 # Table window
 
 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
@@ -2492,6 +2529,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 
 		self.ResizeColumnsToContents()
 
+		self.context_menu = ContextMenu(self.view)
+
 		self.find_bar = FindBar(self, self, True)
 
 		self.finder = ChildDataItemFinder(self.data_model)
@@ -2608,6 +2647,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
 		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
 
+		self.context_menu = ContextMenu(self.view)
+
 		self.ResizeColumnsToContents()
 
 		self.find_bar = FindBar(self, self, True)

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [tip:perf/core] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box
  2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
  2019-05-13 20:12   ` Arnaldo Carvalho de Melo
@ 2019-05-18  8:58   ` tip-bot for Adrian Hunter
  1 sibling, 0 replies; 17+ messages in thread
From: tip-bot for Adrian Hunter @ 2019-05-18  8:58 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: tglx, mingo, jolsa, hpa, acme, adrian.hunter, linux-kernel

Commit-ID:  b62d18aba1109506c1926ab7b564c4ac3bd48786
Gitweb:     https://git.kernel.org/tip/b62d18aba1109506c1926ab7b564c4ac3bd48786
Author:     Adrian Hunter <adrian.hunter@intel.com>
AuthorDate: Fri, 3 May 2019 15:08:28 +0300
Committer:  Arnaldo Carvalho de Melo <acme@redhat.com>
CommitDate: Wed, 15 May 2019 16:36:47 -0300

perf scripts python: exported-sql-viewer.py: Add 'About' dialog box

With support for Python 2 or 3 and PySide 1 or 2 (Qt 4 or 5), it is
useful to see what versions are in use. Add an 'About' dialog box that
displays Python, PySide, Qt and database server (SQLite or PostgreSQL)
version numbers.

Committer testing:

  $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

  Then go to 'Help', then 'About', select all the lines with the mouse
  press 'Control+C', then, on the same terminal press control+shift+V
  which shows my current environment:

Python version:     2.7.16
PySide version:     1
Qt version:         4.8.7
SQLite version:     3.26.0

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20190503120828.25326-7-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/scripts/python/exported-sql-viewer.py | 59 ++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
index affd80ebcae0..affed7d149be 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/scripts/python/exported-sql-viewer.py
@@ -2913,6 +2913,60 @@ class HelpOnlyWindow(QMainWindow):
 
 		self.setCentralWidget(self.text)
 
+# PostqreSQL server version
+
+def PostqreSQLServerVersion(db):
+	query = QSqlQuery(db)
+	QueryExec(query, "SELECT VERSION()")
+	if query.next():
+		v_str = query.value(0)
+		v_list = v_str.strip().split(" ")
+		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
+			return v_list[1]
+		return v_str
+	return "Unknown"
+
+# SQLite version
+
+def SQLiteVersion(db):
+	query = QSqlQuery(db)
+	QueryExec(query, "SELECT sqlite_version()")
+	if query.next():
+		return query.value(0)
+	return "Unknown"
+
+# About dialog
+
+class AboutDialog(QDialog):
+
+	def __init__(self, glb, parent=None):
+		super(AboutDialog, self).__init__(parent)
+
+		self.setWindowTitle("About Exported SQL Viewer")
+		self.setMinimumWidth(300)
+
+		pyside_version = "1" if pyside_version_1 else "2"
+
+		text = "<pre>"
+		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
+		text += "PySide version:     " + pyside_version + "\n"
+		text += "Qt version:         " + qVersion() + "\n"
+		if glb.dbref.is_sqlite3:
+			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
+		else:
+			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
+		text += "</pre>"
+
+		self.text = QTextBrowser()
+		self.text.setHtml(text)
+		self.text.setReadOnly(True)
+		self.text.setOpenExternalLinks(True)
+
+		self.vbox = QVBoxLayout()
+		self.vbox.addWidget(self.text)
+
+		self.setLayout(self.vbox);
+
 # Font resize
 
 def ResizeFont(widget, diff):
@@ -3010,6 +3064,7 @@ class MainWindow(QMainWindow):
 
 		help_menu = menu.addMenu("&Help")
 		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
+		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
 
 	def Try(self, fn):
 		win = self.mdi_area.activeSubWindow()
@@ -3095,6 +3150,10 @@ class MainWindow(QMainWindow):
 	def Help(self):
 		HelpWindow(self.glb, self)
 
+	def About(self):
+		dialog = AboutDialog(self.glb, self)
+		dialog.exec_()
+
 # XED Disassembler
 
 class xed_state_t(Structure):

^ permalink raw reply related	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2019-05-18  8:58 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
2019-05-13 19:57   ` Arnaldo Carvalho de Melo
2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation Adrian Hunter
2019-05-18  8:54   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level Adrian Hunter
2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
2019-05-13 20:03   ` Arnaldo Carvalho de Melo
2019-05-18  8:56   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
2019-05-13 20:09   ` Arnaldo Carvalho de Melo
2019-05-18  8:57   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
2019-05-13 20:12   ` Arnaldo Carvalho de Melo
2019-05-18  8:58   ` [tip:perf/core] " tip-bot for Adrian Hunter

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.