linux-kernel.vger.kernel.org archive mirror
 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).