License cleanup: add SPDX GPL-2.0 license identifier to files with no license
Many source files in the tree are missing licensing information, which
makes it harder for compliance tools to determine the correct license.
By default all files without license information are under the default
license of the kernel, which is GPL version 2.
Update the files which contain no license information with the 'GPL-2.0'
SPDX license identifier. The SPDX identifier is a legally binding
shorthand, which can be used instead of the full boiler plate text.
This patch is based on work done by Thomas Gleixner and Kate Stewart and
Philippe Ombredanne.
How this work was done:
Patches were generated and checked against linux-4.14-rc6 for a subset of
the use cases:
- file had no licensing information it it.
- file was a */uapi/* one with no licensing information in it,
- file was a */uapi/* one with existing licensing information,
Further patches will be generated in subsequent months to fix up cases
where non-standard license headers were used, and references to license
had to be inferred by heuristics based on keywords.
The analysis to determine which SPDX License Identifier to be applied to
a file was done in a spreadsheet of side by side results from of the
output of two independent scanners (ScanCode & Windriver) producing SPDX
tag:value files created by Philippe Ombredanne. Philippe prepared the
base worksheet, and did an initial spot review of a few 1000 files.
The 4.13 kernel was the starting point of the analysis with 60,537 files
assessed. Kate Stewart did a file by file comparison of the scanner
results in the spreadsheet to determine which SPDX license identifier(s)
to be applied to the file. She confirmed any determination that was not
immediately clear with lawyers working with the Linux Foundation.
Criteria used to select files for SPDX license identifier tagging was:
- Files considered eligible had to be source code files.
- Make and config files were included as candidates if they contained >5
lines of source
- File already had some variant of a license header in it (even if <5
lines).
All documentation files were explicitly excluded.
The following heuristics were used to determine which SPDX license
identifiers to apply.
- when both scanners couldn't find any license traces, file was
considered to have no license information in it, and the top level
COPYING file license applied.
For non */uapi/* files that summary was:
SPDX license identifier # files
---------------------------------------------------|-------
GPL-2.0 11139
and resulted in the first patch in this series.
If that file was a */uapi/* path one, it was "GPL-2.0 WITH
Linux-syscall-note" otherwise it was "GPL-2.0". Results of that was:
SPDX license identifier # files
---------------------------------------------------|-------
GPL-2.0 WITH Linux-syscall-note 930
and resulted in the second patch in this series.
- if a file had some form of licensing information in it, and was one
of the */uapi/* ones, it was denoted with the Linux-syscall-note if
any GPL family license was found in the file or had no licensing in
it (per prior point). Results summary:
SPDX license identifier # files
---------------------------------------------------|------
GPL-2.0 WITH Linux-syscall-note 270
GPL-2.0+ WITH Linux-syscall-note 169
((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) 21
((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) 17
LGPL-2.1+ WITH Linux-syscall-note 15
GPL-1.0+ WITH Linux-syscall-note 14
((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) 5
LGPL-2.0+ WITH Linux-syscall-note 4
LGPL-2.1 WITH Linux-syscall-note 3
((GPL-2.0 WITH Linux-syscall-note) OR MIT) 3
((GPL-2.0 WITH Linux-syscall-note) AND MIT) 1
and that resulted in the third patch in this series.
- when the two scanners agreed on the detected license(s), that became
the concluded license(s).
- when there was disagreement between the two scanners (one detected a
license but the other didn't, or they both detected different
licenses) a manual inspection of the file occurred.
- In most cases a manual inspection of the information in the file
resulted in a clear resolution of the license that should apply (and
which scanner probably needed to revisit its heuristics).
- When it was not immediately clear, the license identifier was
confirmed with lawyers working with the Linux Foundation.
- If there was any question as to the appropriate license identifier,
the file was flagged for further research and to be revisited later
in time.
In total, over 70 hours of logged manual review was done on the
spreadsheet to determine the SPDX license identifiers to apply to the
source files by Kate, Philippe, Thomas and, in some cases, confirmation
by lawyers working with the Linux Foundation.
Kate also obtained a third independent scan of the 4.13 code base from
FOSSology, and compared selected files where the other two scanners
disagreed against that SPDX file, to see if there was new insights. The
Windriver scanner is based on an older version of FOSSology in part, so
they are related.
Thomas did random spot checks in about 500 files from the spreadsheets
for the uapi headers and agreed with SPDX license identifier in the
files he inspected. For the non-uapi files Thomas did random spot checks
in about 15000 files.
In initial set of patches against 4.14-rc6, 3 files were found to have
copy/paste license identifier errors, and have been fixed to reflect the
correct identifier.
Additionally Philippe spent 10 hours this week doing a detailed manual
inspection and review of the 12,461 patched files from the initial patch
version early this week with:
- a full scancode scan run, collecting the matched texts, detected
license ids and scores
- reviewing anything where there was a license detected (about 500+
files) to ensure that the applied SPDX license was correct
- reviewing anything where there was no detection but the patch license
was not GPL-2.0 WITH Linux-syscall-note to ensure that the applied
SPDX license was correct
This produced a worksheet with 20 files needing minor correction. This
worksheet was then exported into 3 different .csv files for the
different types of files to be modified.
These .csv files were then reviewed by Greg. Thomas wrote a script to
parse the csv files and add the proper SPDX tag to the file, in the
format that the file expected. This script was further refined by Greg
based on the output to detect more types of files automatically and to
distinguish between header and source .c files (which need different
comment types.) Finally Greg ran the script using the .csv files to
generate the patches.
Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org>
Reviewed-by: Philippe Ombredanne <pombredanne@nexb.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-11-01 22:07:57 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2017-04-18 23:26:44 +08:00
|
|
|
#include <dirent.h>
|
2017-04-18 21:46:11 +08:00
|
|
|
#include <errno.h>
|
2017-04-18 02:23:08 +08:00
|
|
|
#include <inttypes.h>
|
2010-08-11 02:49:07 +08:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <linux/rbtree.h>
|
2019-06-26 23:06:20 +08:00
|
|
|
#include <linux/string.h>
|
2017-04-18 22:08:10 +08:00
|
|
|
#include <sys/ttydefaults.h>
|
2019-03-11 22:44:56 +08:00
|
|
|
#include <linux/time64.h>
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2019-01-29 18:11:04 +08:00
|
|
|
#include "../../util/callchain.h"
|
2012-04-04 15:14:26 +08:00
|
|
|
#include "../../util/evsel.h"
|
|
|
|
#include "../../util/evlist.h"
|
|
|
|
#include "../../util/hist.h"
|
2019-01-27 20:42:37 +08:00
|
|
|
#include "../../util/map.h"
|
2019-01-28 07:03:34 +08:00
|
|
|
#include "../../util/symbol.h"
|
2012-04-04 15:14:26 +08:00
|
|
|
#include "../../util/pstack.h"
|
|
|
|
#include "../../util/sort.h"
|
|
|
|
#include "../../util/util.h"
|
2014-08-12 16:16:06 +08:00
|
|
|
#include "../../util/top.h"
|
2017-04-20 08:34:35 +08:00
|
|
|
#include "../../util/thread.h"
|
2012-11-02 13:50:06 +08:00
|
|
|
#include "../../arch/common.h"
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-06-21 05:58:13 +08:00
|
|
|
#include "../browsers/hists.h"
|
2010-08-11 02:49:07 +08:00
|
|
|
#include "../helpline.h"
|
|
|
|
#include "../util.h"
|
2011-10-26 22:04:37 +08:00
|
|
|
#include "../ui.h"
|
2010-08-11 02:49:07 +08:00
|
|
|
#include "map.h"
|
2014-06-15 16:22:15 +08:00
|
|
|
#include "annotate.h"
|
2017-04-18 03:30:49 +08:00
|
|
|
#include "srcline.h"
|
2017-04-18 03:51:59 +08:00
|
|
|
#include "string2.h"
|
2017-04-20 03:05:56 +08:00
|
|
|
#include "units.h"
|
2019-03-11 22:44:56 +08:00
|
|
|
#include "time-utils.h"
|
2010-08-11 02:49:07 +08:00
|
|
|
|
tools perf: Move from sane_ctype.h obtained from git to the Linux's original
We got the sane_ctype.h headers from git and kept using it so far, but
since that code originally came from the kernel sources to the git
sources, perhaps its better to just use the one in the kernel, so that
we can leverage tools/perf/check_headers.sh to be notified when our copy
gets out of sync, i.e. when fixes or goodies are added to the code we've
copied.
This will help with things like tools/lib/string.c where we want to have
more things in common with the kernel, such as strim(), skip_spaces(),
etc so as to go on removing the things that we have in tools/perf/util/
and instead using the code in the kernel, indirectly and removing things
like EXPORT_SYMBOL(), etc, getting notified when fixes and improvements
are made to the original code.
Hopefully this also should help with reducing the difference of code
hosted in tools/ to the one in the kernel proper.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: https://lkml.kernel.org/n/tip-7k9868l713wqtgo01xxygn12@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2019-06-26 04:27:31 +08:00
|
|
|
#include <linux/ctype.h>
|
2017-04-18 03:10:49 +08:00
|
|
|
|
2012-09-03 10:53:09 +08:00
|
|
|
extern void hist_browser__init_hpp(void);
|
|
|
|
|
2018-04-03 01:00:04 +08:00
|
|
|
static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size);
|
2014-04-22 13:05:35 +08:00
|
|
|
static void hist_browser__update_nr_entries(struct hist_browser *hb);
|
2011-10-06 06:11:32 +08:00
|
|
|
|
2014-04-22 14:56:17 +08:00
|
|
|
static struct rb_node *hists__filter_entries(struct rb_node *nd,
|
|
|
|
float min_pcnt);
|
|
|
|
|
2014-04-22 13:49:31 +08:00
|
|
|
static bool hist_browser__has_filter(struct hist_browser *hb)
|
|
|
|
{
|
2016-01-06 23:59:02 +08:00
|
|
|
return hists__has_filter(hb->hists) || hb->min_pcnt || symbol_conf.has_filter || hb->c2c_filter;
|
2014-04-22 13:49:31 +08:00
|
|
|
}
|
|
|
|
|
2015-03-12 15:21:49 +08:00
|
|
|
static int hist_browser__get_folding(struct hist_browser *browser)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
struct hists *hists = browser->hists;
|
|
|
|
int unfolded_rows = 0;
|
|
|
|
|
2018-12-07 03:18:18 +08:00
|
|
|
for (nd = rb_first_cached(&hists->entries);
|
2015-03-12 15:21:49 +08:00
|
|
|
(nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
|
2016-02-24 23:13:43 +08:00
|
|
|
nd = rb_hierarchy_next(nd)) {
|
2015-03-12 15:21:49 +08:00
|
|
|
struct hist_entry *he =
|
|
|
|
rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
2016-02-24 23:13:43 +08:00
|
|
|
if (he->leaf && he->unfolded)
|
2015-03-12 15:21:49 +08:00
|
|
|
unfolded_rows += he->nr_rows;
|
|
|
|
}
|
|
|
|
return unfolded_rows;
|
|
|
|
}
|
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
static void hist_browser__set_title_space(struct hist_browser *hb)
|
|
|
|
{
|
|
|
|
struct ui_browser *browser = &hb->b;
|
|
|
|
struct hists *hists = hb->hists;
|
|
|
|
struct perf_hpp_list *hpp_list = hists->hpp_list;
|
|
|
|
|
|
|
|
browser->extra_title_lines = hb->show_headers ? hpp_list->nr_header_lines : 0;
|
|
|
|
}
|
|
|
|
|
2014-04-22 14:56:17 +08:00
|
|
|
static u32 hist_browser__nr_entries(struct hist_browser *hb)
|
|
|
|
{
|
|
|
|
u32 nr_entries;
|
|
|
|
|
2016-02-24 23:13:43 +08:00
|
|
|
if (symbol_conf.report_hierarchy)
|
|
|
|
nr_entries = hb->nr_hierarchy_entries;
|
|
|
|
else if (hist_browser__has_filter(hb))
|
2014-04-22 14:56:17 +08:00
|
|
|
nr_entries = hb->nr_non_filtered_entries;
|
|
|
|
else
|
|
|
|
nr_entries = hb->hists->nr_entries;
|
|
|
|
|
2015-03-12 15:21:49 +08:00
|
|
|
hb->nr_callchain_rows = hist_browser__get_folding(hb);
|
2014-04-22 14:56:17 +08:00
|
|
|
return nr_entries + hb->nr_callchain_rows;
|
|
|
|
}
|
|
|
|
|
2014-06-14 21:44:52 +08:00
|
|
|
static void hist_browser__update_rows(struct hist_browser *hb)
|
|
|
|
{
|
|
|
|
struct ui_browser *browser = &hb->b;
|
2016-08-07 23:28:26 +08:00
|
|
|
struct hists *hists = hb->hists;
|
|
|
|
struct perf_hpp_list *hpp_list = hists->hpp_list;
|
2018-04-03 02:48:18 +08:00
|
|
|
u16 index_row;
|
|
|
|
|
|
|
|
if (!hb->show_headers) {
|
|
|
|
browser->rows += browser->extra_title_lines;
|
|
|
|
browser->extra_title_lines = 0;
|
|
|
|
return;
|
|
|
|
}
|
2014-06-14 21:44:52 +08:00
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
browser->extra_title_lines = hpp_list->nr_header_lines;
|
|
|
|
browser->rows -= browser->extra_title_lines;
|
2014-06-14 21:44:52 +08:00
|
|
|
/*
|
|
|
|
* Verify if we were at the last line and that line isn't
|
|
|
|
* visibe because we now show the header line(s).
|
|
|
|
*/
|
|
|
|
index_row = browser->index - browser->top_idx;
|
|
|
|
if (index_row >= browser->rows)
|
|
|
|
browser->index -= index_row - browser->rows + 1;
|
|
|
|
}
|
|
|
|
|
2014-07-02 04:01:01 +08:00
|
|
|
static void hist_browser__refresh_dimensions(struct ui_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2014-07-02 04:01:01 +08:00
|
|
|
struct hist_browser *hb = container_of(browser, struct hist_browser, b);
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
/* 3 == +/- toggle symbol before actual hist_entry rendering */
|
2014-07-02 04:01:01 +08:00
|
|
|
browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]"));
|
|
|
|
/*
|
|
|
|
* FIXME: Just keeping existing behaviour, but this really should be
|
|
|
|
* before updating browser->width, as it will invalidate the
|
|
|
|
* calculation above. Fix this and the fallout in another
|
|
|
|
* changeset.
|
|
|
|
*/
|
|
|
|
ui_browser__refresh_dimensions(browser);
|
2014-07-02 03:42:24 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void hist_browser__reset(struct hist_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2014-04-22 14:56:17 +08:00
|
|
|
/*
|
|
|
|
* The hists__remove_entry_filter() already folds non-filtered
|
|
|
|
* entries so we can assume it has 0 callchain rows.
|
|
|
|
*/
|
|
|
|
browser->nr_callchain_rows = 0;
|
|
|
|
|
2014-04-22 13:49:31 +08:00
|
|
|
hist_browser__update_nr_entries(browser);
|
2014-04-22 14:56:17 +08:00
|
|
|
browser->b.nr_entries = hist_browser__nr_entries(browser);
|
2014-07-02 04:01:01 +08:00
|
|
|
hist_browser__refresh_dimensions(&browser->b);
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__reset_index(&browser->b);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static char tree__folded_sign(bool unfolded)
|
|
|
|
{
|
|
|
|
return unfolded ? '-' : '+';
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static char hist_entry__folded(const struct hist_entry *he)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2015-05-05 22:55:46 +08:00
|
|
|
return he->has_children ? tree__folded_sign(he->unfolded) : ' ';
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static char callchain_list__folded(const struct callchain_list *cl)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2015-05-05 22:55:46 +08:00
|
|
|
return cl->has_children ? tree__folded_sign(cl->unfolded) : ' ';
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
static void callchain_list__set_folding(struct callchain_list *cl, bool unfold)
|
2010-08-26 04:18:35 +08:00
|
|
|
{
|
2015-05-05 22:55:46 +08:00
|
|
|
cl->unfolded = unfold ? cl->has_children : false;
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2017-10-10 04:32:55 +08:00
|
|
|
int n = 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
struct rb_node *nd;
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
|
2010-08-11 02:49:07 +08:00
|
|
|
struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = ' '; /* No children */
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
++n;
|
2017-03-26 04:34:29 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
/* We need this because we may not have children */
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
if (folded_sign == '+')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folded_sign == '-') /* Have children and they're unfolded */
|
|
|
|
n += callchain_node__count_rows_rb_tree(child);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2015-11-09 13:45:43 +08:00
|
|
|
static int callchain_node__count_flat_rows(struct callchain_node *node)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = 0;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &node->parent_val, list) {
|
|
|
|
if (!folded_sign) {
|
|
|
|
/* only check first chain list entry */
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
if (folded_sign == '+')
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &node->val, list) {
|
|
|
|
if (!folded_sign) {
|
|
|
|
/* node->parent_val list might be empty */
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
if (folded_sign == '+')
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2015-11-09 13:45:44 +08:00
|
|
|
static int callchain_node__count_folded_rows(struct callchain_node *node __maybe_unused)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
static int callchain_node__count_rows(struct callchain_node *node)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
bool unfolded = false;
|
2017-10-10 04:32:55 +08:00
|
|
|
int n = 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2015-11-09 13:45:43 +08:00
|
|
|
if (callchain_param.mode == CHAIN_FLAT)
|
|
|
|
return callchain_node__count_flat_rows(node);
|
2015-11-09 13:45:44 +08:00
|
|
|
else if (callchain_param.mode == CHAIN_FOLDED)
|
|
|
|
return callchain_node__count_folded_rows(node);
|
2015-11-09 13:45:43 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
list_for_each_entry(chain, &node->val, list) {
|
|
|
|
++n;
|
2017-03-26 04:34:29 +08:00
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
unfolded = chain->unfolded;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unfolded)
|
|
|
|
n += callchain_node__count_rows_rb_tree(node);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain__count_rows(struct rb_root *chain)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
n += callchain_node__count_rows(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2016-02-24 23:13:43 +08:00
|
|
|
static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he,
|
|
|
|
bool include_children)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
struct rb_node *node;
|
|
|
|
struct hist_entry *child;
|
|
|
|
|
|
|
|
if (he->leaf)
|
|
|
|
return callchain__count_rows(&he->sorted_chain);
|
|
|
|
|
2016-02-26 20:13:19 +08:00
|
|
|
if (he->has_no_entry)
|
|
|
|
return 1;
|
|
|
|
|
2018-12-07 03:18:18 +08:00
|
|
|
node = rb_first_cached(&he->hroot_out);
|
2016-02-24 23:13:43 +08:00
|
|
|
while (node) {
|
|
|
|
float percent;
|
|
|
|
|
|
|
|
child = rb_entry(node, struct hist_entry, rb_node);
|
|
|
|
percent = hist_entry__get_percent_limit(child);
|
|
|
|
|
|
|
|
if (!child->filtered && percent >= hb->min_pcnt) {
|
|
|
|
count++;
|
|
|
|
|
|
|
|
if (include_children && child->unfolded)
|
|
|
|
count += hierarchy_count_rows(hb, child, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
node = rb_next(node);
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
static bool hist_entry__toggle_fold(struct hist_entry *he)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2015-05-05 22:55:46 +08:00
|
|
|
if (!he)
|
2012-04-05 04:21:31 +08:00
|
|
|
return false;
|
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
if (!he->has_children)
|
2010-08-11 02:49:07 +08:00
|
|
|
return false;
|
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
he->unfolded = !he->unfolded;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool callchain_list__toggle_fold(struct callchain_list *cl)
|
|
|
|
{
|
|
|
|
if (!cl)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!cl->has_children)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
cl->unfolded = !cl->unfolded;
|
2010-08-11 02:49:07 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2012-05-30 09:42:18 +08:00
|
|
|
struct rb_node *nd = rb_first(&node->rb_root);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
|
2010-08-11 02:49:07 +08:00
|
|
|
struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
struct callchain_list *chain;
|
2010-08-26 03:05:36 +08:00
|
|
|
bool first = true;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
2015-05-05 22:55:46 +08:00
|
|
|
chain->has_children = chain->list.next != &child->val ||
|
2010-08-26 03:05:36 +08:00
|
|
|
!RB_EMPTY_ROOT(&child->rb_root);
|
2010-08-11 02:49:07 +08:00
|
|
|
} else
|
2015-05-05 22:55:46 +08:00
|
|
|
chain->has_children = chain->list.next == &child->val &&
|
2010-08-26 03:05:36 +08:00
|
|
|
!RB_EMPTY_ROOT(&child->rb_root);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
callchain_node__init_have_children_rb_tree(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 16:13:27 +08:00
|
|
|
static void callchain_node__init_have_children(struct callchain_node *node,
|
|
|
|
bool has_sibling)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
|
2014-11-24 16:13:27 +08:00
|
|
|
chain = list_entry(node->val.next, struct callchain_list, list);
|
2015-05-05 22:55:46 +08:00
|
|
|
chain->has_children = has_sibling;
|
2014-11-24 16:13:27 +08:00
|
|
|
|
2016-03-31 03:02:45 +08:00
|
|
|
if (!list_empty(&node->val)) {
|
2014-08-13 14:02:41 +08:00
|
|
|
chain = list_entry(node->val.prev, struct callchain_list, list);
|
2015-05-05 22:55:46 +08:00
|
|
|
chain->has_children = !RB_EMPTY_ROOT(&node->rb_root);
|
2014-08-13 14:02:41 +08:00
|
|
|
}
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
callchain_node__init_have_children_rb_tree(node);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void callchain__init_have_children(struct rb_root *root)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2014-11-24 16:13:27 +08:00
|
|
|
struct rb_node *nd = rb_first(root);
|
|
|
|
bool has_sibling = nd && rb_next(nd);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
for (nd = rb_first(root); nd; nd = rb_next(nd)) {
|
2010-08-11 02:49:07 +08:00
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
2014-11-24 16:13:27 +08:00
|
|
|
callchain_node__init_have_children(node, has_sibling);
|
2015-11-09 13:45:44 +08:00
|
|
|
if (callchain_param.mode == CHAIN_FLAT ||
|
|
|
|
callchain_param.mode == CHAIN_FOLDED)
|
2015-11-09 13:45:43 +08:00
|
|
|
callchain_node__make_parent_list(node);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void hist_entry__init_have_children(struct hist_entry *he)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2016-02-24 23:13:43 +08:00
|
|
|
if (he->init_have_children)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (he->leaf) {
|
2015-05-05 22:55:46 +08:00
|
|
|
he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
|
2012-05-30 09:42:18 +08:00
|
|
|
callchain__init_have_children(&he->sorted_chain);
|
2016-02-24 23:13:43 +08:00
|
|
|
} else {
|
2018-12-07 03:18:18 +08:00
|
|
|
he->has_children = !RB_EMPTY_ROOT(&he->hroot_out.rb_root);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
2016-02-24 23:13:43 +08:00
|
|
|
|
|
|
|
he->init_have_children = true;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static bool hist_browser__toggle_fold(struct hist_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2015-05-05 22:55:46 +08:00
|
|
|
struct hist_entry *he = browser->he_selection;
|
|
|
|
struct map_symbol *ms = browser->selection;
|
|
|
|
struct callchain_list *cl = container_of(ms, struct callchain_list, ms);
|
|
|
|
bool has_children;
|
|
|
|
|
2015-12-07 10:35:44 +08:00
|
|
|
if (!he || !ms)
|
|
|
|
return false;
|
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
if (ms == &he->ms)
|
|
|
|
has_children = hist_entry__toggle_fold(he);
|
|
|
|
else
|
|
|
|
has_children = callchain_list__toggle_fold(cl);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
if (has_children) {
|
2016-02-24 23:13:43 +08:00
|
|
|
int child_rows = 0;
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
hist_entry__init_have_children(he);
|
2014-04-22 14:56:17 +08:00
|
|
|
browser->b.nr_entries -= he->nr_rows;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-02-24 23:13:43 +08:00
|
|
|
if (he->leaf)
|
|
|
|
browser->nr_callchain_rows -= he->nr_rows;
|
2010-08-11 02:49:07 +08:00
|
|
|
else
|
2016-02-24 23:13:43 +08:00
|
|
|
browser->nr_hierarchy_entries -= he->nr_rows;
|
|
|
|
|
|
|
|
if (symbol_conf.report_hierarchy)
|
|
|
|
child_rows = hierarchy_count_rows(browser, he, true);
|
|
|
|
|
|
|
|
if (he->unfolded) {
|
|
|
|
if (he->leaf)
|
2017-10-10 04:32:55 +08:00
|
|
|
he->nr_rows = callchain__count_rows(
|
|
|
|
&he->sorted_chain);
|
2016-02-24 23:13:43 +08:00
|
|
|
else
|
|
|
|
he->nr_rows = hierarchy_count_rows(browser, he, false);
|
|
|
|
|
|
|
|
/* account grand children */
|
|
|
|
if (symbol_conf.report_hierarchy)
|
|
|
|
browser->b.nr_entries += child_rows - he->nr_rows;
|
2016-02-26 20:13:19 +08:00
|
|
|
|
|
|
|
if (!he->leaf && he->nr_rows == 0) {
|
|
|
|
he->has_no_entry = true;
|
|
|
|
he->nr_rows = 1;
|
|
|
|
}
|
2016-02-24 23:13:43 +08:00
|
|
|
} else {
|
|
|
|
if (symbol_conf.report_hierarchy)
|
|
|
|
browser->b.nr_entries -= child_rows - he->nr_rows;
|
|
|
|
|
2016-02-26 20:13:19 +08:00
|
|
|
if (he->has_no_entry)
|
|
|
|
he->has_no_entry = false;
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
he->nr_rows = 0;
|
2016-02-24 23:13:43 +08:00
|
|
|
}
|
2014-04-22 14:56:17 +08:00
|
|
|
|
|
|
|
browser->b.nr_entries += he->nr_rows;
|
2016-02-24 23:13:43 +08:00
|
|
|
|
|
|
|
if (he->leaf)
|
|
|
|
browser->nr_callchain_rows += he->nr_rows;
|
|
|
|
else
|
|
|
|
browser->nr_hierarchy_entries += he->nr_rows;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If it doesn't have children, no toggling performed */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
|
2010-08-26 04:18:35 +08:00
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
struct rb_node *nd;
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
|
2010-08-26 04:18:35 +08:00
|
|
|
struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
bool has_children = false;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
++n;
|
2015-05-05 22:55:46 +08:00
|
|
|
callchain_list__set_folding(chain, unfold);
|
|
|
|
has_children = chain->has_children;
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (has_children)
|
|
|
|
n += callchain_node__set_folding_rb_tree(child, unfold);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
bool has_children = false;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &node->val, list) {
|
|
|
|
++n;
|
2015-05-05 22:55:46 +08:00
|
|
|
callchain_list__set_folding(chain, unfold);
|
|
|
|
has_children = chain->has_children;
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (has_children)
|
|
|
|
n += callchain_node__set_folding_rb_tree(node, unfold);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain__set_folding(struct rb_root *chain, bool unfold)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
n += callchain_node__set_folding(node, unfold);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2016-02-24 23:13:44 +08:00
|
|
|
static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he,
|
|
|
|
bool unfold __maybe_unused)
|
|
|
|
{
|
|
|
|
float percent;
|
|
|
|
struct rb_node *nd;
|
|
|
|
struct hist_entry *child;
|
|
|
|
int n = 0;
|
|
|
|
|
2018-12-07 03:18:18 +08:00
|
|
|
for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) {
|
2016-02-24 23:13:44 +08:00
|
|
|
child = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
percent = hist_entry__get_percent_limit(child);
|
|
|
|
if (!child->filtered && percent >= hb->min_pcnt)
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2017-01-20 17:20:29 +08:00
|
|
|
static void __hist_entry__set_folding(struct hist_entry *he,
|
|
|
|
struct hist_browser *hb, bool unfold)
|
2010-08-26 04:18:35 +08:00
|
|
|
{
|
2012-05-30 09:42:18 +08:00
|
|
|
hist_entry__init_have_children(he);
|
2015-05-05 22:55:46 +08:00
|
|
|
he->unfolded = unfold ? he->has_children : false;
|
2010-08-26 04:18:35 +08:00
|
|
|
|
2015-05-05 22:55:46 +08:00
|
|
|
if (he->has_children) {
|
2016-02-24 23:13:44 +08:00
|
|
|
int n;
|
|
|
|
|
|
|
|
if (he->leaf)
|
|
|
|
n = callchain__set_folding(&he->sorted_chain, unfold);
|
|
|
|
else
|
|
|
|
n = hierarchy_set_folding(hb, he, unfold);
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
he->nr_rows = unfold ? n : 0;
|
2010-08-26 04:18:35 +08:00
|
|
|
} else
|
2012-05-30 09:42:18 +08:00
|
|
|
he->nr_rows = 0;
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
|
2017-01-20 17:20:29 +08:00
|
|
|
static void hist_entry__set_folding(struct hist_entry *he,
|
|
|
|
struct hist_browser *browser, bool unfold)
|
|
|
|
{
|
|
|
|
double percent;
|
|
|
|
|
|
|
|
percent = hist_entry__get_percent_limit(he);
|
|
|
|
if (he->filtered || percent < browser->min_pcnt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
__hist_entry__set_folding(he, browser, unfold);
|
|
|
|
|
|
|
|
if (!he->depth || unfold)
|
|
|
|
browser->nr_hierarchy_entries++;
|
|
|
|
if (he->leaf)
|
|
|
|
browser->nr_callchain_rows += he->nr_rows;
|
|
|
|
else if (unfold && !hist_entry__has_hierarchy_children(he, browser->min_pcnt)) {
|
|
|
|
browser->nr_hierarchy_entries++;
|
|
|
|
he->has_no_entry = true;
|
|
|
|
he->nr_rows = 1;
|
|
|
|
} else
|
|
|
|
he->has_no_entry = false;
|
|
|
|
}
|
|
|
|
|
2014-04-22 14:56:17 +08:00
|
|
|
static void
|
|
|
|
__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
|
2010-08-26 04:18:35 +08:00
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
2016-02-24 23:13:44 +08:00
|
|
|
struct hist_entry *he;
|
2010-08-26 04:18:35 +08:00
|
|
|
|
2018-12-07 03:18:18 +08:00
|
|
|
nd = rb_first_cached(&browser->hists->entries);
|
2016-02-24 23:13:44 +08:00
|
|
|
while (nd) {
|
|
|
|
he = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
|
|
|
/* set folding state even if it's currently folded */
|
|
|
|
nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD);
|
|
|
|
|
|
|
|
hist_entry__set_folding(he, browser, unfold);
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
|
2010-08-26 04:18:35 +08:00
|
|
|
{
|
2016-02-24 23:13:44 +08:00
|
|
|
browser->nr_hierarchy_entries = 0;
|
2014-04-22 14:56:17 +08:00
|
|
|
browser->nr_callchain_rows = 0;
|
|
|
|
__hist_browser__set_folding(browser, unfold);
|
|
|
|
|
|
|
|
browser->b.nr_entries = hist_browser__nr_entries(browser);
|
2010-08-26 04:18:35 +08:00
|
|
|
/* Go to the start, we may be way after valid entries after a collapse */
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__reset_index(&browser->b);
|
2010-08-26 04:18:35 +08:00
|
|
|
}
|
|
|
|
|
2017-01-20 17:20:30 +08:00
|
|
|
static void hist_browser__set_folding_selected(struct hist_browser *browser, bool unfold)
|
|
|
|
{
|
|
|
|
if (!browser->he_selection)
|
|
|
|
return;
|
|
|
|
|
|
|
|
hist_entry__set_folding(browser->he_selection, browser, unfold);
|
|
|
|
browser->b.nr_entries = hist_browser__nr_entries(browser);
|
|
|
|
}
|
|
|
|
|
2011-10-29 22:15:04 +08:00
|
|
|
static void ui_browser__warn_lost_events(struct ui_browser *browser)
|
|
|
|
{
|
|
|
|
ui_browser__warning(browser, 4,
|
|
|
|
"Events are being lost, check IO/CPU overload!\n\n"
|
|
|
|
"You may want to run 'perf' using a RT scheduler policy:\n\n"
|
|
|
|
" perf top -r 80\n\n"
|
|
|
|
"Or reduce the sampling frequency.");
|
|
|
|
}
|
|
|
|
|
2016-06-21 05:58:15 +08:00
|
|
|
static int hist_browser__title(struct hist_browser *browser, char *bf, size_t size)
|
|
|
|
{
|
|
|
|
return browser->title ? browser->title(browser, bf, size) : 0;
|
|
|
|
}
|
|
|
|
|
2018-01-19 05:26:29 +08:00
|
|
|
int hist_browser__run(struct hist_browser *browser, const char *help,
|
|
|
|
bool warn_lost_event)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2010-08-11 21:07:43 +08:00
|
|
|
int key;
|
2011-10-06 06:11:32 +08:00
|
|
|
char title[160];
|
2015-04-22 15:18:15 +08:00
|
|
|
struct hist_browser_timer *hbt = browser->hbt;
|
2012-11-02 13:50:05 +08:00
|
|
|
int delay_secs = hbt ? hbt->refresh : 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->b.entries = &browser->hists->entries;
|
2014-04-22 14:56:17 +08:00
|
|
|
browser->b.nr_entries = hist_browser__nr_entries(browser);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-06-21 05:58:15 +08:00
|
|
|
hist_browser__title(browser, title, sizeof(title));
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-01-11 18:53:14 +08:00
|
|
|
if (ui_browser__show(&browser->b, title, "%s", help) < 0)
|
2010-08-11 02:49:07 +08:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
while (1) {
|
2012-05-30 09:42:18 +08:00
|
|
|
key = ui_browser__run(&browser->b, delay_secs);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2010-08-11 21:07:43 +08:00
|
|
|
switch (key) {
|
2013-05-14 10:09:05 +08:00
|
|
|
case K_TIMER: {
|
|
|
|
u64 nr_entries;
|
2012-11-02 13:50:05 +08:00
|
|
|
hbt->timer(hbt->arg);
|
2013-05-14 10:09:05 +08:00
|
|
|
|
2016-10-07 13:04:12 +08:00
|
|
|
if (hist_browser__has_filter(browser) ||
|
|
|
|
symbol_conf.report_hierarchy)
|
2014-04-22 13:05:35 +08:00
|
|
|
hist_browser__update_nr_entries(browser);
|
2013-05-14 10:09:05 +08:00
|
|
|
|
2014-04-22 14:56:17 +08:00
|
|
|
nr_entries = hist_browser__nr_entries(browser);
|
2013-05-14 10:09:05 +08:00
|
|
|
ui_browser__update_nr_entries(&browser->b, nr_entries);
|
2011-10-29 22:15:04 +08:00
|
|
|
|
2018-01-19 05:26:29 +08:00
|
|
|
if (warn_lost_event &&
|
|
|
|
(browser->hists->stats.nr_lost_warned !=
|
|
|
|
browser->hists->stats.nr_events[PERF_RECORD_LOST])) {
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->hists->stats.nr_lost_warned =
|
|
|
|
browser->hists->stats.nr_events[PERF_RECORD_LOST];
|
|
|
|
ui_browser__warn_lost_events(&browser->b);
|
2011-10-29 22:15:04 +08:00
|
|
|
}
|
|
|
|
|
2016-06-21 05:58:15 +08:00
|
|
|
hist_browser__title(browser, title, sizeof(title));
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__show_title(&browser->b, title);
|
2011-10-06 06:11:32 +08:00
|
|
|
continue;
|
2013-05-14 10:09:05 +08:00
|
|
|
}
|
2010-08-11 02:50:07 +08:00
|
|
|
case 'D': { /* Debug */
|
2010-08-11 02:49:07 +08:00
|
|
|
static int seq;
|
2012-05-30 09:42:18 +08:00
|
|
|
struct hist_entry *h = rb_entry(browser->b.top,
|
2010-08-11 02:49:07 +08:00
|
|
|
struct hist_entry, rb_node);
|
|
|
|
ui_helpline__pop();
|
2018-04-06 22:56:11 +08:00
|
|
|
ui_helpline__fpush("%d: nr_ent=(%d,%d), etl: %d, rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
|
2012-05-30 09:42:18 +08:00
|
|
|
seq++, browser->b.nr_entries,
|
|
|
|
browser->hists->nr_entries,
|
2018-04-06 22:56:11 +08:00
|
|
|
browser->b.extra_title_lines,
|
perf ui browser: Add ->rows to disambiguate from ->height
The ui_browser->height is about the whole browser "window", including
any header, status lines or any other space needed for some "Yes", "No",
etc buttons a descendent browser, like hist_browser, may have.
Since the navigation is done mostly on the ui_browser methods, it needs
to know how many rows are on the screen, while details about what other
components are, say, if a header (that may be composed of multiple
lines, etc) is present.
Besides this we'll need to add a ui_browser->refresh_dimensions() hook
so that browsers like hist_browser can update ->rows in response to
screen resizes, this will come in a follow up patch.
This patch just adds ->rows and updates it when updating ->height, keeps
using ->height for the only other widget that can come with ui_browser,
the scrollbar, that goes on using all the height on the rightmost column
in the screen, using ->rows for the keyboard navigation needs.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-xexmwg1mv7u03j5imn66jdak@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2014-07-01 22:07:54 +08:00
|
|
|
browser->b.rows,
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->b.index,
|
|
|
|
browser->b.top_idx,
|
2010-08-11 02:49:07 +08:00
|
|
|
h->row_offset, h->nr_rows);
|
|
|
|
}
|
2010-08-26 04:18:35 +08:00
|
|
|
break;
|
|
|
|
case 'C':
|
|
|
|
/* Collapse the whole world. */
|
2012-05-30 09:42:18 +08:00
|
|
|
hist_browser__set_folding(browser, false);
|
2010-08-26 04:18:35 +08:00
|
|
|
break;
|
2017-01-20 17:20:30 +08:00
|
|
|
case 'c':
|
|
|
|
/* Collapse the selected entry. */
|
|
|
|
hist_browser__set_folding_selected(browser, false);
|
|
|
|
break;
|
2010-08-26 04:18:35 +08:00
|
|
|
case 'E':
|
|
|
|
/* Expand the whole world. */
|
2012-05-30 09:42:18 +08:00
|
|
|
hist_browser__set_folding(browser, true);
|
2010-08-26 04:18:35 +08:00
|
|
|
break;
|
2017-01-20 17:20:30 +08:00
|
|
|
case 'e':
|
|
|
|
/* Expand the selected entry. */
|
|
|
|
hist_browser__set_folding_selected(browser, true);
|
|
|
|
break;
|
2014-06-14 21:44:52 +08:00
|
|
|
case 'H':
|
|
|
|
browser->show_headers = !browser->show_headers;
|
|
|
|
hist_browser__update_rows(browser);
|
|
|
|
break;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_ENTER:
|
2012-05-30 09:42:18 +08:00
|
|
|
if (hist_browser__toggle_fold(browser))
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
/* fall thru */
|
|
|
|
default:
|
2010-08-11 21:07:43 +08:00
|
|
|
goto out;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
}
|
2010-08-11 21:07:43 +08:00
|
|
|
out:
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__hide(&browser->b);
|
2010-08-11 21:07:43 +08:00
|
|
|
return key;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2014-08-22 08:13:21 +08:00
|
|
|
struct callchain_print_arg {
|
|
|
|
/* for hists browser */
|
|
|
|
off_t row_offset;
|
|
|
|
bool is_current_entry;
|
|
|
|
|
|
|
|
/* for file dump */
|
|
|
|
FILE *fp;
|
|
|
|
int printed;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef void (*print_callchain_entry_fn)(struct hist_browser *browser,
|
|
|
|
struct callchain_list *chain,
|
|
|
|
const char *str, int offset,
|
|
|
|
unsigned short row,
|
|
|
|
struct callchain_print_arg *arg);
|
|
|
|
|
2014-08-20 16:07:57 +08:00
|
|
|
static void hist_browser__show_callchain_entry(struct hist_browser *browser,
|
|
|
|
struct callchain_list *chain,
|
2014-08-22 08:13:21 +08:00
|
|
|
const char *str, int offset,
|
|
|
|
unsigned short row,
|
|
|
|
struct callchain_print_arg *arg)
|
2014-08-20 16:07:57 +08:00
|
|
|
{
|
|
|
|
int color, width;
|
2014-08-22 08:13:21 +08:00
|
|
|
char folded_sign = callchain_list__folded(chain);
|
2015-03-20 03:07:21 +08:00
|
|
|
bool show_annotated = browser->show_dso && chain->ms.sym && symbol__annotation(chain->ms.sym)->src;
|
2014-08-20 16:07:57 +08:00
|
|
|
|
|
|
|
color = HE_COLORSET_NORMAL;
|
|
|
|
width = browser->b.width - (offset + 2);
|
|
|
|
if (ui_browser__is_current_entry(&browser->b, row)) {
|
|
|
|
browser->selection = &chain->ms;
|
|
|
|
color = HE_COLORSET_SELECTED;
|
2014-08-22 08:13:21 +08:00
|
|
|
arg->is_current_entry = true;
|
2014-08-20 16:07:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ui_browser__set_color(&browser->b, color);
|
2018-04-03 02:48:18 +08:00
|
|
|
ui_browser__gotorc(&browser->b, row, 0);
|
2015-08-11 23:24:27 +08:00
|
|
|
ui_browser__write_nstring(&browser->b, " ", offset);
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(&browser->b, "%c", folded_sign);
|
2015-03-20 03:07:21 +08:00
|
|
|
ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' ');
|
2015-08-11 23:24:27 +08:00
|
|
|
ui_browser__write_nstring(&browser->b, str, width);
|
2014-08-20 16:07:57 +08:00
|
|
|
}
|
|
|
|
|
2014-08-22 08:13:21 +08:00
|
|
|
static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused,
|
|
|
|
struct callchain_list *chain,
|
|
|
|
const char *str, int offset,
|
|
|
|
unsigned short row __maybe_unused,
|
|
|
|
struct callchain_print_arg *arg)
|
|
|
|
{
|
|
|
|
char folded_sign = callchain_list__folded(chain);
|
|
|
|
|
|
|
|
arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ",
|
|
|
|
folded_sign, str);
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef bool (*check_output_full_fn)(struct hist_browser *browser,
|
|
|
|
unsigned short row);
|
|
|
|
|
|
|
|
static bool hist_browser__check_output_full(struct hist_browser *browser,
|
|
|
|
unsigned short row)
|
|
|
|
{
|
|
|
|
return browser->b.rows == row;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused,
|
|
|
|
unsigned short row __maybe_unused)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
#define LEVEL_OFFSET_STEP 3
|
|
|
|
|
2015-11-09 13:45:42 +08:00
|
|
|
static int hist_browser__show_callchain_list(struct hist_browser *browser,
|
|
|
|
struct callchain_node *node,
|
|
|
|
struct callchain_list *chain,
|
|
|
|
unsigned short row, u64 total,
|
|
|
|
bool need_percent, int offset,
|
|
|
|
print_callchain_entry_fn print,
|
|
|
|
struct callchain_print_arg *arg)
|
|
|
|
{
|
|
|
|
char bf[1024], *alloc_str;
|
2016-10-31 09:19:53 +08:00
|
|
|
char buf[64], *alloc_str2;
|
2015-11-09 13:45:42 +08:00
|
|
|
const char *str;
|
2017-10-10 04:32:55 +08:00
|
|
|
int ret = 1;
|
2015-11-09 13:45:42 +08:00
|
|
|
|
|
|
|
if (arg->row_offset != 0) {
|
|
|
|
arg->row_offset--;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc_str = NULL;
|
2016-10-31 09:19:53 +08:00
|
|
|
alloc_str2 = NULL;
|
|
|
|
|
2015-11-09 13:45:42 +08:00
|
|
|
str = callchain_list__sym_name(chain, bf, sizeof(bf),
|
|
|
|
browser->show_dso);
|
|
|
|
|
2016-10-31 09:19:53 +08:00
|
|
|
if (symbol_conf.show_branchflag_count) {
|
2017-08-07 21:05:15 +08:00
|
|
|
callchain_list_counts__printf_value(chain, NULL,
|
|
|
|
buf, sizeof(buf));
|
2016-10-31 09:19:53 +08:00
|
|
|
|
|
|
|
if (asprintf(&alloc_str2, "%s%s", str, buf) < 0)
|
|
|
|
str = "Not enough memory!";
|
|
|
|
else
|
|
|
|
str = alloc_str2;
|
|
|
|
}
|
2015-11-09 13:45:42 +08:00
|
|
|
|
2016-10-31 09:19:53 +08:00
|
|
|
if (need_percent) {
|
2015-11-09 13:45:42 +08:00
|
|
|
callchain_node__scnprintf_value(node, buf, sizeof(buf),
|
|
|
|
total);
|
|
|
|
|
|
|
|
if (asprintf(&alloc_str, "%s %s", buf, str) < 0)
|
|
|
|
str = "Not enough memory!";
|
|
|
|
else
|
|
|
|
str = alloc_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
print(browser, chain, str, offset, row, arg);
|
|
|
|
free(alloc_str);
|
2016-10-31 09:19:53 +08:00
|
|
|
free(alloc_str2);
|
2017-03-26 04:34:29 +08:00
|
|
|
|
2017-10-10 04:32:55 +08:00
|
|
|
return ret;
|
2015-11-09 13:45:42 +08:00
|
|
|
}
|
|
|
|
|
2016-01-27 23:40:56 +08:00
|
|
|
static bool check_percent_display(struct rb_node *node, u64 parent_total)
|
|
|
|
{
|
|
|
|
struct callchain_node *child;
|
|
|
|
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (rb_next(node))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
return callchain_cumul_hits(child) != parent_total;
|
|
|
|
}
|
|
|
|
|
2015-11-09 13:45:43 +08:00
|
|
|
static int hist_browser__show_callchain_flat(struct hist_browser *browser,
|
|
|
|
struct rb_root *root,
|
|
|
|
unsigned short row, u64 total,
|
2016-01-27 23:40:56 +08:00
|
|
|
u64 parent_total,
|
2015-11-09 13:45:43 +08:00
|
|
|
print_callchain_entry_fn print,
|
|
|
|
struct callchain_print_arg *arg,
|
|
|
|
check_output_full_fn is_output_full)
|
|
|
|
{
|
|
|
|
struct rb_node *node;
|
|
|
|
int first_row = row, offset = LEVEL_OFFSET_STEP;
|
|
|
|
bool need_percent;
|
|
|
|
|
|
|
|
node = rb_first(root);
|
2016-01-27 23:40:56 +08:00
|
|
|
need_percent = check_percent_display(node, parent_total);
|
2015-11-09 13:45:43 +08:00
|
|
|
|
|
|
|
while (node) {
|
|
|
|
struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
struct rb_node *next = rb_next(node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
int first = true;
|
|
|
|
int extra_offset = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->parent_val, list) {
|
|
|
|
bool was_first = first;
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else if (need_percent)
|
|
|
|
extra_offset = LEVEL_OFFSET_STEP;
|
|
|
|
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
|
|
|
|
row += hist_browser__show_callchain_list(browser, child,
|
|
|
|
chain, row, total,
|
|
|
|
was_first && need_percent,
|
|
|
|
offset + extra_offset,
|
|
|
|
print, arg);
|
|
|
|
|
|
|
|
if (is_output_full(browser, row))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (folded_sign == '+')
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
bool was_first = first;
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else if (need_percent)
|
|
|
|
extra_offset = LEVEL_OFFSET_STEP;
|
|
|
|
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
|
|
|
|
row += hist_browser__show_callchain_list(browser, child,
|
|
|
|
chain, row, total,
|
|
|
|
was_first && need_percent,
|
|
|
|
offset + extra_offset,
|
|
|
|
print, arg);
|
|
|
|
|
|
|
|
if (is_output_full(browser, row))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (folded_sign == '+')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next:
|
|
|
|
if (is_output_full(browser, row))
|
|
|
|
break;
|
|
|
|
node = next;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
2015-11-09 13:45:44 +08:00
|
|
|
static char *hist_browser__folded_callchain_str(struct hist_browser *browser,
|
|
|
|
struct callchain_list *chain,
|
|
|
|
char *value_str, char *old_str)
|
|
|
|
{
|
|
|
|
char bf[1024];
|
|
|
|
const char *str;
|
|
|
|
char *new;
|
|
|
|
|
|
|
|
str = callchain_list__sym_name(chain, bf, sizeof(bf),
|
|
|
|
browser->show_dso);
|
|
|
|
if (old_str) {
|
|
|
|
if (asprintf(&new, "%s%s%s", old_str,
|
|
|
|
symbol_conf.field_sep ?: ";", str) < 0)
|
|
|
|
new = NULL;
|
|
|
|
} else {
|
|
|
|
if (value_str) {
|
|
|
|
if (asprintf(&new, "%s %s", value_str, str) < 0)
|
|
|
|
new = NULL;
|
|
|
|
} else {
|
|
|
|
if (asprintf(&new, "%s", str) < 0)
|
|
|
|
new = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__show_callchain_folded(struct hist_browser *browser,
|
|
|
|
struct rb_root *root,
|
|
|
|
unsigned short row, u64 total,
|
2016-01-27 23:40:56 +08:00
|
|
|
u64 parent_total,
|
2015-11-09 13:45:44 +08:00
|
|
|
print_callchain_entry_fn print,
|
|
|
|
struct callchain_print_arg *arg,
|
|
|
|
check_output_full_fn is_output_full)
|
|
|
|
{
|
|
|
|
struct rb_node *node;
|
|
|
|
int first_row = row, offset = LEVEL_OFFSET_STEP;
|
|
|
|
bool need_percent;
|
|
|
|
|
|
|
|
node = rb_first(root);
|
2016-01-27 23:40:56 +08:00
|
|
|
need_percent = check_percent_display(node, parent_total);
|
2015-11-09 13:45:44 +08:00
|
|
|
|
|
|
|
while (node) {
|
|
|
|
struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
struct rb_node *next = rb_next(node);
|
|
|
|
struct callchain_list *chain, *first_chain = NULL;
|
|
|
|
int first = true;
|
|
|
|
char *value_str = NULL, *value_str_alloc = NULL;
|
|
|
|
char *chain_str = NULL, *chain_str_alloc = NULL;
|
|
|
|
|
|
|
|
if (arg->row_offset != 0) {
|
|
|
|
arg->row_offset--;
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_percent) {
|
|
|
|
char buf[64];
|
|
|
|
|
|
|
|
callchain_node__scnprintf_value(child, buf, sizeof(buf), total);
|
|
|
|
if (asprintf(&value_str, "%s", buf) < 0) {
|
|
|
|
value_str = (char *)"<...>";
|
|
|
|
goto do_print;
|
|
|
|
}
|
|
|
|
value_str_alloc = value_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->parent_val, list) {
|
|
|
|
chain_str = hist_browser__folded_callchain_str(browser,
|
|
|
|
chain, value_str, chain_str);
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
first_chain = chain;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chain_str == NULL) {
|
|
|
|
chain_str = (char *)"Not enough memory!";
|
|
|
|
goto do_print;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_str_alloc = chain_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
chain_str = hist_browser__folded_callchain_str(browser,
|
|
|
|
chain, value_str, chain_str);
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
first_chain = chain;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chain_str == NULL) {
|
|
|
|
chain_str = (char *)"Not enough memory!";
|
|
|
|
goto do_print;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_str_alloc = chain_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
do_print:
|
|
|
|
print(browser, first_chain, chain_str, offset, row++, arg);
|
|
|
|
free(value_str_alloc);
|
|
|
|
free(chain_str_alloc);
|
|
|
|
|
|
|
|
next:
|
|
|
|
if (is_output_full(browser, row))
|
|
|
|
break;
|
|
|
|
node = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
2016-01-27 23:40:54 +08:00
|
|
|
static int hist_browser__show_callchain_graph(struct hist_browser *browser,
|
2014-08-21 09:15:45 +08:00
|
|
|
struct rb_root *root, int level,
|
2014-08-22 08:13:21 +08:00
|
|
|
unsigned short row, u64 total,
|
2016-01-27 23:40:55 +08:00
|
|
|
u64 parent_total,
|
2014-08-22 08:13:21 +08:00
|
|
|
print_callchain_entry_fn print,
|
|
|
|
struct callchain_print_arg *arg,
|
|
|
|
check_output_full_fn is_output_full)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
|
|
|
struct rb_node *node;
|
2014-08-20 16:07:57 +08:00
|
|
|
int first_row = row, offset = level * LEVEL_OFFSET_STEP;
|
2014-11-24 16:13:26 +08:00
|
|
|
bool need_percent;
|
2016-01-27 23:40:55 +08:00
|
|
|
u64 percent_total = total;
|
|
|
|
|
|
|
|
if (callchain_param.mode == CHAIN_GRAPH_REL)
|
|
|
|
percent_total = parent_total;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2014-08-21 09:15:45 +08:00
|
|
|
node = rb_first(root);
|
2016-01-27 23:40:56 +08:00
|
|
|
need_percent = check_percent_display(node, parent_total);
|
2014-11-24 16:13:26 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
while (node) {
|
|
|
|
struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
struct rb_node *next = rb_next(node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
int first = true;
|
|
|
|
int extra_offset = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
bool was_first = first;
|
|
|
|
|
2010-08-26 03:30:03 +08:00
|
|
|
if (first)
|
2010-08-11 02:49:07 +08:00
|
|
|
first = false;
|
2014-11-24 16:13:26 +08:00
|
|
|
else if (need_percent)
|
2010-08-11 02:49:07 +08:00
|
|
|
extra_offset = LEVEL_OFFSET_STEP;
|
|
|
|
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
2014-08-21 09:15:45 +08:00
|
|
|
|
2015-11-09 13:45:42 +08:00
|
|
|
row += hist_browser__show_callchain_list(browser, child,
|
2016-01-27 23:40:55 +08:00
|
|
|
chain, row, percent_total,
|
2015-11-09 13:45:42 +08:00
|
|
|
was_first && need_percent,
|
|
|
|
offset + extra_offset,
|
|
|
|
print, arg);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2015-11-09 13:45:42 +08:00
|
|
|
if (is_output_full(browser, row))
|
2010-08-11 02:49:07 +08:00
|
|
|
goto out;
|
2015-11-09 13:45:42 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
if (folded_sign == '+')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folded_sign == '-') {
|
|
|
|
const int new_level = level + (extra_offset ? 2 : 1);
|
|
|
|
|
2016-01-27 23:40:54 +08:00
|
|
|
row += hist_browser__show_callchain_graph(browser, &child->rb_root,
|
2016-01-27 23:40:55 +08:00
|
|
|
new_level, row, total,
|
|
|
|
child->children_hit,
|
2014-08-22 08:13:21 +08:00
|
|
|
print, arg, is_output_full);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
2014-08-22 08:13:21 +08:00
|
|
|
if (is_output_full(browser, row))
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
2014-08-21 09:15:45 +08:00
|
|
|
node = next;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
2014-08-21 09:15:45 +08:00
|
|
|
out:
|
2010-08-11 02:49:07 +08:00
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
2016-01-27 23:40:54 +08:00
|
|
|
static int hist_browser__show_callchain(struct hist_browser *browser,
|
|
|
|
struct hist_entry *entry, int level,
|
|
|
|
unsigned short row,
|
|
|
|
print_callchain_entry_fn print,
|
|
|
|
struct callchain_print_arg *arg,
|
|
|
|
check_output_full_fn is_output_full)
|
|
|
|
{
|
|
|
|
u64 total = hists__total_period(entry->hists);
|
2016-01-27 23:40:55 +08:00
|
|
|
u64 parent_total;
|
2016-01-27 23:40:54 +08:00
|
|
|
int printed;
|
|
|
|
|
2016-01-27 23:40:55 +08:00
|
|
|
if (symbol_conf.cumulate_callchain)
|
|
|
|
parent_total = entry->stat_acc->period;
|
|
|
|
else
|
|
|
|
parent_total = entry->stat.period;
|
2016-01-27 23:40:54 +08:00
|
|
|
|
|
|
|
if (callchain_param.mode == CHAIN_FLAT) {
|
|
|
|
printed = hist_browser__show_callchain_flat(browser,
|
2016-01-27 23:40:55 +08:00
|
|
|
&entry->sorted_chain, row,
|
|
|
|
total, parent_total, print, arg,
|
|
|
|
is_output_full);
|
2016-01-27 23:40:54 +08:00
|
|
|
} else if (callchain_param.mode == CHAIN_FOLDED) {
|
|
|
|
printed = hist_browser__show_callchain_folded(browser,
|
2016-01-27 23:40:55 +08:00
|
|
|
&entry->sorted_chain, row,
|
|
|
|
total, parent_total, print, arg,
|
|
|
|
is_output_full);
|
2016-01-27 23:40:54 +08:00
|
|
|
} else {
|
|
|
|
printed = hist_browser__show_callchain_graph(browser,
|
2016-01-27 23:40:55 +08:00
|
|
|
&entry->sorted_chain, level, row,
|
|
|
|
total, parent_total, print, arg,
|
|
|
|
is_output_full);
|
2016-01-27 23:40:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (arg->is_current_entry)
|
|
|
|
browser->he_selection = entry;
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
2013-01-22 17:09:38 +08:00
|
|
|
struct hpp_arg {
|
|
|
|
struct ui_browser *b;
|
|
|
|
char folded_sign;
|
|
|
|
bool current_entry;
|
|
|
|
};
|
|
|
|
|
2016-09-22 23:36:35 +08:00
|
|
|
int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
|
2014-03-03 09:14:05 +08:00
|
|
|
{
|
|
|
|
struct hpp_arg *arg = hpp->ptr;
|
2014-07-31 13:47:36 +08:00
|
|
|
int ret, len;
|
2014-03-03 09:14:05 +08:00
|
|
|
va_list args;
|
|
|
|
double percent;
|
2013-01-22 17:09:39 +08:00
|
|
|
|
2014-03-03 09:14:05 +08:00
|
|
|
va_start(args, fmt);
|
2014-07-31 13:47:36 +08:00
|
|
|
len = va_arg(args, int);
|
2014-03-03 09:14:05 +08:00
|
|
|
percent = va_arg(args, double);
|
|
|
|
va_end(args);
|
2013-01-22 17:09:39 +08:00
|
|
|
|
2014-03-03 09:14:05 +08:00
|
|
|
ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
|
2013-01-22 17:09:39 +08:00
|
|
|
|
2014-07-31 13:47:36 +08:00
|
|
|
ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent);
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(arg->b, "%s", hpp->buf);
|
2013-01-22 17:09:35 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-03 16:05:19 +08:00
|
|
|
#define __HPP_COLOR_PERCENT_FN(_type, _field) \
|
2013-01-22 17:09:35 +08:00
|
|
|
static u64 __hpp_get_##_field(struct hist_entry *he) \
|
|
|
|
{ \
|
|
|
|
return he->stat._field; \
|
|
|
|
} \
|
|
|
|
\
|
2013-02-01 06:31:11 +08:00
|
|
|
static int \
|
2014-07-31 13:47:38 +08:00
|
|
|
hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
|
2013-02-01 06:31:11 +08:00
|
|
|
struct perf_hpp *hpp, \
|
|
|
|
struct hist_entry *he) \
|
2012-09-03 10:53:09 +08:00
|
|
|
{ \
|
2014-07-31 13:47:38 +08:00
|
|
|
return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \
|
|
|
|
__hpp__slsmg_color_printf, true); \
|
2012-09-03 10:53:09 +08:00
|
|
|
}
|
|
|
|
|
2013-10-30 15:12:59 +08:00
|
|
|
#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
|
|
|
|
static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
|
|
|
|
{ \
|
|
|
|
return he->stat_acc->_field; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
static int \
|
2014-07-31 13:47:38 +08:00
|
|
|
hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
|
2013-10-30 15:12:59 +08:00
|
|
|
struct perf_hpp *hpp, \
|
|
|
|
struct hist_entry *he) \
|
|
|
|
{ \
|
|
|
|
if (!symbol_conf.cumulate_callchain) { \
|
2015-08-11 23:50:55 +08:00
|
|
|
struct hpp_arg *arg = hpp->ptr; \
|
2014-07-31 13:47:38 +08:00
|
|
|
int len = fmt->user_len ?: fmt->len; \
|
2014-07-31 13:47:36 +08:00
|
|
|
int ret = scnprintf(hpp->buf, hpp->size, \
|
2014-07-31 13:47:38 +08:00
|
|
|
"%*s", len, "N/A"); \
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(arg->b, "%s", hpp->buf); \
|
2013-10-30 15:12:59 +08:00
|
|
|
\
|
|
|
|
return ret; \
|
|
|
|
} \
|
2014-07-31 13:47:38 +08:00
|
|
|
return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \
|
|
|
|
" %*.2f%%", __hpp__slsmg_color_printf, true); \
|
2013-10-30 15:12:59 +08:00
|
|
|
}
|
|
|
|
|
2014-03-03 16:05:19 +08:00
|
|
|
__HPP_COLOR_PERCENT_FN(overhead, period)
|
|
|
|
__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
|
|
|
|
__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
|
|
|
|
__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
|
|
|
|
__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
|
2013-10-30 15:12:59 +08:00
|
|
|
__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
|
2012-09-03 10:53:09 +08:00
|
|
|
|
2013-01-22 17:09:35 +08:00
|
|
|
#undef __HPP_COLOR_PERCENT_FN
|
2013-10-30 15:12:59 +08:00
|
|
|
#undef __HPP_COLOR_ACC_PERCENT_FN
|
2012-09-03 10:53:09 +08:00
|
|
|
|
|
|
|
void hist_browser__init_hpp(void)
|
|
|
|
{
|
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD].color =
|
|
|
|
hist_browser__hpp_color_overhead;
|
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
|
|
|
|
hist_browser__hpp_color_overhead_sys;
|
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
|
|
|
|
hist_browser__hpp_color_overhead_us;
|
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
|
|
|
|
hist_browser__hpp_color_overhead_guest_sys;
|
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
|
|
|
|
hist_browser__hpp_color_overhead_guest_us;
|
2013-10-30 15:12:59 +08:00
|
|
|
perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
|
|
|
|
hist_browser__hpp_color_overhead_acc;
|
2019-03-11 22:44:58 +08:00
|
|
|
|
|
|
|
res_sample_init();
|
2012-09-03 10:53:09 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static int hist_browser__show_entry(struct hist_browser *browser,
|
2010-08-11 02:49:07 +08:00
|
|
|
struct hist_entry *entry,
|
|
|
|
unsigned short row)
|
|
|
|
{
|
2012-10-13 06:06:16 +08:00
|
|
|
int printed = 0;
|
2012-09-12 14:35:06 +08:00
|
|
|
int width = browser->b.width;
|
2010-08-11 02:49:07 +08:00
|
|
|
char folded_sign = ' ';
|
2012-05-30 09:42:18 +08:00
|
|
|
bool current_entry = ui_browser__is_current_entry(&browser->b, row);
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
bool use_callchain = hist_entry__has_callchains(entry) && symbol_conf.use_callchain;
|
2010-08-11 02:49:07 +08:00
|
|
|
off_t row_offset = entry->row_offset;
|
2012-10-16 05:14:35 +08:00
|
|
|
bool first = true;
|
2012-10-13 06:06:16 +08:00
|
|
|
struct perf_hpp_fmt *fmt;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
|
|
|
if (current_entry) {
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->he_selection = entry;
|
|
|
|
browser->selection = &entry->ms;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
if (use_callchain) {
|
2010-08-26 03:30:03 +08:00
|
|
|
hist_entry__init_have_children(entry);
|
2010-08-11 02:49:07 +08:00
|
|
|
folded_sign = hist_entry__folded(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (row_offset == 0) {
|
2013-01-22 17:09:38 +08:00
|
|
|
struct hpp_arg arg = {
|
2014-03-03 16:05:19 +08:00
|
|
|
.b = &browser->b,
|
2013-01-22 17:09:38 +08:00
|
|
|
.folded_sign = folded_sign,
|
|
|
|
.current_entry = current_entry,
|
|
|
|
};
|
perf hists browser: Implement horizontal scrolling
Do it using the recently introduced ui_brower scrolling mode, setting
ui_browser.columns to the number of sort columns and then, when
rendering each line, skipping as many initial columns as the user
pressed the right arrow.
As the user presses the left arrow, the ui_browser code will remove the
scrolling counter and the left scrolling takes place.
The right arrow key was an alias for ENTER, so people used to press it
may get a bit annoyed at first, sorry! Ditto for ESC and the left key.
Callchains can be left as is or we can, when rendering the Symbol
column, store the at what position on the screen it is and then
using ui_browser__gotorc() to print it from there, i.e. the callchain
would move around with the symbol.
Leaving it as is, i.e. at a fixed position, close to the left, saves
precious screen real state for it, so I'm inclined to leave it as is
now.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Borislav Petkov <bp@suse.de>
Cc: Chandler Carruth <chandlerc@gmail.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-ccqq9sabgfge5dwbqjwh71ij@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-08-12 04:22:43 +08:00
|
|
|
int column = 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
ui_browser__gotorc(&browser->b, row, 0);
|
2011-10-19 00:31:35 +08:00
|
|
|
|
2016-01-18 17:24:23 +08:00
|
|
|
hists__for_each_format(browser->hists, fmt) {
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
char s[2048];
|
|
|
|
struct perf_hpp hpp = {
|
|
|
|
.buf = s,
|
|
|
|
.size = sizeof(s),
|
|
|
|
.ptr = &arg,
|
|
|
|
};
|
|
|
|
|
2015-12-23 01:07:08 +08:00
|
|
|
if (perf_hpp__should_skip(fmt, entry->hists) ||
|
|
|
|
column++ < browser->b.horiz_scroll)
|
2014-03-18 12:00:59 +08:00
|
|
|
continue;
|
|
|
|
|
2014-03-03 16:05:19 +08:00
|
|
|
if (current_entry && browser->b.navkeypressed) {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_SELECTED);
|
|
|
|
} else {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (first) {
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
if (use_callchain) {
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(&browser->b, "%c ", folded_sign);
|
2014-03-03 16:05:19 +08:00
|
|
|
width -= 2;
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
} else {
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(&browser->b, " ");
|
2012-09-03 10:53:09 +08:00
|
|
|
width -= 2;
|
|
|
|
}
|
2011-10-19 00:31:35 +08:00
|
|
|
|
2012-10-13 06:06:16 +08:00
|
|
|
if (fmt->color) {
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
int ret = fmt->color(fmt, &hpp, entry);
|
|
|
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
|
|
|
|
/*
|
|
|
|
* fmt->color() already used ui_browser to
|
|
|
|
* print the non alignment bits, skip it (+ret):
|
|
|
|
*/
|
|
|
|
ui_browser__printf(&browser->b, "%s", s + ret);
|
2012-09-03 10:53:09 +08:00
|
|
|
} else {
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, fmt->entry(fmt, &hpp, entry));
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(&browser->b, "%s", s);
|
2012-09-03 10:53:09 +08:00
|
|
|
}
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
width -= hpp.buf - s;
|
2011-10-20 00:37:59 +08:00
|
|
|
}
|
|
|
|
|
2012-09-03 10:53:09 +08:00
|
|
|
/* The scroll bar isn't being used */
|
|
|
|
if (!browser->b.navkeypressed)
|
|
|
|
width += 1;
|
|
|
|
|
2015-08-11 23:24:27 +08:00
|
|
|
ui_browser__write_nstring(&browser->b, "", width);
|
2014-03-03 15:16:20 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
++row;
|
|
|
|
++printed;
|
|
|
|
} else
|
|
|
|
--row_offset;
|
|
|
|
|
perf ui browser: Add ->rows to disambiguate from ->height
The ui_browser->height is about the whole browser "window", including
any header, status lines or any other space needed for some "Yes", "No",
etc buttons a descendent browser, like hist_browser, may have.
Since the navigation is done mostly on the ui_browser methods, it needs
to know how many rows are on the screen, while details about what other
components are, say, if a header (that may be composed of multiple
lines, etc) is present.
Besides this we'll need to add a ui_browser->refresh_dimensions() hook
so that browsers like hist_browser can update ->rows in response to
screen resizes, this will come in a follow up patch.
This patch just adds ->rows and updates it when updating ->height, keeps
using ->height for the only other widget that can come with ui_browser,
the scrollbar, that goes on using all the height on the rightmost column
in the screen, using ->rows for the keyboard navigation needs.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-xexmwg1mv7u03j5imn66jdak@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2014-07-01 22:07:54 +08:00
|
|
|
if (folded_sign == '-' && row != browser->b.rows) {
|
2014-08-22 08:13:21 +08:00
|
|
|
struct callchain_print_arg arg = {
|
|
|
|
.row_offset = row_offset,
|
|
|
|
.is_current_entry = current_entry,
|
|
|
|
};
|
2014-08-21 09:15:45 +08:00
|
|
|
|
2017-10-10 04:32:55 +08:00
|
|
|
printed += hist_browser__show_callchain(browser,
|
|
|
|
entry, 1, row,
|
|
|
|
hist_browser__show_callchain_entry,
|
|
|
|
&arg,
|
|
|
|
hist_browser__check_output_full);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
static int hist_browser__show_hierarchy_entry(struct hist_browser *browser,
|
|
|
|
struct hist_entry *entry,
|
|
|
|
unsigned short row,
|
2016-03-08 03:44:48 +08:00
|
|
|
int level)
|
2016-02-24 23:13:45 +08:00
|
|
|
{
|
|
|
|
int printed = 0;
|
|
|
|
int width = browser->b.width;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
bool current_entry = ui_browser__is_current_entry(&browser->b, row);
|
|
|
|
off_t row_offset = entry->row_offset;
|
|
|
|
bool first = true;
|
|
|
|
struct perf_hpp_fmt *fmt;
|
2016-03-08 03:44:50 +08:00
|
|
|
struct perf_hpp_list_node *fmt_node;
|
2016-02-24 23:13:45 +08:00
|
|
|
struct hpp_arg arg = {
|
|
|
|
.b = &browser->b,
|
|
|
|
.current_entry = current_entry,
|
|
|
|
};
|
|
|
|
int column = 0;
|
2016-03-08 03:44:48 +08:00
|
|
|
int hierarchy_indent = (entry->hists->nr_hpp_node - 2) * HIERARCHY_INDENT;
|
2016-02-24 23:13:45 +08:00
|
|
|
|
|
|
|
if (current_entry) {
|
|
|
|
browser->he_selection = entry;
|
|
|
|
browser->selection = &entry->ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
hist_entry__init_have_children(entry);
|
|
|
|
folded_sign = hist_entry__folded(entry);
|
|
|
|
arg.folded_sign = folded_sign;
|
|
|
|
|
|
|
|
if (entry->leaf && row_offset) {
|
|
|
|
row_offset--;
|
|
|
|
goto show_callchain;
|
|
|
|
}
|
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
ui_browser__gotorc(&browser->b, row, 0);
|
2016-02-24 23:13:45 +08:00
|
|
|
|
|
|
|
if (current_entry && browser->b.navkeypressed)
|
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED);
|
|
|
|
else
|
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
|
|
|
|
|
|
|
|
ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT);
|
|
|
|
width -= level * HIERARCHY_INDENT;
|
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
/* the first hpp_list_node is for overhead columns */
|
|
|
|
fmt_node = list_first_entry(&entry->hists->hpp_formats,
|
|
|
|
struct perf_hpp_list_node, list);
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
|
2016-02-24 23:13:45 +08:00
|
|
|
char s[2048];
|
|
|
|
struct perf_hpp hpp = {
|
|
|
|
.buf = s,
|
|
|
|
.size = sizeof(s),
|
|
|
|
.ptr = &arg,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (perf_hpp__should_skip(fmt, entry->hists) ||
|
|
|
|
column++ < browser->b.horiz_scroll)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (current_entry && browser->b.navkeypressed) {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_SELECTED);
|
|
|
|
} else {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (first) {
|
2016-11-08 21:08:30 +08:00
|
|
|
ui_browser__printf(&browser->b, "%c ", folded_sign);
|
|
|
|
width -= 2;
|
2016-02-24 23:13:45 +08:00
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
ui_browser__printf(&browser->b, " ");
|
|
|
|
width -= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fmt->color) {
|
|
|
|
int ret = fmt->color(fmt, &hpp, entry);
|
|
|
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
|
|
|
|
/*
|
|
|
|
* fmt->color() already used ui_browser to
|
|
|
|
* print the non alignment bits, skip it (+ret):
|
|
|
|
*/
|
|
|
|
ui_browser__printf(&browser->b, "%s", s + ret);
|
|
|
|
} else {
|
|
|
|
int ret = fmt->entry(fmt, &hpp, entry);
|
|
|
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
|
|
|
|
ui_browser__printf(&browser->b, "%s", s);
|
|
|
|
}
|
|
|
|
width -= hpp.buf - s;
|
|
|
|
}
|
|
|
|
|
2016-11-08 21:08:32 +08:00
|
|
|
if (!first) {
|
|
|
|
ui_browser__write_nstring(&browser->b, "", hierarchy_indent);
|
|
|
|
width -= hierarchy_indent;
|
|
|
|
}
|
2016-02-24 23:13:45 +08:00
|
|
|
|
|
|
|
if (column >= browser->b.horiz_scroll) {
|
|
|
|
char s[2048];
|
|
|
|
struct perf_hpp hpp = {
|
|
|
|
.buf = s,
|
|
|
|
.size = sizeof(s),
|
|
|
|
.ptr = &arg,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (current_entry && browser->b.navkeypressed) {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_SELECTED);
|
|
|
|
} else {
|
|
|
|
ui_browser__set_color(&browser->b,
|
|
|
|
HE_COLORSET_NORMAL);
|
|
|
|
}
|
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
perf_hpp_list__for_each_format(entry->hpp_list, fmt) {
|
2016-11-08 21:08:31 +08:00
|
|
|
if (first) {
|
|
|
|
ui_browser__printf(&browser->b, "%c ", folded_sign);
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
ui_browser__write_nstring(&browser->b, "", 2);
|
|
|
|
}
|
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
width -= 2;
|
2016-02-24 23:13:45 +08:00
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
/*
|
|
|
|
* No need to call hist_entry__snprintf_alignment()
|
|
|
|
* since this fmt is always the last column in the
|
|
|
|
* hierarchy mode.
|
|
|
|
*/
|
|
|
|
if (fmt->color) {
|
|
|
|
width -= fmt->color(fmt, &hpp, entry);
|
|
|
|
} else {
|
|
|
|
int i = 0;
|
2016-02-27 02:52:45 +08:00
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
width -= fmt->entry(fmt, &hpp, entry);
|
2019-06-26 22:42:03 +08:00
|
|
|
ui_browser__printf(&browser->b, "%s", skip_spaces(s));
|
2016-02-27 02:52:45 +08:00
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
while (isspace(s[i++]))
|
|
|
|
width++;
|
|
|
|
}
|
2016-02-24 23:13:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The scroll bar isn't being used */
|
|
|
|
if (!browser->b.navkeypressed)
|
|
|
|
width += 1;
|
|
|
|
|
|
|
|
ui_browser__write_nstring(&browser->b, "", width);
|
|
|
|
|
|
|
|
++row;
|
|
|
|
++printed;
|
|
|
|
|
|
|
|
show_callchain:
|
|
|
|
if (entry->leaf && folded_sign == '-' && row != browser->b.rows) {
|
|
|
|
struct callchain_print_arg carg = {
|
|
|
|
.row_offset = row_offset,
|
|
|
|
};
|
|
|
|
|
|
|
|
printed += hist_browser__show_callchain(browser, entry,
|
|
|
|
level + 1, row,
|
|
|
|
hist_browser__show_callchain_entry, &carg,
|
|
|
|
hist_browser__check_output_full);
|
|
|
|
}
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
2016-02-26 20:13:19 +08:00
|
|
|
static int hist_browser__show_no_entry(struct hist_browser *browser,
|
2016-03-08 03:44:48 +08:00
|
|
|
unsigned short row, int level)
|
2016-02-26 20:13:19 +08:00
|
|
|
{
|
|
|
|
int width = browser->b.width;
|
|
|
|
bool current_entry = ui_browser__is_current_entry(&browser->b, row);
|
|
|
|
bool first = true;
|
|
|
|
int column = 0;
|
|
|
|
int ret;
|
|
|
|
struct perf_hpp_fmt *fmt;
|
2016-03-08 03:44:50 +08:00
|
|
|
struct perf_hpp_list_node *fmt_node;
|
2016-03-08 03:44:48 +08:00
|
|
|
int indent = browser->hists->nr_hpp_node - 2;
|
2016-02-26 20:13:19 +08:00
|
|
|
|
|
|
|
if (current_entry) {
|
|
|
|
browser->he_selection = NULL;
|
|
|
|
browser->selection = NULL;
|
|
|
|
}
|
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
ui_browser__gotorc(&browser->b, row, 0);
|
2016-02-26 20:13:19 +08:00
|
|
|
|
|
|
|
if (current_entry && browser->b.navkeypressed)
|
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED);
|
|
|
|
else
|
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
|
|
|
|
|
|
|
|
ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT);
|
|
|
|
width -= level * HIERARCHY_INDENT;
|
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
/* the first hpp_list_node is for overhead columns */
|
|
|
|
fmt_node = list_first_entry(&browser->hists->hpp_formats,
|
|
|
|
struct perf_hpp_list_node, list);
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
|
2016-02-26 20:13:19 +08:00
|
|
|
if (perf_hpp__should_skip(fmt, browser->hists) ||
|
|
|
|
column++ < browser->b.horiz_scroll)
|
|
|
|
continue;
|
|
|
|
|
2016-06-15 02:19:20 +08:00
|
|
|
ret = fmt->width(fmt, NULL, browser->hists);
|
2016-02-26 20:13:19 +08:00
|
|
|
|
|
|
|
if (first) {
|
|
|
|
/* for folded sign */
|
|
|
|
first = false;
|
|
|
|
ret++;
|
|
|
|
} else {
|
|
|
|
/* space between columns */
|
|
|
|
ret += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
ui_browser__write_nstring(&browser->b, "", ret);
|
|
|
|
width -= ret;
|
|
|
|
}
|
|
|
|
|
2016-03-08 03:44:48 +08:00
|
|
|
ui_browser__write_nstring(&browser->b, "", indent * HIERARCHY_INDENT);
|
|
|
|
width -= indent * HIERARCHY_INDENT;
|
2016-02-26 20:13:19 +08:00
|
|
|
|
|
|
|
if (column >= browser->b.horiz_scroll) {
|
|
|
|
char buf[32];
|
|
|
|
|
|
|
|
ret = snprintf(buf, sizeof(buf), "no entry >= %.2f%%", browser->min_pcnt);
|
|
|
|
ui_browser__printf(&browser->b, " %s", buf);
|
|
|
|
width -= ret + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The scroll bar isn't being used */
|
|
|
|
if (!browser->b.navkeypressed)
|
|
|
|
width += 1;
|
|
|
|
|
|
|
|
ui_browser__write_nstring(&browser->b, "", width);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-06-14 21:44:52 +08:00
|
|
|
static int advance_hpp_check(struct perf_hpp *hpp, int inc)
|
|
|
|
{
|
|
|
|
advance_hpp(hpp, inc);
|
|
|
|
return hpp->size <= 0;
|
|
|
|
}
|
|
|
|
|
2016-08-07 23:28:28 +08:00
|
|
|
static int
|
|
|
|
hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf,
|
|
|
|
size_t size, int line)
|
2014-06-14 21:44:52 +08:00
|
|
|
{
|
perf hists browser: Implement horizontal scrolling
Do it using the recently introduced ui_brower scrolling mode, setting
ui_browser.columns to the number of sort columns and then, when
rendering each line, skipping as many initial columns as the user
pressed the right arrow.
As the user presses the left arrow, the ui_browser code will remove the
scrolling counter and the left scrolling takes place.
The right arrow key was an alias for ENTER, so people used to press it
may get a bit annoyed at first, sorry! Ditto for ESC and the left key.
Callchains can be left as is or we can, when rendering the Symbol
column, store the at what position on the screen it is and then
using ui_browser__gotorc() to print it from there, i.e. the callchain
would move around with the symbol.
Leaving it as is, i.e. at a fixed position, close to the left, saves
precious screen real state for it, so I'm inclined to leave it as is
now.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Borislav Petkov <bp@suse.de>
Cc: Chandler Carruth <chandlerc@gmail.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-ccqq9sabgfge5dwbqjwh71ij@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-08-12 04:22:43 +08:00
|
|
|
struct hists *hists = browser->hists;
|
2014-06-14 21:44:52 +08:00
|
|
|
struct perf_hpp dummy_hpp = {
|
|
|
|
.buf = buf,
|
|
|
|
.size = size,
|
|
|
|
};
|
|
|
|
struct perf_hpp_fmt *fmt;
|
|
|
|
size_t ret = 0;
|
perf hists browser: Implement horizontal scrolling
Do it using the recently introduced ui_brower scrolling mode, setting
ui_browser.columns to the number of sort columns and then, when
rendering each line, skipping as many initial columns as the user
pressed the right arrow.
As the user presses the left arrow, the ui_browser code will remove the
scrolling counter and the left scrolling takes place.
The right arrow key was an alias for ENTER, so people used to press it
may get a bit annoyed at first, sorry! Ditto for ESC and the left key.
Callchains can be left as is or we can, when rendering the Symbol
column, store the at what position on the screen it is and then
using ui_browser__gotorc() to print it from there, i.e. the callchain
would move around with the symbol.
Leaving it as is, i.e. at a fixed position, close to the left, saves
precious screen real state for it, so I'm inclined to leave it as is
now.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Borislav Petkov <bp@suse.de>
Cc: Chandler Carruth <chandlerc@gmail.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-ccqq9sabgfge5dwbqjwh71ij@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2015-08-12 04:22:43 +08:00
|
|
|
int column = 0;
|
2016-08-07 23:28:30 +08:00
|
|
|
int span = 0;
|
2014-06-14 21:44:52 +08:00
|
|
|
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
if (hists__has_callchains(hists) && symbol_conf.use_callchain) {
|
2014-06-14 21:44:52 +08:00
|
|
|
ret = scnprintf(buf, size, " ");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-01-18 17:24:23 +08:00
|
|
|
hists__for_each_format(browser->hists, fmt) {
|
2015-12-23 01:07:08 +08:00
|
|
|
if (perf_hpp__should_skip(fmt, hists) || column++ < browser->b.horiz_scroll)
|
2014-06-14 21:44:52 +08:00
|
|
|
continue;
|
|
|
|
|
2016-08-07 23:28:30 +08:00
|
|
|
ret = fmt->header(fmt, &dummy_hpp, hists, line, &span);
|
2014-06-14 21:44:52 +08:00
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
|
2016-08-07 23:28:30 +08:00
|
|
|
if (span)
|
|
|
|
continue;
|
|
|
|
|
2014-06-14 21:44:52 +08:00
|
|
|
ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-02-24 23:13:46 +08:00
|
|
|
static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser, char *buf, size_t size)
|
|
|
|
{
|
|
|
|
struct hists *hists = browser->hists;
|
|
|
|
struct perf_hpp dummy_hpp = {
|
|
|
|
.buf = buf,
|
|
|
|
.size = size,
|
|
|
|
};
|
|
|
|
struct perf_hpp_fmt *fmt;
|
2016-03-08 03:44:50 +08:00
|
|
|
struct perf_hpp_list_node *fmt_node;
|
2016-02-24 23:13:46 +08:00
|
|
|
size_t ret = 0;
|
|
|
|
int column = 0;
|
2016-03-08 03:44:48 +08:00
|
|
|
int indent = hists->nr_hpp_node - 2;
|
2016-03-08 03:44:50 +08:00
|
|
|
bool first_node, first_col;
|
2016-02-24 23:13:46 +08:00
|
|
|
|
2016-11-08 21:08:30 +08:00
|
|
|
ret = scnprintf(buf, size, " ");
|
2016-02-24 23:13:46 +08:00
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
return ret;
|
|
|
|
|
2016-11-08 21:08:32 +08:00
|
|
|
first_node = true;
|
2016-03-08 03:44:50 +08:00
|
|
|
/* the first hpp_list_node is for overhead columns */
|
|
|
|
fmt_node = list_first_entry(&hists->hpp_formats,
|
|
|
|
struct perf_hpp_list_node, list);
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
|
2016-02-24 23:13:46 +08:00
|
|
|
if (column++ < browser->b.horiz_scroll)
|
|
|
|
continue;
|
|
|
|
|
2016-08-07 23:28:30 +08:00
|
|
|
ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL);
|
2016-02-24 23:13:46 +08:00
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
|
|
|
|
ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
2016-11-08 21:08:32 +08:00
|
|
|
|
|
|
|
first_node = false;
|
2016-02-24 23:13:46 +08:00
|
|
|
}
|
|
|
|
|
2016-11-08 21:08:32 +08:00
|
|
|
if (!first_node) {
|
|
|
|
ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "%*s",
|
|
|
|
indent * HIERARCHY_INDENT, "");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
return ret;
|
|
|
|
}
|
2016-02-24 23:13:46 +08:00
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
first_node = true;
|
|
|
|
list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
|
|
|
|
if (!first_node) {
|
2016-02-24 23:13:46 +08:00
|
|
|
ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " / ");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
}
|
2016-03-08 03:44:50 +08:00
|
|
|
first_node = false;
|
2016-02-24 23:13:46 +08:00
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
first_col = true;
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
|
|
|
|
char *start;
|
2016-02-24 23:13:46 +08:00
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
if (perf_hpp__should_skip(fmt, hists))
|
|
|
|
continue;
|
2016-02-27 02:52:45 +08:00
|
|
|
|
2016-03-08 03:44:50 +08:00
|
|
|
if (!first_col) {
|
|
|
|
ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "+");
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
first_col = false;
|
2016-02-27 02:52:45 +08:00
|
|
|
|
2016-08-07 23:28:30 +08:00
|
|
|
ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL);
|
2016-03-08 03:44:50 +08:00
|
|
|
dummy_hpp.buf[ret] = '\0';
|
|
|
|
|
2019-06-26 23:06:20 +08:00
|
|
|
start = strim(dummy_hpp.buf);
|
2016-03-08 03:44:50 +08:00
|
|
|
ret = strlen(start);
|
|
|
|
|
|
|
|
if (start != dummy_hpp.buf)
|
|
|
|
memmove(dummy_hpp.buf, start, ret + 1);
|
|
|
|
|
|
|
|
if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
|
break;
|
|
|
|
}
|
2016-02-24 23:13:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-06-15 02:19:13 +08:00
|
|
|
static void hists_browser__hierarchy_headers(struct hist_browser *browser)
|
2014-06-14 21:44:52 +08:00
|
|
|
{
|
2014-06-14 21:44:52 +08:00
|
|
|
char headers[1024];
|
|
|
|
|
2016-06-15 02:19:13 +08:00
|
|
|
hists_browser__scnprintf_hierarchy_headers(browser, headers,
|
|
|
|
sizeof(headers));
|
|
|
|
|
2014-06-14 21:44:52 +08:00
|
|
|
ui_browser__gotorc(&browser->b, 0, 0);
|
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
|
2015-08-11 23:24:27 +08:00
|
|
|
ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
|
2014-06-14 21:44:52 +08:00
|
|
|
}
|
|
|
|
|
2016-06-15 02:19:13 +08:00
|
|
|
static void hists_browser__headers(struct hist_browser *browser)
|
|
|
|
{
|
2016-08-07 23:28:28 +08:00
|
|
|
struct hists *hists = browser->hists;
|
|
|
|
struct perf_hpp_list *hpp_list = hists->hpp_list;
|
2016-06-15 02:19:13 +08:00
|
|
|
|
2016-08-07 23:28:28 +08:00
|
|
|
int line;
|
2016-06-15 02:19:13 +08:00
|
|
|
|
2016-08-07 23:28:28 +08:00
|
|
|
for (line = 0; line < hpp_list->nr_header_lines; line++) {
|
|
|
|
char headers[1024];
|
|
|
|
|
|
|
|
hists_browser__scnprintf_headers(browser, headers,
|
|
|
|
sizeof(headers), line);
|
|
|
|
|
2018-04-03 02:48:18 +08:00
|
|
|
ui_browser__gotorc_title(&browser->b, line, 0);
|
2016-08-07 23:28:28 +08:00
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
|
|
|
|
ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
|
|
|
|
}
|
2016-06-15 02:19:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void hist_browser__show_headers(struct hist_browser *browser)
|
|
|
|
{
|
|
|
|
if (symbol_conf.report_hierarchy)
|
|
|
|
hists_browser__hierarchy_headers(browser);
|
|
|
|
else
|
|
|
|
hists_browser__headers(browser);
|
|
|
|
}
|
|
|
|
|
2011-10-14 20:31:53 +08:00
|
|
|
static void ui_browser__hists_init_top(struct ui_browser *browser)
|
|
|
|
{
|
|
|
|
if (browser->top == NULL) {
|
|
|
|
struct hist_browser *hb;
|
|
|
|
|
|
|
|
hb = container_of(browser, struct hist_browser, b);
|
2018-12-07 03:18:18 +08:00
|
|
|
browser->top = rb_first_cached(&hb->hists->entries);
|
2011-10-14 20:31:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static unsigned int hist_browser__refresh(struct ui_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
|
|
|
unsigned row = 0;
|
|
|
|
struct rb_node *nd;
|
2012-05-30 09:42:18 +08:00
|
|
|
struct hist_browser *hb = container_of(browser, struct hist_browser, b);
|
2016-08-07 23:28:26 +08:00
|
|
|
|
perf hists browser: Remove leftover from row returned from refresh
The per-browser screen refresh routine (ui_browser->refresh()) should
return the first row that should be cleaned after the rows just printed,
in case not all rows available on the screen gets filled.
When moving the extra title lines logic from the hists browser to the
generic ui_browser class, one piece of that logic remained in the hists
browser and then when going back from the annotate browser to the hists
browser in a case where fewer lines were displayed in the hists browser,
for instance when filtering the entries per substring, one line of the
annotate browser would remain on the screen, fix that.
Example of the screen artifact:
================================================================================
Samples: 73K of event 'cycles:ppp', 4000 Hz, Event count (approx.): 45172901394
Overhead Shared O Symbol
0.30% [kernel] [k] __indirect_thunk_start
0.09% [kernel] [k] __x86_indirect_thunk_r10
│ lfence
================================================================================
Here from 'perf top' the view was zoomed with '/thunk' to functions
having that substring, then the first was annotated and from the
annotate browser ESC was pressed, then the first lines were overwritten,
but the 'lfence' line remained due to the off by one bug fixed in this
cset.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Fixes: ef9ff6017e3c ("perf ui browser: Move the extra title lines from the hists browser")
Link: https://lkml.kernel.org/n/tip-odryfso74eaarm0z3e4v9owx@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-04-06 23:11:11 +08:00
|
|
|
if (hb->show_headers)
|
2014-06-14 21:44:52 +08:00
|
|
|
hist_browser__show_headers(hb);
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__hists_init_top(browser);
|
2015-12-07 10:35:46 +08:00
|
|
|
hb->he_selection = NULL;
|
|
|
|
hb->selection = NULL;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
for (nd = browser->top; nd; nd = rb_hierarchy_next(nd)) {
|
2010-08-11 02:49:07 +08:00
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
2013-10-31 09:17:39 +08:00
|
|
|
float percent;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
if (h->filtered) {
|
|
|
|
/* let it move to sibling */
|
|
|
|
h->unfolded = false;
|
2010-08-11 02:49:07 +08:00
|
|
|
continue;
|
2016-02-24 23:13:45 +08:00
|
|
|
}
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2013-10-31 09:17:39 +08:00
|
|
|
percent = hist_entry__get_percent_limit(h);
|
2013-05-14 10:09:04 +08:00
|
|
|
if (percent < hb->min_pcnt)
|
|
|
|
continue;
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
if (symbol_conf.report_hierarchy) {
|
|
|
|
row += hist_browser__show_hierarchy_entry(hb, h, row,
|
2016-03-08 03:44:48 +08:00
|
|
|
h->depth);
|
2016-02-26 20:13:19 +08:00
|
|
|
if (row == browser->rows)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (h->has_no_entry) {
|
2016-03-08 03:44:50 +08:00
|
|
|
hist_browser__show_no_entry(hb, row, h->depth + 1);
|
2016-02-26 20:13:19 +08:00
|
|
|
row++;
|
|
|
|
}
|
2016-02-24 23:13:45 +08:00
|
|
|
} else {
|
|
|
|
row += hist_browser__show_entry(hb, h, row);
|
|
|
|
}
|
|
|
|
|
perf ui browser: Add ->rows to disambiguate from ->height
The ui_browser->height is about the whole browser "window", including
any header, status lines or any other space needed for some "Yes", "No",
etc buttons a descendent browser, like hist_browser, may have.
Since the navigation is done mostly on the ui_browser methods, it needs
to know how many rows are on the screen, while details about what other
components are, say, if a header (that may be composed of multiple
lines, etc) is present.
Besides this we'll need to add a ui_browser->refresh_dimensions() hook
so that browsers like hist_browser can update ->rows in response to
screen resizes, this will come in a follow up patch.
This patch just adds ->rows and updates it when updating ->height, keeps
using ->height for the only other widget that can come with ui_browser,
the scrollbar, that goes on using all the height on the rightmost column
in the screen, using ->rows for the keyboard navigation needs.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/n/tip-xexmwg1mv7u03j5imn66jdak@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2014-07-01 22:07:54 +08:00
|
|
|
if (row == browser->rows)
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
perf hists browser: Remove leftover from row returned from refresh
The per-browser screen refresh routine (ui_browser->refresh()) should
return the first row that should be cleaned after the rows just printed,
in case not all rows available on the screen gets filled.
When moving the extra title lines logic from the hists browser to the
generic ui_browser class, one piece of that logic remained in the hists
browser and then when going back from the annotate browser to the hists
browser in a case where fewer lines were displayed in the hists browser,
for instance when filtering the entries per substring, one line of the
annotate browser would remain on the screen, fix that.
Example of the screen artifact:
================================================================================
Samples: 73K of event 'cycles:ppp', 4000 Hz, Event count (approx.): 45172901394
Overhead Shared O Symbol
0.30% [kernel] [k] __indirect_thunk_start
0.09% [kernel] [k] __x86_indirect_thunk_r10
│ lfence
================================================================================
Here from 'perf top' the view was zoomed with '/thunk' to functions
having that substring, then the first was annotated and from the
annotate browser ESC was pressed, then the first lines were overwritten,
but the 'lfence' line remained due to the off by one bug fixed in this
cset.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Fixes: ef9ff6017e3c ("perf ui browser: Move the extra title lines from the hists browser")
Link: https://lkml.kernel.org/n/tip-odryfso74eaarm0z3e4v9owx@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-04-06 23:11:11 +08:00
|
|
|
return row;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2013-05-14 10:09:04 +08:00
|
|
|
static struct rb_node *hists__filter_entries(struct rb_node *nd,
|
|
|
|
float min_pcnt)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
|
|
|
while (nd != NULL) {
|
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
2013-10-31 09:17:39 +08:00
|
|
|
float percent = hist_entry__get_percent_limit(h);
|
2013-05-14 10:09:04 +08:00
|
|
|
|
2014-04-16 10:16:33 +08:00
|
|
|
if (!h->filtered && percent >= min_pcnt)
|
2010-08-11 02:49:07 +08:00
|
|
|
return nd;
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
/*
|
|
|
|
* If it's filtered, its all children also were filtered.
|
|
|
|
* So move to sibling node.
|
|
|
|
*/
|
|
|
|
if (rb_next(nd))
|
|
|
|
nd = rb_next(nd);
|
|
|
|
else
|
|
|
|
nd = rb_hierarchy_next(nd);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-14 10:09:04 +08:00
|
|
|
static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
|
|
|
|
float min_pcnt)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
|
|
|
while (nd != NULL) {
|
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
2013-10-31 09:17:39 +08:00
|
|
|
float percent = hist_entry__get_percent_limit(h);
|
2013-05-14 10:09:04 +08:00
|
|
|
|
|
|
|
if (!h->filtered && percent >= min_pcnt)
|
2010-08-11 02:49:07 +08:00
|
|
|
return nd;
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
nd = rb_hierarchy_prev(nd);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static void ui_browser__hists_seek(struct ui_browser *browser,
|
2010-08-11 02:49:07 +08:00
|
|
|
off_t offset, int whence)
|
|
|
|
{
|
|
|
|
struct hist_entry *h;
|
|
|
|
struct rb_node *nd;
|
|
|
|
bool first = true;
|
2013-05-14 10:09:04 +08:00
|
|
|
struct hist_browser *hb;
|
|
|
|
|
|
|
|
hb = container_of(browser, struct hist_browser, b);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
if (browser->nr_entries == 0)
|
2011-03-05 08:19:21 +08:00
|
|
|
return;
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
ui_browser__hists_init_top(browser);
|
2011-10-14 20:31:53 +08:00
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET:
|
2013-05-14 10:09:04 +08:00
|
|
|
nd = hists__filter_entries(rb_first(browser->entries),
|
2013-10-31 09:17:39 +08:00
|
|
|
hb->min_pcnt);
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
2012-05-30 09:42:18 +08:00
|
|
|
nd = browser->top;
|
2010-08-11 02:49:07 +08:00
|
|
|
goto do_offset;
|
|
|
|
case SEEK_END:
|
2016-02-24 23:13:45 +08:00
|
|
|
nd = rb_hierarchy_last(rb_last(browser->entries));
|
|
|
|
nd = hists__filter_prev_entries(nd, hb->min_pcnt);
|
2010-08-11 02:49:07 +08:00
|
|
|
first = false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Moves not relative to the first visible entry invalidates its
|
|
|
|
* row_offset:
|
|
|
|
*/
|
2012-05-30 09:42:18 +08:00
|
|
|
h = rb_entry(browser->top, struct hist_entry, rb_node);
|
2010-08-11 02:49:07 +08:00
|
|
|
h->row_offset = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Here we have to check if nd is expanded (+), if it is we can't go
|
|
|
|
* the next top level hist_entry, instead we must compute an offset of
|
|
|
|
* what _not_ to show and not change the first visible entry.
|
|
|
|
*
|
|
|
|
* This offset increments when we are going from top to bottom and
|
|
|
|
* decreases when we're going from bottom to top.
|
|
|
|
*
|
|
|
|
* As we don't have backpointers to the top level in the callchains
|
|
|
|
* structure, we need to always print the whole hist_entry callchain,
|
|
|
|
* skipping the first ones that are before the first visible entry
|
|
|
|
* and stop when we printed enough lines to fill the screen.
|
|
|
|
*/
|
|
|
|
do_offset:
|
2015-12-07 10:35:45 +08:00
|
|
|
if (!nd)
|
|
|
|
return;
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
if (offset > 0) {
|
|
|
|
do {
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
2016-02-24 23:13:45 +08:00
|
|
|
if (h->unfolded && h->leaf) {
|
2010-08-11 02:49:07 +08:00
|
|
|
u16 remaining = h->nr_rows - h->row_offset;
|
|
|
|
if (offset > remaining) {
|
|
|
|
offset -= remaining;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset += offset;
|
|
|
|
offset = 0;
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-02-24 23:13:45 +08:00
|
|
|
nd = hists__filter_entries(rb_hierarchy_next(nd),
|
|
|
|
hb->min_pcnt);
|
2010-08-11 02:49:07 +08:00
|
|
|
if (nd == NULL)
|
|
|
|
break;
|
|
|
|
--offset;
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
} while (offset != 0);
|
|
|
|
} else if (offset < 0) {
|
|
|
|
while (1) {
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
2016-02-24 23:13:45 +08:00
|
|
|
if (h->unfolded && h->leaf) {
|
2010-08-11 02:49:07 +08:00
|
|
|
if (first) {
|
|
|
|
if (-offset > h->row_offset) {
|
|
|
|
offset += h->row_offset;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset += offset;
|
|
|
|
offset = 0;
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (-offset > h->nr_rows) {
|
|
|
|
offset += h->nr_rows;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset = h->nr_rows + offset;
|
|
|
|
offset = 0;
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
nd = hists__filter_prev_entries(rb_hierarchy_prev(nd),
|
2013-05-14 10:09:04 +08:00
|
|
|
hb->min_pcnt);
|
2010-08-11 02:49:07 +08:00
|
|
|
if (nd == NULL)
|
|
|
|
break;
|
|
|
|
++offset;
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
if (offset == 0) {
|
|
|
|
/*
|
|
|
|
* Last unfiltered hist_entry, check if it is
|
|
|
|
* unfolded, if it is then we should have
|
|
|
|
* row_offset at its last entry.
|
|
|
|
*/
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
2016-02-24 23:13:45 +08:00
|
|
|
if (h->unfolded && h->leaf)
|
2010-08-11 02:49:07 +08:00
|
|
|
h->row_offset = h->nr_rows;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
} else {
|
2012-05-30 09:42:18 +08:00
|
|
|
browser->top = nd;
|
2010-08-11 02:49:07 +08:00
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
h->row_offset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-08 06:31:28 +08:00
|
|
|
static int hist_browser__fprintf_callchain(struct hist_browser *browser,
|
2016-02-24 23:13:45 +08:00
|
|
|
struct hist_entry *he, FILE *fp,
|
|
|
|
int level)
|
2012-06-08 06:31:28 +08:00
|
|
|
{
|
2014-08-22 08:13:21 +08:00
|
|
|
struct callchain_print_arg arg = {
|
|
|
|
.fp = fp,
|
|
|
|
};
|
2012-06-08 06:31:28 +08:00
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
hist_browser__show_callchain(browser, he, level, 0,
|
2014-08-22 08:13:21 +08:00
|
|
|
hist_browser__fprintf_callchain_entry, &arg,
|
|
|
|
hist_browser__check_dump_full);
|
|
|
|
return arg.printed;
|
2012-06-08 06:31:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__fprintf_entry(struct hist_browser *browser,
|
|
|
|
struct hist_entry *he, FILE *fp)
|
|
|
|
{
|
|
|
|
char s[8192];
|
|
|
|
int printed = 0;
|
|
|
|
char folded_sign = ' ';
|
2014-03-03 15:16:20 +08:00
|
|
|
struct perf_hpp hpp = {
|
|
|
|
.buf = s,
|
|
|
|
.size = sizeof(s),
|
|
|
|
};
|
|
|
|
struct perf_hpp_fmt *fmt;
|
|
|
|
bool first = true;
|
|
|
|
int ret;
|
2012-06-08 06:31:28 +08:00
|
|
|
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
if (hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
|
2012-06-08 06:31:28 +08:00
|
|
|
folded_sign = hist_entry__folded(he);
|
|
|
|
printed += fprintf(fp, "%c ", folded_sign);
|
2016-04-18 23:24:41 +08:00
|
|
|
}
|
2012-06-08 06:31:28 +08:00
|
|
|
|
2016-01-18 17:24:23 +08:00
|
|
|
hists__for_each_format(browser->hists, fmt) {
|
2015-12-23 01:07:08 +08:00
|
|
|
if (perf_hpp__should_skip(fmt, he->hists))
|
2014-03-18 12:00:59 +08:00
|
|
|
continue;
|
|
|
|
|
2014-03-03 15:16:20 +08:00
|
|
|
if (!first) {
|
|
|
|
ret = scnprintf(hpp.buf, hpp.size, " ");
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
} else
|
|
|
|
first = false;
|
2012-06-08 06:31:28 +08:00
|
|
|
|
2014-03-03 15:16:20 +08:00
|
|
|
ret = fmt->entry(fmt, &hpp, he);
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
ret = hist_entry__snprintf_alignment(he, &hpp, fmt, ret);
|
2014-03-03 15:16:20 +08:00
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
}
|
perf hists: Do column alignment on the format iterator
We were doing column alignment in the format function for each cell,
returning a string padded with spaces so that when the next column is
printed the cursor is at its column alignment.
This ends up needlessly printing trailing spaces, do it at the format
iterator, that is where we know if it is needed, i.e. if there is more
columns to be printed.
This eliminates the need for triming lines when doing a dump using 'P'
in the TUI browser and also produces far saner results with things like
piping 'perf report' to 'less'.
Right now only the formatters for sym->name and the 'locked' column
(perf mem report), that are the ones that end up at the end of lines
in the default 'perf report', 'perf top' and 'perf mem report' tools,
the others will be done in a subsequent patch.
In the end the 'width' parameter for the formatters now mean, in
'printf' terms, the 'precision', where before it was the field 'width'.
Reported-by: Dave Jones <davej@codemonkey.org.uk>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/n/tip-s7iwl2gj23w92l6tibnrcqzr@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-02-12 04:14:13 +08:00
|
|
|
printed += fprintf(fp, "%s\n", s);
|
2012-06-08 06:31:28 +08:00
|
|
|
|
|
|
|
if (folded_sign == '-')
|
2016-02-24 23:13:45 +08:00
|
|
|
printed += hist_browser__fprintf_callchain(browser, he, fp, 1);
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser,
|
|
|
|
struct hist_entry *he,
|
2016-03-09 21:47:00 +08:00
|
|
|
FILE *fp, int level)
|
2016-02-24 23:13:45 +08:00
|
|
|
{
|
|
|
|
char s[8192];
|
|
|
|
int printed = 0;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
struct perf_hpp hpp = {
|
|
|
|
.buf = s,
|
|
|
|
.size = sizeof(s),
|
|
|
|
};
|
|
|
|
struct perf_hpp_fmt *fmt;
|
2016-03-09 21:47:00 +08:00
|
|
|
struct perf_hpp_list_node *fmt_node;
|
2016-02-24 23:13:45 +08:00
|
|
|
bool first = true;
|
|
|
|
int ret;
|
2016-03-09 21:47:00 +08:00
|
|
|
int hierarchy_indent = (he->hists->nr_hpp_node - 2) * HIERARCHY_INDENT;
|
2016-02-24 23:13:45 +08:00
|
|
|
|
|
|
|
printed = fprintf(fp, "%*s", level * HIERARCHY_INDENT, "");
|
|
|
|
|
|
|
|
folded_sign = hist_entry__folded(he);
|
|
|
|
printed += fprintf(fp, "%c", folded_sign);
|
|
|
|
|
2016-03-09 21:47:00 +08:00
|
|
|
/* the first hpp_list_node is for overhead columns */
|
|
|
|
fmt_node = list_first_entry(&he->hists->hpp_formats,
|
|
|
|
struct perf_hpp_list_node, list);
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
|
2016-02-24 23:13:45 +08:00
|
|
|
if (!first) {
|
|
|
|
ret = scnprintf(hpp.buf, hpp.size, " ");
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
} else
|
|
|
|
first = false;
|
|
|
|
|
|
|
|
ret = fmt->entry(fmt, &hpp, he);
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = scnprintf(hpp.buf, hpp.size, "%*s", hierarchy_indent, "");
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
|
2016-03-08 03:44:46 +08:00
|
|
|
perf_hpp_list__for_each_format(he->hpp_list, fmt) {
|
|
|
|
ret = scnprintf(hpp.buf, hpp.size, " ");
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
|
|
|
|
ret = fmt->entry(fmt, &hpp, he);
|
|
|
|
advance_hpp(&hpp, ret);
|
|
|
|
}
|
2016-02-24 23:13:45 +08:00
|
|
|
|
2019-06-26 23:13:13 +08:00
|
|
|
strim(s);
|
|
|
|
printed += fprintf(fp, "%s\n", s);
|
2016-02-24 23:13:45 +08:00
|
|
|
|
|
|
|
if (he->leaf && folded_sign == '-') {
|
|
|
|
printed += hist_browser__fprintf_callchain(browser, he, fp,
|
|
|
|
he->depth + 1);
|
|
|
|
}
|
2012-06-08 06:31:28 +08:00
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
|
|
|
|
{
|
2013-05-14 10:09:04 +08:00
|
|
|
struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
|
|
|
|
browser->min_pcnt);
|
2012-06-08 06:31:28 +08:00
|
|
|
int printed = 0;
|
|
|
|
|
|
|
|
while (nd) {
|
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
if (symbol_conf.report_hierarchy) {
|
|
|
|
printed += hist_browser__fprintf_hierarchy_entry(browser,
|
|
|
|
h, fp,
|
2016-03-09 21:47:00 +08:00
|
|
|
h->depth);
|
2016-02-24 23:13:45 +08:00
|
|
|
} else {
|
|
|
|
printed += hist_browser__fprintf_entry(browser, h, fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
nd = hists__filter_entries(rb_hierarchy_next(nd),
|
|
|
|
browser->min_pcnt);
|
2012-06-08 06:31:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__dump(struct hist_browser *browser)
|
|
|
|
{
|
|
|
|
char filename[64];
|
|
|
|
FILE *fp;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
|
|
|
|
if (access(filename, F_OK))
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* XXX: Just an arbitrary lazy upper limit
|
|
|
|
*/
|
|
|
|
if (++browser->print_seq == 8192) {
|
|
|
|
ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fp = fopen(filename, "w");
|
|
|
|
if (fp == NULL) {
|
|
|
|
char bf[64];
|
tools: Introduce str_error_r()
The tools so far have been using the strerror_r() GNU variant, that
returns a string, be it the buffer passed or something else.
But that, besides being tricky in cases where we expect that the
function using strerror_r() returns the error formatted in a provided
buffer (we have to check if it returned something else and copy that
instead), breaks the build on systems not using glibc, like Alpine
Linux, where musl libc is used.
So, introduce yet another wrapper, str_error_r(), that has the GNU
interface, but uses the portable XSI variant of strerror_r(), so that
users rest asured that the provided buffer is used and it is what is
returned.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: http://lkml.kernel.org/n/tip-d4t42fnf48ytlk8rjxs822tf@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2016-07-06 22:56:20 +08:00
|
|
|
const char *err = str_error_r(errno, bf, sizeof(bf));
|
2012-07-24 05:06:54 +08:00
|
|
|
ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
|
2012-06-08 06:31:28 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
++browser->print_seq;
|
|
|
|
hist_browser__fprintf(browser, fp);
|
|
|
|
fclose(fp);
|
|
|
|
ui_helpline__fpush("%s written!", filename);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-21 05:58:18 +08:00
|
|
|
void hist_browser__init(struct hist_browser *browser,
|
|
|
|
struct hists *hists)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2016-06-21 05:58:18 +08:00
|
|
|
struct perf_hpp_fmt *fmt;
|
2016-06-21 05:58:16 +08:00
|
|
|
|
2016-06-21 05:58:18 +08:00
|
|
|
browser->hists = hists;
|
|
|
|
browser->b.refresh = hist_browser__refresh;
|
|
|
|
browser->b.refresh_dimensions = hist_browser__refresh_dimensions;
|
|
|
|
browser->b.seek = ui_browser__hists_seek;
|
|
|
|
browser->b.use_navkeypressed = true;
|
|
|
|
browser->show_headers = symbol_conf.show_hist_headers;
|
2018-04-03 02:48:18 +08:00
|
|
|
hist_browser__set_title_space(browser);
|
2016-06-21 05:58:16 +08:00
|
|
|
|
2016-10-25 00:21:10 +08:00
|
|
|
if (symbol_conf.report_hierarchy) {
|
|
|
|
struct perf_hpp_list_node *fmt_node;
|
|
|
|
|
|
|
|
/* count overhead columns (in the first node) */
|
|
|
|
fmt_node = list_first_entry(&hists->hpp_formats,
|
|
|
|
struct perf_hpp_list_node, list);
|
|
|
|
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt)
|
|
|
|
++browser->b.columns;
|
|
|
|
|
|
|
|
/* add a single column for whole hierarchy sort keys*/
|
2016-06-21 05:58:18 +08:00
|
|
|
++browser->b.columns;
|
2016-10-25 00:21:10 +08:00
|
|
|
} else {
|
|
|
|
hists__for_each_format(hists, fmt)
|
|
|
|
++browser->b.columns;
|
|
|
|
}
|
2016-09-20 13:30:24 +08:00
|
|
|
|
|
|
|
hists__reset_column_width(hists);
|
2016-06-21 05:58:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct hist_browser *hist_browser__new(struct hists *hists)
|
|
|
|
{
|
|
|
|
struct hist_browser *browser = zalloc(sizeof(*browser));
|
|
|
|
|
|
|
|
if (browser)
|
|
|
|
hist_browser__init(browser, hists);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
return browser;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2016-06-21 05:58:17 +08:00
|
|
|
static struct hist_browser *
|
|
|
|
perf_evsel_browser__new(struct perf_evsel *evsel,
|
|
|
|
struct hist_browser_timer *hbt,
|
2018-05-29 00:54:59 +08:00
|
|
|
struct perf_env *env,
|
|
|
|
struct annotation_options *annotation_opts)
|
2016-06-21 05:58:17 +08:00
|
|
|
{
|
|
|
|
struct hist_browser *browser = hist_browser__new(evsel__hists(evsel));
|
|
|
|
|
|
|
|
if (browser) {
|
|
|
|
browser->hbt = hbt;
|
|
|
|
browser->env = env;
|
2018-04-03 01:00:04 +08:00
|
|
|
browser->title = hists_browser__scnprintf_title;
|
2018-05-29 00:54:59 +08:00
|
|
|
browser->annotation_opts = annotation_opts;
|
2016-06-21 05:58:17 +08:00
|
|
|
}
|
|
|
|
return browser;
|
|
|
|
}
|
|
|
|
|
2016-06-21 05:58:14 +08:00
|
|
|
void hist_browser__delete(struct hist_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2012-05-30 09:42:18 +08:00
|
|
|
free(browser);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2012-05-30 09:42:18 +08:00
|
|
|
return browser->he_selection;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-05-30 09:42:18 +08:00
|
|
|
static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2012-05-30 09:42:18 +08:00
|
|
|
return browser->he_selection->thread;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2014-10-07 15:13:15 +08:00
|
|
|
/* Check whether the browser is for 'top' or 'report' */
|
|
|
|
static inline bool is_report_browser(void *timer)
|
|
|
|
{
|
|
|
|
return timer == NULL;
|
|
|
|
}
|
|
|
|
|
2018-04-03 01:20:20 +08:00
|
|
|
static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size)
|
|
|
|
{
|
|
|
|
struct hist_browser_timer *hbt = browser->hbt;
|
|
|
|
int printed = __hists__scnprintf_title(browser->hists, bf, size, !is_report_browser(hbt));
|
|
|
|
|
2014-10-07 15:13:15 +08:00
|
|
|
if (!is_report_browser(hbt)) {
|
|
|
|
struct perf_top *top = hbt->arg;
|
|
|
|
|
2018-11-06 22:45:14 +08:00
|
|
|
printed += scnprintf(bf + printed, size - printed,
|
|
|
|
" lost: %" PRIu64 "/%" PRIu64,
|
|
|
|
top->lost, top->lost_total);
|
|
|
|
|
2018-11-12 03:02:46 +08:00
|
|
|
printed += scnprintf(bf + printed, size - printed,
|
|
|
|
" drop: %" PRIu64 "/%" PRIu64,
|
|
|
|
top->drop, top->drop_total);
|
|
|
|
|
2014-10-07 15:13:15 +08:00
|
|
|
if (top->zero)
|
|
|
|
printed += scnprintf(bf + printed, size - printed, " [z]");
|
2018-11-13 18:15:34 +08:00
|
|
|
|
|
|
|
perf_top__reset_sample_counters(top);
|
2014-10-07 15:13:15 +08:00
|
|
|
}
|
|
|
|
|
2018-11-13 18:15:34 +08:00
|
|
|
|
2010-09-13 21:25:04 +08:00
|
|
|
return printed;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2012-03-12 23:13:30 +08:00
|
|
|
static inline void free_popup_options(char **options, int n)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-12-27 04:41:15 +08:00
|
|
|
for (i = 0; i < n; ++i)
|
|
|
|
zfree(&options[i]);
|
2012-03-12 23:13:30 +08:00
|
|
|
}
|
|
|
|
|
2013-02-03 14:38:20 +08:00
|
|
|
/*
|
|
|
|
* Only runtime switching of perf data file will make "input_name" point
|
|
|
|
* to a malloced buffer. So add "is_input_name_malloced" flag to decide
|
|
|
|
* whether we need to call free() for current "input_name" during the switch.
|
|
|
|
*/
|
|
|
|
static bool is_input_name_malloced = false;
|
|
|
|
|
|
|
|
static int switch_data_file(void)
|
|
|
|
{
|
|
|
|
char *pwd, *options[32], *abs_path[32], *tmp;
|
|
|
|
DIR *pwd_dir;
|
|
|
|
int nr_options = 0, choice = -1, ret = -1;
|
|
|
|
struct dirent *dent;
|
|
|
|
|
|
|
|
pwd = getenv("PWD");
|
|
|
|
if (!pwd)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
pwd_dir = opendir(pwd);
|
|
|
|
if (!pwd_dir)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
memset(options, 0, sizeof(options));
|
2017-03-13 19:46:52 +08:00
|
|
|
memset(abs_path, 0, sizeof(abs_path));
|
2013-02-03 14:38:20 +08:00
|
|
|
|
|
|
|
while ((dent = readdir(pwd_dir))) {
|
|
|
|
char path[PATH_MAX];
|
|
|
|
u64 magic;
|
|
|
|
char *name = dent->d_name;
|
|
|
|
FILE *file;
|
|
|
|
|
|
|
|
if (!(dent->d_type == DT_REG))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", pwd, name);
|
|
|
|
|
|
|
|
file = fopen(path, "r");
|
|
|
|
if (!file)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (fread(&magic, 1, 8, file) < 8)
|
|
|
|
goto close_file_and_continue;
|
|
|
|
|
|
|
|
if (is_perf_magic(magic)) {
|
|
|
|
options[nr_options] = strdup(name);
|
|
|
|
if (!options[nr_options])
|
|
|
|
goto close_file_and_continue;
|
|
|
|
|
|
|
|
abs_path[nr_options] = strdup(path);
|
|
|
|
if (!abs_path[nr_options]) {
|
2013-12-28 03:55:14 +08:00
|
|
|
zfree(&options[nr_options]);
|
2013-02-03 14:38:20 +08:00
|
|
|
ui__warning("Can't search all data files due to memory shortage.\n");
|
|
|
|
fclose(file);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
nr_options++;
|
|
|
|
}
|
|
|
|
|
|
|
|
close_file_and_continue:
|
|
|
|
fclose(file);
|
|
|
|
if (nr_options >= 32) {
|
|
|
|
ui__warning("Too many perf data files in PWD!\n"
|
|
|
|
"Only the first 32 files will be listed.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(pwd_dir);
|
|
|
|
|
|
|
|
if (nr_options) {
|
|
|
|
choice = ui__popup_menu(nr_options, options);
|
|
|
|
if (choice < nr_options && choice >= 0) {
|
|
|
|
tmp = strdup(abs_path[choice]);
|
|
|
|
if (tmp) {
|
|
|
|
if (is_input_name_malloced)
|
|
|
|
free((void *)input_name);
|
|
|
|
input_name = tmp;
|
|
|
|
is_input_name_malloced = true;
|
|
|
|
ret = 0;
|
|
|
|
} else
|
|
|
|
ui__warning("Data switch failed due to memory shortage!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free_popup_options(options, nr_options);
|
|
|
|
free_popup_options(abs_path, nr_options);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
struct popup_action {
|
2019-03-11 22:44:56 +08:00
|
|
|
unsigned long time;
|
2015-04-22 15:18:19 +08:00
|
|
|
struct thread *thread;
|
|
|
|
struct map_symbol ms;
|
2015-09-04 22:45:45 +08:00
|
|
|
int socket;
|
2019-03-11 22:44:57 +08:00
|
|
|
struct perf_evsel *evsel;
|
2019-03-11 22:44:58 +08:00
|
|
|
enum rstype rstype;
|
2015-04-22 15:18:19 +08:00
|
|
|
|
|
|
|
int (*fn)(struct hist_browser *browser, struct popup_action *act);
|
|
|
|
};
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
static int
|
2015-04-22 15:18:19 +08:00
|
|
|
do_annotate(struct hist_browser *browser, struct popup_action *act)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
|
|
|
struct perf_evsel *evsel;
|
|
|
|
struct annotation *notes;
|
|
|
|
struct hist_entry *he;
|
|
|
|
int err;
|
|
|
|
|
2018-05-29 01:24:45 +08:00
|
|
|
if (!browser->annotation_opts->objdump_path &&
|
|
|
|
perf_env__lookup_objdump(browser->env, &browser->annotation_opts->objdump_path))
|
2015-04-22 15:18:18 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
notes = symbol__annotation(act->ms.sym);
|
2015-04-22 15:18:18 +08:00
|
|
|
if (!notes->src)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
evsel = hists_to_evsel(browser->hists);
|
2018-05-29 00:54:59 +08:00
|
|
|
err = map_symbol__tui_annotate(&act->ms, evsel, browser->hbt,
|
|
|
|
browser->annotation_opts);
|
2015-04-22 15:18:18 +08:00
|
|
|
he = hist_browser__selected_entry(browser);
|
|
|
|
/*
|
|
|
|
* offer option to annotate the other branch source or target
|
|
|
|
* (if they exists) when returning from annotate
|
|
|
|
*/
|
|
|
|
if ((err == 'q' || err == CTRL('c')) && he->branch_info)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
|
|
|
|
if (err)
|
|
|
|
ui_browser__handle_resize(&browser->b);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2015-04-22 15:18:19 +08:00
|
|
|
add_annotate_opt(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act, char **optstr,
|
|
|
|
struct map *map, struct symbol *sym)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
2015-04-22 15:18:19 +08:00
|
|
|
if (sym == NULL || map->dso->annotate_warned)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Annotate %s", sym->name) < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->ms.map = map;
|
|
|
|
act->ms.sym = sym;
|
|
|
|
act->fn = do_annotate;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_zoom_thread(struct hist_browser *browser, struct popup_action *act)
|
|
|
|
{
|
|
|
|
struct thread *thread = act->thread;
|
|
|
|
|
2016-05-03 19:54:48 +08:00
|
|
|
if ((!hists__has(browser->hists, thread) &&
|
|
|
|
!hists__has(browser->hists, comm)) || thread == NULL)
|
2016-03-09 22:20:53 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
if (browser->hists->thread_filter) {
|
|
|
|
pstack__remove(browser->pstack, &browser->hists->thread_filter);
|
|
|
|
perf_hpp__set_elide(HISTC_THREAD, false);
|
|
|
|
thread__zput(browser->hists->thread_filter);
|
|
|
|
ui_helpline__pop();
|
|
|
|
} else {
|
2016-05-03 19:54:47 +08:00
|
|
|
if (hists__has(browser->hists, thread)) {
|
2016-03-09 23:14:50 +08:00
|
|
|
ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"",
|
|
|
|
thread->comm_set ? thread__comm_str(thread) : "",
|
|
|
|
thread->tid);
|
|
|
|
} else {
|
|
|
|
ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"",
|
|
|
|
thread->comm_set ? thread__comm_str(thread) : "");
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
browser->hists->thread_filter = thread__get(thread);
|
|
|
|
perf_hpp__set_elide(HISTC_THREAD, false);
|
|
|
|
pstack__push(browser->pstack, &browser->hists->thread_filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
hists__filter_by_thread(browser->hists);
|
|
|
|
hist_browser__reset(browser);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2015-04-22 15:18:19 +08:00
|
|
|
add_thread_opt(struct hist_browser *browser, struct popup_action *act,
|
|
|
|
char **optstr, struct thread *thread)
|
|
|
|
{
|
2016-03-09 23:14:50 +08:00
|
|
|
int ret;
|
|
|
|
|
2016-05-03 19:54:48 +08:00
|
|
|
if ((!hists__has(browser->hists, thread) &&
|
|
|
|
!hists__has(browser->hists, comm)) || thread == NULL)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
|
2016-05-03 19:54:47 +08:00
|
|
|
if (hists__has(browser->hists, thread)) {
|
2016-03-09 23:14:50 +08:00
|
|
|
ret = asprintf(optstr, "Zoom %s %s(%d) thread",
|
|
|
|
browser->hists->thread_filter ? "out of" : "into",
|
|
|
|
thread->comm_set ? thread__comm_str(thread) : "",
|
|
|
|
thread->tid);
|
|
|
|
} else {
|
|
|
|
ret = asprintf(optstr, "Zoom %s %s thread",
|
|
|
|
browser->hists->thread_filter ? "out of" : "into",
|
|
|
|
thread->comm_set ? thread__comm_str(thread) : "");
|
|
|
|
}
|
|
|
|
if (ret < 0)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->thread = thread;
|
|
|
|
act->fn = do_zoom_thread;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_zoom_dso(struct hist_browser *browser, struct popup_action *act)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
2015-09-24 02:38:55 +08:00
|
|
|
struct map *map = act->ms.map;
|
2015-04-22 15:18:19 +08:00
|
|
|
|
2016-05-03 19:54:45 +08:00
|
|
|
if (!hists__has(browser->hists, dso) || map == NULL)
|
2016-03-09 22:20:53 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
if (browser->hists->dso_filter) {
|
|
|
|
pstack__remove(browser->pstack, &browser->hists->dso_filter);
|
|
|
|
perf_hpp__set_elide(HISTC_DSO, false);
|
|
|
|
browser->hists->dso_filter = NULL;
|
|
|
|
ui_helpline__pop();
|
|
|
|
} else {
|
2015-10-13 00:56:50 +08:00
|
|
|
ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"",
|
2015-09-24 02:38:55 +08:00
|
|
|
__map__is_kernel(map) ? "the Kernel" : map->dso->short_name);
|
|
|
|
browser->hists->dso_filter = map->dso;
|
2015-04-22 15:18:18 +08:00
|
|
|
perf_hpp__set_elide(HISTC_DSO, true);
|
|
|
|
pstack__push(browser->pstack, &browser->hists->dso_filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
hists__filter_by_dso(browser->hists);
|
|
|
|
hist_browser__reset(browser);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2015-04-22 15:18:19 +08:00
|
|
|
add_dso_opt(struct hist_browser *browser, struct popup_action *act,
|
2015-09-24 02:38:55 +08:00
|
|
|
char **optstr, struct map *map)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
2016-05-03 19:54:45 +08:00
|
|
|
if (!hists__has(browser->hists, dso) || map == NULL)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Zoom %s %s DSO",
|
|
|
|
browser->hists->dso_filter ? "out of" : "into",
|
2015-09-24 02:38:55 +08:00
|
|
|
__map__is_kernel(map) ? "the Kernel" : map->dso->short_name) < 0)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-09-24 02:38:55 +08:00
|
|
|
act->ms.map = map;
|
2015-04-22 15:18:19 +08:00
|
|
|
act->fn = do_zoom_dso;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_browse_map(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act)
|
|
|
|
{
|
|
|
|
map__browse(act->ms.map);
|
2015-04-22 15:18:18 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
static int
|
2016-05-03 19:54:45 +08:00
|
|
|
add_map_opt(struct hist_browser *browser,
|
2015-04-22 15:18:19 +08:00
|
|
|
struct popup_action *act, char **optstr, struct map *map)
|
|
|
|
{
|
2016-05-03 19:54:45 +08:00
|
|
|
if (!hists__has(browser->hists, dso) || map == NULL)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Browse map details") < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->ms.map = map;
|
|
|
|
act->fn = do_browse_map;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
static int
|
|
|
|
do_run_script(struct hist_browser *browser __maybe_unused,
|
2015-04-22 15:18:19 +08:00
|
|
|
struct popup_action *act)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
2019-03-11 22:44:56 +08:00
|
|
|
char *script_opt;
|
|
|
|
int len;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
len = 100;
|
|
|
|
if (act->thread)
|
|
|
|
len += strlen(thread__comm_str(act->thread));
|
|
|
|
else if (act->ms.sym)
|
|
|
|
len += strlen(act->ms.sym->name);
|
|
|
|
script_opt = malloc(len);
|
|
|
|
if (!script_opt)
|
|
|
|
return -1;
|
2015-04-22 15:18:18 +08:00
|
|
|
|
2019-03-11 22:44:56 +08:00
|
|
|
script_opt[0] = 0;
|
2015-04-22 15:18:19 +08:00
|
|
|
if (act->thread) {
|
2019-03-11 22:44:56 +08:00
|
|
|
n = scnprintf(script_opt, len, " -c %s ",
|
2015-04-22 15:18:19 +08:00
|
|
|
thread__comm_str(act->thread));
|
|
|
|
} else if (act->ms.sym) {
|
2019-03-11 22:44:56 +08:00
|
|
|
n = scnprintf(script_opt, len, " -S %s ",
|
2015-04-22 15:18:19 +08:00
|
|
|
act->ms.sym->name);
|
2015-04-22 15:18:18 +08:00
|
|
|
}
|
|
|
|
|
2019-03-11 22:44:56 +08:00
|
|
|
if (act->time) {
|
|
|
|
char start[32], end[32];
|
|
|
|
unsigned long starttime = act->time;
|
|
|
|
unsigned long endtime = act->time + symbol_conf.time_quantum;
|
|
|
|
|
|
|
|
if (starttime == endtime) { /* Display 1ms as fallback */
|
|
|
|
starttime -= 1*NSEC_PER_MSEC;
|
|
|
|
endtime += 1*NSEC_PER_MSEC;
|
|
|
|
}
|
|
|
|
timestamp__scnprintf_usec(starttime, start, sizeof start);
|
|
|
|
timestamp__scnprintf_usec(endtime, end, sizeof end);
|
|
|
|
n += snprintf(script_opt + n, len - n, " --time %s,%s", start, end);
|
|
|
|
}
|
|
|
|
|
2019-03-11 22:44:57 +08:00
|
|
|
script_browse(script_opt, act->evsel);
|
2019-03-11 22:44:56 +08:00
|
|
|
free(script_opt);
|
2015-04-22 15:18:18 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-11 22:44:58 +08:00
|
|
|
static int
|
|
|
|
do_res_sample_script(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act)
|
|
|
|
{
|
|
|
|
struct hist_entry *he;
|
|
|
|
|
|
|
|
he = hist_browser__selected_entry(browser);
|
|
|
|
res_sample_browse(he->res_samples, he->num_res, act->evsel, act->rstype);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:18 +08:00
|
|
|
static int
|
2019-03-11 22:44:56 +08:00
|
|
|
add_script_opt_2(struct hist_browser *browser __maybe_unused,
|
2015-04-22 15:18:19 +08:00
|
|
|
struct popup_action *act, char **optstr,
|
2019-03-11 22:44:56 +08:00
|
|
|
struct thread *thread, struct symbol *sym,
|
2019-03-11 22:44:57 +08:00
|
|
|
struct perf_evsel *evsel, const char *tstr)
|
2015-04-22 15:18:19 +08:00
|
|
|
{
|
2019-03-11 22:44:56 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
if (thread) {
|
2019-03-11 22:44:56 +08:00
|
|
|
if (asprintf(optstr, "Run scripts for samples of thread [%s]%s",
|
|
|
|
thread__comm_str(thread), tstr) < 0)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
} else if (sym) {
|
2019-03-11 22:44:56 +08:00
|
|
|
if (asprintf(optstr, "Run scripts for samples of symbol [%s]%s",
|
|
|
|
sym->name, tstr) < 0)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
} else {
|
2019-03-11 22:44:56 +08:00
|
|
|
if (asprintf(optstr, "Run scripts for all samples%s", tstr) < 0)
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
act->thread = thread;
|
|
|
|
act->ms.sym = sym;
|
2019-03-11 22:44:57 +08:00
|
|
|
act->evsel = evsel;
|
2015-04-22 15:18:19 +08:00
|
|
|
act->fn = do_run_script;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-03-11 22:44:56 +08:00
|
|
|
static int
|
|
|
|
add_script_opt(struct hist_browser *browser,
|
|
|
|
struct popup_action *act, char **optstr,
|
2019-03-11 22:44:57 +08:00
|
|
|
struct thread *thread, struct symbol *sym,
|
|
|
|
struct perf_evsel *evsel)
|
2019-03-11 22:44:56 +08:00
|
|
|
{
|
|
|
|
int n, j;
|
|
|
|
struct hist_entry *he;
|
|
|
|
|
2019-03-11 22:44:57 +08:00
|
|
|
n = add_script_opt_2(browser, act, optstr, thread, sym, evsel, "");
|
2019-03-11 22:44:56 +08:00
|
|
|
|
|
|
|
he = hist_browser__selected_entry(browser);
|
|
|
|
if (sort_order && strstr(sort_order, "time")) {
|
|
|
|
char tstr[128];
|
|
|
|
|
|
|
|
optstr++;
|
|
|
|
act++;
|
|
|
|
j = sprintf(tstr, " in ");
|
|
|
|
j += timestamp__scnprintf_usec(he->time, tstr + j,
|
|
|
|
sizeof tstr - j);
|
|
|
|
j += sprintf(tstr + j, "-");
|
|
|
|
timestamp__scnprintf_usec(he->time + symbol_conf.time_quantum,
|
2019-03-11 22:44:57 +08:00
|
|
|
tstr + j, sizeof tstr - j);
|
2019-03-11 22:44:56 +08:00
|
|
|
n += add_script_opt_2(browser, act, optstr, thread, sym,
|
2019-03-11 22:44:57 +08:00
|
|
|
evsel, tstr);
|
2019-03-11 22:44:56 +08:00
|
|
|
act->time = he->time;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2019-03-11 22:44:58 +08:00
|
|
|
static int
|
|
|
|
add_res_sample_opt(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act, char **optstr,
|
|
|
|
struct res_sample *res_sample,
|
|
|
|
struct perf_evsel *evsel,
|
|
|
|
enum rstype type)
|
|
|
|
{
|
|
|
|
if (!res_sample)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Show context for individual samples %s",
|
|
|
|
type == A_ASM ? "with assembler" :
|
|
|
|
type == A_SOURCE ? "with source" : "") < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->fn = do_res_sample_script;
|
|
|
|
act->evsel = evsel;
|
|
|
|
act->rstype = type;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
static int
|
|
|
|
do_switch_data(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act __maybe_unused)
|
2015-04-22 15:18:18 +08:00
|
|
|
{
|
|
|
|
if (switch_data_file()) {
|
|
|
|
ui__warning("Won't switch the data files due to\n"
|
|
|
|
"no valid data file get selected!\n");
|
2015-04-22 15:18:19 +08:00
|
|
|
return 0;
|
2015-04-22 15:18:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return K_SWITCH_INPUT_DATA;
|
|
|
|
}
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
static int
|
|
|
|
add_switch_opt(struct hist_browser *browser,
|
|
|
|
struct popup_action *act, char **optstr)
|
|
|
|
{
|
|
|
|
if (!is_report_browser(browser->hbt))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Switch to another data file in PWD") < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->fn = do_switch_data;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_exit_browser(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act __maybe_unused)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
add_exit_opt(struct hist_browser *browser __maybe_unused,
|
|
|
|
struct popup_action *act, char **optstr)
|
|
|
|
{
|
|
|
|
if (asprintf(optstr, "Exit") < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->fn = do_exit_browser;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-09-04 22:45:45 +08:00
|
|
|
static int
|
|
|
|
do_zoom_socket(struct hist_browser *browser, struct popup_action *act)
|
|
|
|
{
|
2016-05-03 19:54:46 +08:00
|
|
|
if (!hists__has(browser->hists, socket) || act->socket < 0)
|
2016-03-09 22:20:53 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-09-04 22:45:45 +08:00
|
|
|
if (browser->hists->socket_filter > -1) {
|
|
|
|
pstack__remove(browser->pstack, &browser->hists->socket_filter);
|
|
|
|
browser->hists->socket_filter = -1;
|
|
|
|
perf_hpp__set_elide(HISTC_SOCKET, false);
|
|
|
|
} else {
|
|
|
|
browser->hists->socket_filter = act->socket;
|
|
|
|
perf_hpp__set_elide(HISTC_SOCKET, true);
|
|
|
|
pstack__push(browser->pstack, &browser->hists->socket_filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
hists__filter_by_socket(browser->hists);
|
|
|
|
hist_browser__reset(browser);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
add_socket_opt(struct hist_browser *browser, struct popup_action *act,
|
|
|
|
char **optstr, int socket_id)
|
|
|
|
{
|
2016-05-03 19:54:46 +08:00
|
|
|
if (!hists__has(browser->hists, socket) || socket_id < 0)
|
2015-09-04 22:45:45 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (asprintf(optstr, "Zoom %s Processor Socket %d",
|
|
|
|
(browser->hists->socket_filter > -1) ? "out of" : "into",
|
|
|
|
socket_id) < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
act->socket = socket_id;
|
|
|
|
act->fn = do_zoom_socket;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-04-22 13:05:35 +08:00
|
|
|
static void hist_browser__update_nr_entries(struct hist_browser *hb)
|
2013-05-14 10:09:04 +08:00
|
|
|
{
|
|
|
|
u64 nr_entries = 0;
|
2018-12-07 03:18:18 +08:00
|
|
|
struct rb_node *nd = rb_first_cached(&hb->hists->entries);
|
2013-05-14 10:09:04 +08:00
|
|
|
|
2016-02-24 23:13:43 +08:00
|
|
|
if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) {
|
2014-04-22 13:49:31 +08:00
|
|
|
hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-31 09:17:39 +08:00
|
|
|
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
2013-05-14 10:09:04 +08:00
|
|
|
nr_entries++;
|
2016-02-24 23:13:43 +08:00
|
|
|
nd = rb_hierarchy_next(nd);
|
2013-05-14 10:09:04 +08:00
|
|
|
}
|
|
|
|
|
2014-04-22 13:05:35 +08:00
|
|
|
hb->nr_non_filtered_entries = nr_entries;
|
2016-02-24 23:13:43 +08:00
|
|
|
hb->nr_hierarchy_entries = nr_entries;
|
2013-05-14 10:09:04 +08:00
|
|
|
}
|
2013-02-03 14:38:20 +08:00
|
|
|
|
2016-02-03 22:11:23 +08:00
|
|
|
static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
|
|
|
double percent)
|
|
|
|
{
|
|
|
|
struct hist_entry *he;
|
2018-12-07 03:18:18 +08:00
|
|
|
struct rb_node *nd = rb_first_cached(&hb->hists->entries);
|
2016-02-03 22:11:23 +08:00
|
|
|
u64 total = hists__total_period(hb->hists);
|
|
|
|
u64 min_callchain_hits = total * (percent / 100);
|
|
|
|
|
|
|
|
hb->min_pcnt = callchain_param.min_percent = percent;
|
|
|
|
|
|
|
|
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
|
|
|
he = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
2016-02-26 20:13:19 +08:00
|
|
|
if (he->has_no_entry) {
|
|
|
|
he->has_no_entry = false;
|
|
|
|
he->nr_rows = 0;
|
|
|
|
}
|
|
|
|
|
perf hists: Check if a hist_entry has callchains before using them
So far if we use 'perf record -g' this will make
symbol_conf.use_callchain 'true' and logic will assume that all events
have callchains enabled, but ever since we added the possibility of
setting up callchains for some events (e.g.: -e
cycles/call-graph=dwarf/) while not for others, we limit usage scenarios
by looking at that symbol_conf.use_callchain global boolean, we better
look at each event attributes.
On the road to that we need to look if a hist_entry has callchains, that
is, to go from hist_entry->hists to the evsel that contains it, to then
look at evsel->sample_type for PERF_SAMPLE_CALLCHAIN.
The next step is to add a symbol_conf.ignore_callchains global, to use
in the places where what we really want to know is if callchains should
be ignored, even if present.
Then -g will mean just to select a callchain mode to be applied to all
events not explicitely setting some other callchain mode, i.e. a default
callchain mode, and --no-call-graph will set
symbol_conf.ignore_callchains with that clear intention.
That too will at some point become a per evsel thing, that tools can set
for all or just a few of its evsels.
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: https://lkml.kernel.org/n/tip-0sas5cm4dsw2obn75g7ruz69@git.kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-05-30 00:59:24 +08:00
|
|
|
if (!he->leaf || !hist_entry__has_callchains(he) || !symbol_conf.use_callchain)
|
2016-02-24 23:13:45 +08:00
|
|
|
goto next;
|
|
|
|
|
2016-02-03 22:11:23 +08:00
|
|
|
if (callchain_param.mode == CHAIN_GRAPH_REL) {
|
|
|
|
total = he->stat.period;
|
|
|
|
|
|
|
|
if (symbol_conf.cumulate_callchain)
|
|
|
|
total = he->stat_acc->period;
|
|
|
|
|
|
|
|
min_callchain_hits = total * (percent / 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
callchain_param.sort(&he->sorted_chain, he->callchain,
|
|
|
|
min_callchain_hits, &callchain_param);
|
|
|
|
|
2016-02-24 23:13:45 +08:00
|
|
|
next:
|
2016-02-26 20:13:18 +08:00
|
|
|
nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD);
|
2016-02-24 23:13:45 +08:00
|
|
|
|
2016-02-03 22:11:23 +08:00
|
|
|
/* force to re-evaluate folding state of callchains */
|
|
|
|
he->init_have_children = false;
|
2016-02-24 23:13:44 +08:00
|
|
|
hist_entry__set_folding(he, hb, false);
|
2016-02-03 22:11:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-06 06:35:54 +08:00
|
|
|
static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
|
2014-06-19 19:41:13 +08:00
|
|
|
const char *helpline,
|
2011-10-06 06:11:32 +08:00
|
|
|
bool left_exits,
|
2012-11-02 13:50:06 +08:00
|
|
|
struct hist_browser_timer *hbt,
|
2013-05-14 10:09:04 +08:00
|
|
|
float min_pcnt,
|
2018-01-19 05:26:29 +08:00
|
|
|
struct perf_env *env,
|
2018-05-29 00:54:59 +08:00
|
|
|
bool warn_lost_event,
|
|
|
|
struct annotation_options *annotation_opts)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2014-10-10 00:13:41 +08:00
|
|
|
struct hists *hists = evsel__hists(evsel);
|
2018-05-29 00:54:59 +08:00
|
|
|
struct hist_browser *browser = perf_evsel_browser__new(evsel, hbt, env, annotation_opts);
|
2012-03-09 06:47:48 +08:00
|
|
|
struct branch_info *bi;
|
2015-04-22 15:18:14 +08:00
|
|
|
#define MAX_OPTIONS 16
|
|
|
|
char *options[MAX_OPTIONS];
|
2015-04-22 15:18:19 +08:00
|
|
|
struct popup_action actions[MAX_OPTIONS];
|
2012-03-12 23:13:30 +08:00
|
|
|
int nr_options = 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
int key = -1;
|
2012-03-16 16:50:53 +08:00
|
|
|
char buf[64];
|
2012-11-02 13:50:05 +08:00
|
|
|
int delay_secs = hbt ? hbt->refresh : 0;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2013-12-26 13:37:58 +08:00
|
|
|
#define HIST_BROWSER_HELP_COMMON \
|
|
|
|
"h/?/F1 Show this window\n" \
|
|
|
|
"UP/DOWN/PGUP\n" \
|
|
|
|
"PGDN/SPACE Navigate\n" \
|
2018-04-07 04:38:10 +08:00
|
|
|
"q/ESC/CTRL+C Exit browser or go back to previous screen\n\n" \
|
2013-12-26 13:37:58 +08:00
|
|
|
"For multiple event sessions:\n\n" \
|
|
|
|
"TAB/UNTAB Switch events\n\n" \
|
|
|
|
"For symbolic views (--sort has sym):\n\n" \
|
2015-10-13 00:56:50 +08:00
|
|
|
"ENTER Zoom into DSO/Threads & Annotate current symbol\n" \
|
|
|
|
"ESC Zoom out\n" \
|
2013-12-26 13:37:58 +08:00
|
|
|
"a Annotate current symbol\n" \
|
|
|
|
"C Collapse all callchains\n" \
|
|
|
|
"d Zoom into current DSO\n" \
|
|
|
|
"E Expand all callchains\n" \
|
2014-02-10 10:20:10 +08:00
|
|
|
"F Toggle percentage of filtered entries\n" \
|
2014-06-14 21:44:52 +08:00
|
|
|
"H Display column headers\n" \
|
2016-02-03 22:11:23 +08:00
|
|
|
"L Change percent limit\n" \
|
2015-10-13 08:02:01 +08:00
|
|
|
"m Display context menu\n" \
|
2015-09-04 22:45:45 +08:00
|
|
|
"S Zoom into current Processor Socket\n" \
|
2013-12-26 13:37:58 +08:00
|
|
|
|
|
|
|
/* help messages are sorted by lexical order of the hotkey */
|
perf tools: Replace automatic const char[] variables by statics
An automatic const char[] variable gets initialized at runtime, just
like any other automatic variable. For long strings, that uses a lot of
stack and wastes time building the string; e.g. for the "No %s
allocation events..." case one has:
444516: 48 b8 4e 6f 20 25 73 20 61 6c movabs $0x6c61207325206f4e,%rax # "No %s al"
...
444674: 48 89 45 80 mov %rax,-0x80(%rbp)
444678: 48 b8 6c 6f 63 61 74 69 6f 6e movabs $0x6e6f697461636f6c,%rax # "location"
444682: 48 89 45 88 mov %rax,-0x78(%rbp)
444686: 48 b8 20 65 76 65 6e 74 73 20 movabs $0x2073746e65766520,%rax # " events "
444690: 66 44 89 55 c4 mov %r10w,-0x3c(%rbp)
444695: 48 89 45 90 mov %rax,-0x70(%rbp)
444699: 48 b8 66 6f 75 6e 64 2e 20 20 movabs $0x20202e646e756f66,%rax
Make them all static so that the compiler just references objects in .rodata.
Committer testing:
Ok, using dwarves's codiff tool:
$ codiff --functions /tmp/perf.before ~/bin/perf
builtin-sched.c:
cmd_sched | -48
1 function changed, 48 bytes removed, diff: -48
builtin-report.c:
cmd_report | -32
1 function changed, 32 bytes removed, diff: -32
builtin-kmem.c:
cmd_kmem | -64
build_alloc_func_list | -50
2 functions changed, 114 bytes removed, diff: -114
builtin-c2c.c:
perf_c2c__report | -390
1 function changed, 390 bytes removed, diff: -390
ui/browsers/header.c:
tui__header_window | -104
1 function changed, 104 bytes removed, diff: -104
/home/acme/bin/perf:
9 functions changed, 688 bytes removed, diff: -688
Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20181102230624.20064-1-linux@rasmusvillemoes.dk
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-11-03 07:06:23 +08:00
|
|
|
static const char report_help[] = HIST_BROWSER_HELP_COMMON
|
2013-12-26 13:37:59 +08:00
|
|
|
"i Show header information\n"
|
2013-12-26 13:37:58 +08:00
|
|
|
"P Print histograms to perf.hist.N\n"
|
|
|
|
"r Run available scripts\n"
|
|
|
|
"s Switch to another data file in PWD\n"
|
|
|
|
"t Zoom into current Thread\n"
|
|
|
|
"V Verbose (DSO names in callchains, etc)\n"
|
|
|
|
"/ Filter symbol by name";
|
perf tools: Replace automatic const char[] variables by statics
An automatic const char[] variable gets initialized at runtime, just
like any other automatic variable. For long strings, that uses a lot of
stack and wastes time building the string; e.g. for the "No %s
allocation events..." case one has:
444516: 48 b8 4e 6f 20 25 73 20 61 6c movabs $0x6c61207325206f4e,%rax # "No %s al"
...
444674: 48 89 45 80 mov %rax,-0x80(%rbp)
444678: 48 b8 6c 6f 63 61 74 69 6f 6e movabs $0x6e6f697461636f6c,%rax # "location"
444682: 48 89 45 88 mov %rax,-0x78(%rbp)
444686: 48 b8 20 65 76 65 6e 74 73 20 movabs $0x2073746e65766520,%rax # " events "
444690: 66 44 89 55 c4 mov %r10w,-0x3c(%rbp)
444695: 48 89 45 90 mov %rax,-0x70(%rbp)
444699: 48 b8 66 6f 75 6e 64 2e 20 20 movabs $0x20202e646e756f66,%rax
Make them all static so that the compiler just references objects in .rodata.
Committer testing:
Ok, using dwarves's codiff tool:
$ codiff --functions /tmp/perf.before ~/bin/perf
builtin-sched.c:
cmd_sched | -48
1 function changed, 48 bytes removed, diff: -48
builtin-report.c:
cmd_report | -32
1 function changed, 32 bytes removed, diff: -32
builtin-kmem.c:
cmd_kmem | -64
build_alloc_func_list | -50
2 functions changed, 114 bytes removed, diff: -114
builtin-c2c.c:
perf_c2c__report | -390
1 function changed, 390 bytes removed, diff: -390
ui/browsers/header.c:
tui__header_window | -104
1 function changed, 104 bytes removed, diff: -104
/home/acme/bin/perf:
9 functions changed, 688 bytes removed, diff: -688
Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20181102230624.20064-1-linux@rasmusvillemoes.dk
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2018-11-03 07:06:23 +08:00
|
|
|
static const char top_help[] = HIST_BROWSER_HELP_COMMON
|
2013-12-26 13:37:58 +08:00
|
|
|
"P Print histograms to perf.hist.N\n"
|
|
|
|
"t Zoom into current Thread\n"
|
|
|
|
"V Verbose (DSO names in callchains, etc)\n"
|
2014-08-12 16:16:06 +08:00
|
|
|
"z Toggle zeroing of samples\n"
|
2015-06-20 03:56:04 +08:00
|
|
|
"f Enable/Disable events\n"
|
2013-12-26 13:37:58 +08:00
|
|
|
"/ Filter symbol by name";
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
if (browser == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2015-05-29 20:53:44 +08:00
|
|
|
/* reset abort key so that it can get Ctrl-C as a key */
|
|
|
|
SLang_reset_tty();
|
|
|
|
SLang_init_tty(0, 0, 0);
|
|
|
|
|
2015-11-28 01:32:39 +08:00
|
|
|
if (min_pcnt)
|
2013-05-14 10:09:04 +08:00
|
|
|
browser->min_pcnt = min_pcnt;
|
2015-11-28 01:32:39 +08:00
|
|
|
hist_browser__update_nr_entries(browser);
|
2013-05-14 10:09:04 +08:00
|
|
|
|
2015-09-04 22:45:45 +08:00
|
|
|
browser->pstack = pstack__new(3);
|
2015-04-22 15:18:16 +08:00
|
|
|
if (browser->pstack == NULL)
|
2010-08-11 02:49:07 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
ui_helpline__push(helpline);
|
|
|
|
|
2012-03-12 23:13:30 +08:00
|
|
|
memset(options, 0, sizeof(options));
|
2015-04-22 15:18:19 +08:00
|
|
|
memset(actions, 0, sizeof(actions));
|
2012-03-12 23:13:30 +08:00
|
|
|
|
2014-07-31 13:47:38 +08:00
|
|
|
if (symbol_conf.col_width_list_str)
|
|
|
|
perf_hpp__set_user_width(symbol_conf.col_width_list_str);
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
while (1) {
|
2015-03-03 09:21:35 +08:00
|
|
|
struct thread *thread = NULL;
|
2015-09-24 02:38:55 +08:00
|
|
|
struct map *map = NULL;
|
2015-04-22 15:18:19 +08:00
|
|
|
int choice = 0;
|
2015-09-04 22:45:45 +08:00
|
|
|
int socked_id = -1;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2012-03-12 23:13:30 +08:00
|
|
|
nr_options = 0;
|
|
|
|
|
2018-01-19 05:26:29 +08:00
|
|
|
key = hist_browser__run(browser, helpline,
|
|
|
|
warn_lost_event);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2011-03-05 08:19:21 +08:00
|
|
|
if (browser->he_selection != NULL) {
|
|
|
|
thread = hist_browser__selected_thread(browser);
|
2015-09-24 02:38:55 +08:00
|
|
|
map = browser->selection->map;
|
2015-09-04 22:45:45 +08:00
|
|
|
socked_id = browser->he_selection->socket;
|
2011-03-05 08:19:21 +08:00
|
|
|
}
|
2010-08-11 21:07:43 +08:00
|
|
|
switch (key) {
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_TAB:
|
|
|
|
case K_UNTAB:
|
2011-10-20 01:37:47 +08:00
|
|
|
if (nr_events == 1)
|
|
|
|
continue;
|
2010-08-11 21:07:43 +08:00
|
|
|
/*
|
|
|
|
* Exit the browser, let hists__browser_tree
|
|
|
|
* go to the next or previous
|
|
|
|
*/
|
|
|
|
goto out_free_stack;
|
|
|
|
case 'a':
|
2016-05-03 19:54:44 +08:00
|
|
|
if (!hists__has(hists, sym)) {
|
2011-10-29 22:15:04 +08:00
|
|
|
ui_browser__warning(&browser->b, delay_secs * 2,
|
2011-10-21 20:58:24 +08:00
|
|
|
"Annotation is only available for symbolic views, "
|
2012-03-09 06:47:48 +08:00
|
|
|
"include \"sym*\" in --sort to use it.");
|
2011-10-21 20:58:24 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-03-05 08:19:21 +08:00
|
|
|
if (browser->selection == NULL ||
|
2011-04-08 14:31:26 +08:00
|
|
|
browser->selection->sym == NULL ||
|
2010-08-11 21:07:43 +08:00
|
|
|
browser->selection->map->dso->annotate_warned)
|
2010-08-11 02:49:07 +08:00
|
|
|
continue;
|
2015-04-22 15:18:18 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
actions->ms.map = browser->selection->map;
|
|
|
|
actions->ms.sym = browser->selection->sym;
|
|
|
|
do_annotate(browser, actions);
|
2015-04-22 15:18:18 +08:00
|
|
|
continue;
|
2012-06-08 06:31:28 +08:00
|
|
|
case 'P':
|
|
|
|
hist_browser__dump(browser);
|
|
|
|
continue;
|
2010-08-11 21:07:43 +08:00
|
|
|
case 'd':
|
2015-11-13 02:59:26 +08:00
|
|
|
actions->ms.map = map;
|
2015-04-22 15:18:19 +08:00
|
|
|
do_zoom_dso(browser, actions);
|
2015-04-22 15:18:18 +08:00
|
|
|
continue;
|
2012-08-04 00:53:40 +08:00
|
|
|
case 'V':
|
2016-10-13 05:48:23 +08:00
|
|
|
verbose = (verbose + 1) % 4;
|
|
|
|
browser->show_dso = verbose > 0;
|
|
|
|
ui_helpline__fpush("Verbosity level set to %d\n",
|
|
|
|
verbose);
|
2012-08-04 00:53:40 +08:00
|
|
|
continue;
|
2010-08-11 21:07:43 +08:00
|
|
|
case 't':
|
2015-04-22 15:18:19 +08:00
|
|
|
actions->thread = thread;
|
|
|
|
do_zoom_thread(browser, actions);
|
2015-04-22 15:18:18 +08:00
|
|
|
continue;
|
2015-09-04 22:45:45 +08:00
|
|
|
case 'S':
|
|
|
|
actions->socket = socked_id;
|
|
|
|
do_zoom_socket(browser, actions);
|
|
|
|
continue;
|
2012-05-09 23:21:22 +08:00
|
|
|
case '/':
|
2012-03-16 16:50:53 +08:00
|
|
|
if (ui_browser__input_window("Symbol to show",
|
2015-10-13 01:02:29 +08:00
|
|
|
"Please enter the name of symbol you want to see.\n"
|
|
|
|
"To remove the filter later, press / + ENTER.",
|
2012-03-16 16:50:53 +08:00
|
|
|
buf, "ENTER: OK, ESC: Cancel",
|
|
|
|
delay_secs * 2) == K_ENTER) {
|
2012-05-30 09:42:18 +08:00
|
|
|
hists->symbol_filter_str = *buf ? buf : NULL;
|
|
|
|
hists__filter_by_symbol(hists);
|
2012-03-16 16:50:53 +08:00
|
|
|
hist_browser__reset(browser);
|
|
|
|
}
|
|
|
|
continue;
|
2012-10-30 11:56:06 +08:00
|
|
|
case 'r':
|
2015-04-22 15:18:19 +08:00
|
|
|
if (is_report_browser(hbt)) {
|
|
|
|
actions->thread = NULL;
|
|
|
|
actions->ms.sym = NULL;
|
|
|
|
do_run_script(browser, actions);
|
|
|
|
}
|
2012-11-01 00:00:55 +08:00
|
|
|
continue;
|
2013-02-03 14:38:20 +08:00
|
|
|
case 's':
|
2015-04-22 15:18:18 +08:00
|
|
|
if (is_report_browser(hbt)) {
|
2015-04-22 15:18:19 +08:00
|
|
|
key = do_switch_data(browser, actions);
|
2015-04-22 15:18:18 +08:00
|
|
|
if (key == K_SWITCH_INPUT_DATA)
|
|
|
|
goto out_free_stack;
|
|
|
|
}
|
2013-02-03 14:38:20 +08:00
|
|
|
continue;
|
2013-12-26 13:37:59 +08:00
|
|
|
case 'i':
|
|
|
|
/* env->arch is NULL for live-mode (i.e. perf top) */
|
|
|
|
if (env->arch)
|
|
|
|
tui__header_window(env);
|
|
|
|
continue;
|
2014-02-10 10:20:10 +08:00
|
|
|
case 'F':
|
|
|
|
symbol_conf.filter_relative ^= 1;
|
|
|
|
continue;
|
2014-08-12 16:16:06 +08:00
|
|
|
case 'z':
|
|
|
|
if (!is_report_browser(hbt)) {
|
|
|
|
struct perf_top *top = hbt->arg;
|
|
|
|
|
|
|
|
top->zero = !top->zero;
|
|
|
|
}
|
|
|
|
continue;
|
2016-02-03 22:11:23 +08:00
|
|
|
case 'L':
|
|
|
|
if (ui_browser__input_window("Percent Limit",
|
|
|
|
"Please enter the value you want to hide entries under that percent.",
|
|
|
|
buf, "ENTER: OK, ESC: Cancel",
|
|
|
|
delay_secs * 2) == K_ENTER) {
|
|
|
|
char *end;
|
|
|
|
double new_percent = strtod(buf, &end);
|
|
|
|
|
|
|
|
if (new_percent < 0 || new_percent > 100) {
|
|
|
|
ui_browser__warning(&browser->b, delay_secs * 2,
|
|
|
|
"Invalid percent: %.2f", new_percent);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
hist_browser__update_percent_limit(browser, new_percent);
|
|
|
|
hist_browser__reset(browser);
|
|
|
|
}
|
|
|
|
continue;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_F1:
|
2010-08-11 21:07:43 +08:00
|
|
|
case 'h':
|
|
|
|
case '?':
|
2011-10-26 22:04:37 +08:00
|
|
|
ui_browser__help_window(&browser->b,
|
2013-12-26 13:37:58 +08:00
|
|
|
is_report_browser(hbt) ? report_help : top_help);
|
2010-08-11 21:07:43 +08:00
|
|
|
continue;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_ENTER:
|
|
|
|
case K_RIGHT:
|
2015-10-13 08:02:01 +08:00
|
|
|
case 'm':
|
2010-08-11 21:07:43 +08:00
|
|
|
/* menu */
|
|
|
|
break;
|
2015-08-12 23:42:58 +08:00
|
|
|
case K_ESC:
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_LEFT: {
|
2010-08-11 21:07:43 +08:00
|
|
|
const void *top;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2015-04-22 15:18:16 +08:00
|
|
|
if (pstack__empty(browser->pstack)) {
|
2011-03-07 00:07:30 +08:00
|
|
|
/*
|
|
|
|
* Go back to the perf_evsel_menu__run or other user
|
|
|
|
*/
|
|
|
|
if (left_exits)
|
|
|
|
goto out_free_stack;
|
2015-08-12 23:42:58 +08:00
|
|
|
|
|
|
|
if (key == K_ESC &&
|
|
|
|
ui_browser__dialog_yesno(&browser->b,
|
|
|
|
"Do you really want to exit?"))
|
|
|
|
goto out_free_stack;
|
|
|
|
|
2010-08-11 02:49:07 +08:00
|
|
|
continue;
|
2011-03-07 00:07:30 +08:00
|
|
|
}
|
2015-04-24 09:15:33 +08:00
|
|
|
top = pstack__peek(browser->pstack);
|
2015-04-22 15:18:18 +08:00
|
|
|
if (top == &browser->hists->dso_filter) {
|
2015-04-24 09:15:33 +08:00
|
|
|
/*
|
|
|
|
* No need to set actions->dso here since
|
|
|
|
* it's just to remove the current filter.
|
|
|
|
* Ditto for thread below.
|
|
|
|
*/
|
|
|
|
do_zoom_dso(browser, actions);
|
2015-09-04 22:45:45 +08:00
|
|
|
} else if (top == &browser->hists->thread_filter) {
|
2015-04-24 09:15:33 +08:00
|
|
|
do_zoom_thread(browser, actions);
|
2015-09-04 22:45:45 +08:00
|
|
|
} else if (top == &browser->hists->socket_filter) {
|
|
|
|
do_zoom_socket(browser, actions);
|
|
|
|
}
|
2010-08-11 21:07:43 +08:00
|
|
|
continue;
|
|
|
|
}
|
2011-10-13 19:31:22 +08:00
|
|
|
case 'q':
|
|
|
|
case CTRL('c'):
|
2015-06-20 03:59:43 +08:00
|
|
|
goto out_free_stack;
|
2015-06-20 03:56:04 +08:00
|
|
|
case 'f':
|
2015-06-21 11:41:16 +08:00
|
|
|
if (!is_report_browser(hbt)) {
|
|
|
|
struct perf_top *top = hbt->arg;
|
|
|
|
|
|
|
|
perf_evlist__toggle_enable(top->evlist);
|
|
|
|
/*
|
|
|
|
* No need to refresh, resort/decay histogram
|
|
|
|
* entries if we are not collecting samples:
|
|
|
|
*/
|
|
|
|
if (top->evlist->enabled) {
|
|
|
|
helpline = "Press 'f' to disable the events or 'h' to see other hotkeys";
|
|
|
|
hbt->refresh = delay_secs;
|
|
|
|
} else {
|
|
|
|
helpline = "Press 'f' again to re-enable the events";
|
|
|
|
hbt->refresh = 0;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2015-06-20 04:49:29 +08:00
|
|
|
/* Fall thru */
|
2011-10-13 19:31:22 +08:00
|
|
|
default:
|
2015-06-20 04:49:29 +08:00
|
|
|
helpline = "Press '?' for help on key bindings";
|
2011-10-13 19:31:22 +08:00
|
|
|
continue;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
|
2016-05-03 19:54:44 +08:00
|
|
|
if (!hists__has(hists, sym) || browser->selection == NULL)
|
2015-03-17 04:41:52 +08:00
|
|
|
goto skip_annotation;
|
|
|
|
|
2013-04-01 19:35:20 +08:00
|
|
|
if (sort__mode == SORT_MODE__BRANCH) {
|
2012-03-09 06:47:48 +08:00
|
|
|
bi = browser->he_selection->branch_info;
|
2015-03-17 04:41:52 +08:00
|
|
|
|
|
|
|
if (bi == NULL)
|
|
|
|
goto skip_annotation;
|
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_annotate_opt(browser,
|
|
|
|
&actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
bi->from.map,
|
|
|
|
bi->from.sym);
|
|
|
|
if (bi->to.sym != bi->from.sym)
|
|
|
|
nr_options += add_annotate_opt(browser,
|
|
|
|
&actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
bi->to.map,
|
|
|
|
bi->to.sym);
|
2012-03-09 06:47:48 +08:00
|
|
|
} else {
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_annotate_opt(browser,
|
|
|
|
&actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
browser->selection->map,
|
|
|
|
browser->selection->sym);
|
2012-03-09 06:47:48 +08:00
|
|
|
}
|
2015-03-17 04:41:52 +08:00
|
|
|
skip_annotation:
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_thread_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options], thread);
|
|
|
|
nr_options += add_dso_opt(browser, &actions[nr_options],
|
2015-09-24 02:38:55 +08:00
|
|
|
&options[nr_options], map);
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_map_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options],
|
2015-09-14 18:23:55 +08:00
|
|
|
browser->selection ?
|
|
|
|
browser->selection->map : NULL);
|
2015-09-04 22:45:45 +08:00
|
|
|
nr_options += add_socket_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
socked_id);
|
2012-10-30 11:56:06 +08:00
|
|
|
/* perf script support */
|
2016-01-27 02:37:30 +08:00
|
|
|
if (!is_report_browser(hbt))
|
|
|
|
goto skip_scripting;
|
|
|
|
|
2012-10-30 11:56:06 +08:00
|
|
|
if (browser->he_selection) {
|
2016-05-03 19:54:47 +08:00
|
|
|
if (hists__has(hists, thread) && thread) {
|
2016-01-22 06:13:24 +08:00
|
|
|
nr_options += add_script_opt(browser,
|
|
|
|
&actions[nr_options],
|
|
|
|
&options[nr_options],
|
2019-03-11 22:44:57 +08:00
|
|
|
thread, NULL, evsel);
|
2016-01-22 06:13:24 +08:00
|
|
|
}
|
2015-09-14 18:23:55 +08:00
|
|
|
/*
|
|
|
|
* Note that browser->selection != NULL
|
|
|
|
* when browser->he_selection is not NULL,
|
|
|
|
* so we don't need to check browser->selection
|
|
|
|
* before fetching browser->selection->sym like what
|
|
|
|
* we do before fetching browser->selection->map.
|
|
|
|
*
|
|
|
|
* See hist_browser__show_entry.
|
|
|
|
*/
|
2016-05-03 19:54:44 +08:00
|
|
|
if (hists__has(hists, sym) && browser->selection->sym) {
|
2016-01-22 06:50:09 +08:00
|
|
|
nr_options += add_script_opt(browser,
|
|
|
|
&actions[nr_options],
|
|
|
|
&options[nr_options],
|
2019-03-11 22:44:57 +08:00
|
|
|
NULL, browser->selection->sym,
|
|
|
|
evsel);
|
2016-01-22 06:50:09 +08:00
|
|
|
}
|
2012-10-30 11:56:06 +08:00
|
|
|
}
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_script_opt(browser, &actions[nr_options],
|
2019-03-11 22:44:57 +08:00
|
|
|
&options[nr_options], NULL, NULL, evsel);
|
2019-03-11 22:44:58 +08:00
|
|
|
nr_options += add_res_sample_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
hist_browser__selected_entry(browser)->res_samples,
|
|
|
|
evsel, A_NORMAL);
|
|
|
|
nr_options += add_res_sample_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
hist_browser__selected_entry(browser)->res_samples,
|
|
|
|
evsel, A_ASM);
|
|
|
|
nr_options += add_res_sample_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options],
|
|
|
|
hist_browser__selected_entry(browser)->res_samples,
|
|
|
|
evsel, A_SOURCE);
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_switch_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options]);
|
2016-01-27 02:37:30 +08:00
|
|
|
skip_scripting:
|
2015-04-22 15:18:19 +08:00
|
|
|
nr_options += add_exit_opt(browser, &actions[nr_options],
|
|
|
|
&options[nr_options]);
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
do {
|
|
|
|
struct popup_action *act;
|
2012-11-02 13:50:06 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
choice = ui__popup_menu(nr_options, options);
|
|
|
|
if (choice == -1 || choice >= nr_options)
|
|
|
|
break;
|
2012-03-09 06:47:48 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
act = &actions[choice];
|
|
|
|
key = act->fn(browser, act);
|
|
|
|
} while (key == 1);
|
2012-03-09 06:47:48 +08:00
|
|
|
|
2015-04-22 15:18:19 +08:00
|
|
|
if (key == K_SWITCH_INPUT_DATA)
|
|
|
|
break;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
out_free_stack:
|
2015-04-22 15:18:16 +08:00
|
|
|
pstack__delete(browser->pstack);
|
2010-08-11 02:49:07 +08:00
|
|
|
out:
|
|
|
|
hist_browser__delete(browser);
|
2015-04-22 15:18:14 +08:00
|
|
|
free_popup_options(options, MAX_OPTIONS);
|
2010-08-11 02:49:07 +08:00
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
2011-03-07 00:07:30 +08:00
|
|
|
struct perf_evsel_menu {
|
|
|
|
struct ui_browser b;
|
|
|
|
struct perf_evsel *selection;
|
2018-05-29 00:54:59 +08:00
|
|
|
struct annotation_options *annotation_opts;
|
2011-10-29 22:15:04 +08:00
|
|
|
bool lost_events, lost_events_warned;
|
2013-05-14 10:09:04 +08:00
|
|
|
float min_pcnt;
|
2015-08-28 17:48:04 +08:00
|
|
|
struct perf_env *env;
|
2011-03-07 00:07:30 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static void perf_evsel_menu__write(struct ui_browser *browser,
|
|
|
|
void *entry, int row)
|
|
|
|
{
|
|
|
|
struct perf_evsel_menu *menu = container_of(browser,
|
|
|
|
struct perf_evsel_menu, b);
|
|
|
|
struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
|
2014-10-10 00:13:41 +08:00
|
|
|
struct hists *hists = evsel__hists(evsel);
|
2011-03-07 00:07:30 +08:00
|
|
|
bool current_entry = ui_browser__is_current_entry(browser, row);
|
2014-10-10 00:13:41 +08:00
|
|
|
unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE];
|
2012-06-12 23:34:58 +08:00
|
|
|
const char *ev_name = perf_evsel__name(evsel);
|
2011-03-07 00:07:30 +08:00
|
|
|
char bf[256], unit;
|
2011-10-29 22:15:04 +08:00
|
|
|
const char *warn = " ";
|
|
|
|
size_t printed;
|
2011-03-07 00:07:30 +08:00
|
|
|
|
|
|
|
ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
|
|
|
|
HE_COLORSET_NORMAL);
|
|
|
|
|
2013-03-05 13:53:26 +08:00
|
|
|
if (perf_evsel__is_group_event(evsel)) {
|
2013-01-22 17:09:44 +08:00
|
|
|
struct perf_evsel *pos;
|
|
|
|
|
|
|
|
ev_name = perf_evsel__group_name(evsel);
|
|
|
|
|
|
|
|
for_each_group_member(pos, evsel) {
|
2014-10-10 00:13:41 +08:00
|
|
|
struct hists *pos_hists = evsel__hists(pos);
|
|
|
|
nr_events += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE];
|
2013-01-22 17:09:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-07 00:07:30 +08:00
|
|
|
nr_events = convert_unit(nr_events, &unit);
|
2012-03-14 23:29:29 +08:00
|
|
|
printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
|
2011-10-29 22:15:04 +08:00
|
|
|
unit, unit == ' ' ? "" : " ", ev_name);
|
2015-08-11 23:50:55 +08:00
|
|
|
ui_browser__printf(browser, "%s", bf);
|
2011-10-29 22:15:04 +08:00
|
|
|
|
2014-10-10 00:13:41 +08:00
|
|
|
nr_events = hists->stats.nr_events[PERF_RECORD_LOST];
|
2011-10-29 22:15:04 +08:00
|
|
|
if (nr_events != 0) {
|
|
|
|
menu->lost_events = true;
|
|
|
|
if (!current_entry)
|
|
|
|
ui_browser__set_color(browser, HE_COLORSET_TOP);
|
|
|
|
nr_events = convert_unit(nr_events, &unit);
|
2012-03-14 23:29:29 +08:00
|
|
|
printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
|
|
|
|
nr_events, unit, unit == ' ' ? "" : " ");
|
2011-10-29 22:15:04 +08:00
|
|
|
warn = bf;
|
|
|
|
}
|
|
|
|
|
2015-08-11 23:24:27 +08:00
|
|
|
ui_browser__write_nstring(browser, warn, browser->width - printed);
|
2011-03-07 00:07:30 +08:00
|
|
|
|
|
|
|
if (current_entry)
|
|
|
|
menu->selection = evsel;
|
|
|
|
}
|
|
|
|
|
2011-10-06 06:35:54 +08:00
|
|
|
static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
|
|
|
|
int nr_events, const char *help,
|
2018-01-19 05:26:29 +08:00
|
|
|
struct hist_browser_timer *hbt,
|
|
|
|
bool warn_lost_event)
|
2010-08-11 02:49:07 +08:00
|
|
|
{
|
2011-03-07 00:07:30 +08:00
|
|
|
struct perf_evlist *evlist = menu->b.priv;
|
2011-03-06 08:40:06 +08:00
|
|
|
struct perf_evsel *pos;
|
2014-06-19 19:41:13 +08:00
|
|
|
const char *title = "Available samples";
|
2012-11-02 13:50:05 +08:00
|
|
|
int delay_secs = hbt ? hbt->refresh : 0;
|
2011-03-07 00:07:30 +08:00
|
|
|
int key;
|
2010-08-11 02:49:07 +08:00
|
|
|
|
2011-03-07 00:07:30 +08:00
|
|
|
if (ui_browser__show(&menu->b, title,
|
|
|
|
"ESC: exit, ENTER|->: Browse histograms") < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
while (1) {
|
2011-10-13 19:52:46 +08:00
|
|
|
key = ui_browser__run(&menu->b, delay_secs);
|
2011-03-07 00:07:30 +08:00
|
|
|
|
|
|
|
switch (key) {
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_TIMER:
|
2012-11-02 13:50:05 +08:00
|
|
|
hbt->timer(hbt->arg);
|
2011-10-29 22:15:04 +08:00
|
|
|
|
2018-01-19 05:26:29 +08:00
|
|
|
if (!menu->lost_events_warned &&
|
|
|
|
menu->lost_events &&
|
|
|
|
warn_lost_event) {
|
2011-10-29 22:15:04 +08:00
|
|
|
ui_browser__warn_lost_events(&menu->b);
|
|
|
|
menu->lost_events_warned = true;
|
|
|
|
}
|
2011-10-06 06:11:32 +08:00
|
|
|
continue;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_RIGHT:
|
|
|
|
case K_ENTER:
|
2011-03-07 00:07:30 +08:00
|
|
|
if (!menu->selection)
|
|
|
|
continue;
|
|
|
|
pos = menu->selection;
|
|
|
|
browse_hists:
|
2011-10-13 23:22:28 +08:00
|
|
|
perf_evlist__set_selected(evlist, pos);
|
|
|
|
/*
|
|
|
|
* Give the calling tool a chance to populate the non
|
|
|
|
* default evsel resorted hists tree.
|
|
|
|
*/
|
2012-11-02 13:50:05 +08:00
|
|
|
if (hbt)
|
|
|
|
hbt->timer(hbt->arg);
|
2011-10-06 06:35:54 +08:00
|
|
|
key = perf_evsel__hists_browse(pos, nr_events, help,
|
2014-06-19 19:41:13 +08:00
|
|
|
true, hbt,
|
2013-05-14 10:09:04 +08:00
|
|
|
menu->min_pcnt,
|
2018-01-19 05:26:29 +08:00
|
|
|
menu->env,
|
2018-05-29 00:54:59 +08:00
|
|
|
warn_lost_event,
|
|
|
|
menu->annotation_opts);
|
2011-03-07 00:07:30 +08:00
|
|
|
ui_browser__show_title(&menu->b, title);
|
2011-10-13 23:22:28 +08:00
|
|
|
switch (key) {
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_TAB:
|
2011-10-13 23:22:28 +08:00
|
|
|
if (pos->node.next == &evlist->entries)
|
2013-11-14 02:54:30 +08:00
|
|
|
pos = perf_evlist__first(evlist);
|
2011-10-13 23:22:28 +08:00
|
|
|
else
|
2013-11-14 02:54:30 +08:00
|
|
|
pos = perf_evsel__next(pos);
|
2011-10-13 23:22:28 +08:00
|
|
|
goto browse_hists;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_UNTAB:
|
2011-10-13 23:22:28 +08:00
|
|
|
if (pos->node.prev == &evlist->entries)
|
2013-11-14 02:54:30 +08:00
|
|
|
pos = perf_evlist__last(evlist);
|
2011-10-13 23:22:28 +08:00
|
|
|
else
|
2013-11-14 02:56:40 +08:00
|
|
|
pos = perf_evsel__prev(pos);
|
2011-10-13 23:22:28 +08:00
|
|
|
goto browse_hists;
|
2013-02-03 14:38:20 +08:00
|
|
|
case K_SWITCH_INPUT_DATA:
|
2011-10-13 23:22:28 +08:00
|
|
|
case 'q':
|
|
|
|
case CTRL('c'):
|
|
|
|
goto out;
|
2015-08-12 23:42:58 +08:00
|
|
|
case K_ESC:
|
2011-10-13 23:22:28 +08:00
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_LEFT:
|
2011-03-07 00:07:30 +08:00
|
|
|
continue;
|
2011-10-21 02:59:15 +08:00
|
|
|
case K_ESC:
|
2011-10-26 22:04:37 +08:00
|
|
|
if (!ui_browser__dialog_yesno(&menu->b,
|
|
|
|
"Do you really want to exit?"))
|
2011-10-13 19:31:22 +08:00
|
|
|
continue;
|
|
|
|
/* Fall thru */
|
2011-03-07 00:07:30 +08:00
|
|
|
case 'q':
|
|
|
|
case CTRL('c'):
|
|
|
|
goto out;
|
2010-08-11 02:49:07 +08:00
|
|
|
default:
|
2011-10-13 23:22:28 +08:00
|
|
|
continue;
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-07 00:07:30 +08:00
|
|
|
out:
|
|
|
|
ui_browser__hide(&menu->b);
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
2013-11-06 02:32:36 +08:00
|
|
|
static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
|
2013-01-22 17:09:43 +08:00
|
|
|
void *entry)
|
|
|
|
{
|
|
|
|
struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
|
|
|
|
|
|
|
|
if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-03-07 00:07:30 +08:00
|
|
|
static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
|
2013-01-22 17:09:43 +08:00
|
|
|
int nr_entries, const char *help,
|
2012-11-02 13:50:06 +08:00
|
|
|
struct hist_browser_timer *hbt,
|
2013-05-14 10:09:04 +08:00
|
|
|
float min_pcnt,
|
2018-01-19 05:26:29 +08:00
|
|
|
struct perf_env *env,
|
2018-05-29 00:54:59 +08:00
|
|
|
bool warn_lost_event,
|
|
|
|
struct annotation_options *annotation_opts)
|
2011-03-07 00:07:30 +08:00
|
|
|
{
|
|
|
|
struct perf_evsel *pos;
|
|
|
|
struct perf_evsel_menu menu = {
|
|
|
|
.b = {
|
|
|
|
.entries = &evlist->entries,
|
|
|
|
.refresh = ui_browser__list_head_refresh,
|
|
|
|
.seek = ui_browser__list_head_seek,
|
|
|
|
.write = perf_evsel_menu__write,
|
2013-01-22 17:09:43 +08:00
|
|
|
.filter = filter_group_entries,
|
|
|
|
.nr_entries = nr_entries,
|
2011-03-07 00:07:30 +08:00
|
|
|
.priv = evlist,
|
|
|
|
},
|
2013-05-14 10:09:04 +08:00
|
|
|
.min_pcnt = min_pcnt,
|
2012-11-02 13:50:06 +08:00
|
|
|
.env = env,
|
2018-05-29 00:54:59 +08:00
|
|
|
.annotation_opts = annotation_opts,
|
2011-03-07 00:07:30 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
ui_helpline__push("Press ESC to exit");
|
|
|
|
|
2016-06-23 22:26:15 +08:00
|
|
|
evlist__for_each_entry(evlist, pos) {
|
2012-06-12 23:34:58 +08:00
|
|
|
const char *ev_name = perf_evsel__name(pos);
|
2011-03-07 00:07:30 +08:00
|
|
|
size_t line_len = strlen(ev_name) + 7;
|
|
|
|
|
|
|
|
if (menu.b.width < line_len)
|
|
|
|
menu.b.width = line_len;
|
|
|
|
}
|
|
|
|
|
2018-01-19 05:26:29 +08:00
|
|
|
return perf_evsel_menu__run(&menu, nr_entries, help,
|
|
|
|
hbt, warn_lost_event);
|
2011-03-07 00:07:30 +08:00
|
|
|
}
|
|
|
|
|
2011-10-06 06:11:32 +08:00
|
|
|
int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
|
2012-11-02 13:50:06 +08:00
|
|
|
struct hist_browser_timer *hbt,
|
2013-05-14 10:09:04 +08:00
|
|
|
float min_pcnt,
|
2018-01-19 05:26:29 +08:00
|
|
|
struct perf_env *env,
|
2018-05-29 00:54:59 +08:00
|
|
|
bool warn_lost_event,
|
|
|
|
struct annotation_options *annotation_opts)
|
2011-03-07 00:07:30 +08:00
|
|
|
{
|
2013-01-22 17:09:43 +08:00
|
|
|
int nr_entries = evlist->nr_entries;
|
|
|
|
|
|
|
|
single_entry:
|
|
|
|
if (nr_entries == 1) {
|
2013-11-14 02:54:30 +08:00
|
|
|
struct perf_evsel *first = perf_evlist__first(evlist);
|
2013-01-22 17:09:43 +08:00
|
|
|
|
|
|
|
return perf_evsel__hists_browse(first, nr_entries, help,
|
2014-06-19 19:41:13 +08:00
|
|
|
false, hbt, min_pcnt,
|
2018-05-29 00:54:59 +08:00
|
|
|
env, warn_lost_event,
|
|
|
|
annotation_opts);
|
2011-03-07 00:07:30 +08:00
|
|
|
}
|
|
|
|
|
2013-01-22 17:09:43 +08:00
|
|
|
if (symbol_conf.event_group) {
|
|
|
|
struct perf_evsel *pos;
|
|
|
|
|
|
|
|
nr_entries = 0;
|
2016-06-23 22:26:15 +08:00
|
|
|
evlist__for_each_entry(evlist, pos) {
|
2013-01-22 17:09:43 +08:00
|
|
|
if (perf_evsel__is_group_leader(pos))
|
|
|
|
nr_entries++;
|
2014-01-10 21:37:27 +08:00
|
|
|
}
|
2013-01-22 17:09:43 +08:00
|
|
|
|
|
|
|
if (nr_entries == 1)
|
|
|
|
goto single_entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
|
2018-01-19 05:26:29 +08:00
|
|
|
hbt, min_pcnt, env,
|
2018-05-29 00:54:59 +08:00
|
|
|
warn_lost_event,
|
|
|
|
annotation_opts);
|
2010-08-11 02:49:07 +08:00
|
|
|
}
|