From: Arnaldo Carvalho de Melo <arnaldo.melo@gmail.com>
To: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: Re: [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard
Date: Mon, 13 May 2019 17:03:22 -0300 [thread overview]
Message-ID: <20190513200322.GD3198@kernel.org> (raw)
In-Reply-To: <20190503120828.25326-5-adrian.hunter@intel.com>
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
next prev parent reply other threads:[~2019-05-13 20:03 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190513200322.GD3198@kernel.org \
--to=arnaldo.melo@gmail.com \
--cc=adrian.hunter@intel.com \
--cc=jolsa@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.