Commit 01897f3e authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull perf updates and fixes from Ingo Molnar:
 "These are almost all tooling updates: 'perf top', 'perf trace' and
  'perf script' fixes and updates, an UAPI header sync with the merge
  window versions, license marker updates, much improved Sparc support
  from David Miller, and a number of fixes"

* 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (66 commits)
  perf intel-pt/bts: Calculate cpumode for synthesized samples
  perf intel-pt: Insert callchain context into synthesized callchains
  perf tools: Don't clone maps from parent when synthesizing forks
  perf top: Start display thread earlier
  tools headers uapi: Update linux/if_link.h header copy
  tools headers uapi: Update linux/netlink.h header copy
  tools headers: Sync the various kvm.h header copies
  tools include uapi: Update linux/mmap.h copy
  perf trace beauty: Use the mmap flags table generated from headers
  perf beauty: Wire up the mmap flags table generator to the Makefile
  perf beauty: Add a generator for MAP_ mmap's flag constants
  tools include uapi: Update asound.h copy
  tools arch uapi: Update asm-generic/unistd.h and arm64 unistd.h copies
  tools include uapi: Update linux/fs.h copy
  perf callchain: Honour the ordering of PERF_CONTEXT_{USER,KERNEL,etc}
  perf cs-etm: Correct CPU mode for samples
  perf unwind: Take pgoff into account when reporting elf to libdwfl
  perf top: Do not use overwrite mode by default
  perf top: Allow disabling the overwrite mode
  perf trace: Beautify mount's first pathname arg
  ...
parents e9ebc215 29995d29
...@@ -242,6 +242,16 @@ Default is to monitor all CPUS. ...@@ -242,6 +242,16 @@ Default is to monitor all CPUS.
--hierarchy:: --hierarchy::
Enable hierarchy output. Enable hierarchy output.
--overwrite::
Enable this to use just the most recent records, which helps in high core count
machines such as Knights Landing/Mill, but right now is disabled by default as
the pausing used in this technique is leading to loss of metadata events such
as PERF_RECORD_MMAP which makes 'perf top' unable to resolve samples, leading
to lots of unknown samples appearing on the UI. Enable this if you are in such
machines and profiling a workload that doesn't creates short lived threads and/or
doesn't uses many executable mmap operations. Work is being planed to solve
this situation, till then, this will remain disabled by default.
--force:: --force::
Don't do ownership validation. Don't do ownership validation.
......
...@@ -171,6 +171,11 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs. ...@@ -171,6 +171,11 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs.
--kernel-syscall-graph:: --kernel-syscall-graph::
Show the kernel callchains on the syscall exit path. Show the kernel callchains on the syscall exit path.
--max-events=N::
Stop after processing N events. Note that strace-like events are considered
only at exit time or when a syscall is interrupted, i.e. in those cases this
option is equivalent to the number of lines printed.
--max-stack:: --max-stack::
Set the stack depth limit when parsing the callchain, anything Set the stack depth limit when parsing the callchain, anything
beyond the specified depth will be ignored. Note that at this point beyond the specified depth will be ignored. Note that at this point
...@@ -238,6 +243,68 @@ Trace syscalls, major and minor pagefaults: ...@@ -238,6 +243,68 @@ Trace syscalls, major and minor pagefaults:
As you can see, there was major pagefault in python process, from As you can see, there was major pagefault in python process, from
CRYPTO_push_info_ routine which faulted somewhere in libcrypto.so. CRYPTO_push_info_ routine which faulted somewhere in libcrypto.so.
Trace the first 4 open, openat or open_by_handle_at syscalls (in the future more syscalls may match here):
$ perf trace -e open* --max-events 4
[root@jouet perf]# trace -e open* --max-events 4
2272.992 ( 0.037 ms): gnome-shell/1370 openat(dfd: CWD, filename: /proc/self/stat) = 31
2277.481 ( 0.139 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65
3026.398 ( 0.076 ms): gnome-shell/3039 openat(dfd: CWD, filename: /proc/self/stat) = 65
4294.665 ( 0.015 ms): sed/15879 openat(dfd: CWD, filename: /etc/ld.so.cache, flags: CLOEXEC) = 3
$
Trace the first minor page fault when running a workload:
# perf trace -F min --max-stack=7 --max-events 1 sleep 1
0.000 ( 0.000 ms): sleep/18006 minfault [__clear_user+0x1a] => 0x5626efa56080 (?k)
__clear_user ([kernel.kallsyms])
load_elf_binary ([kernel.kallsyms])
search_binary_handler ([kernel.kallsyms])
__do_execve_file.isra.33 ([kernel.kallsyms])
__x64_sys_execve ([kernel.kallsyms])
do_syscall_64 ([kernel.kallsyms])
entry_SYSCALL_64 ([kernel.kallsyms])
#
Trace the next min page page fault to take place on the first CPU:
# perf trace -F min --call-graph=dwarf --max-events 1 --cpu 0
0.000 ( 0.000 ms): Web Content/17136 minfault [js::gc::Chunk::fetchNextDecommittedArena+0x4b] => 0x7fbe6181b000 (?.)
js::gc::FreeSpan::initAsEmpty (inlined)
js::gc::Arena::setAsNotAllocated (inlined)
js::gc::Chunk::fetchNextDecommittedArena (/usr/lib64/firefox/libxul.so)
js::gc::Chunk::allocateArena (/usr/lib64/firefox/libxul.so)
js::gc::GCRuntime::allocateArena (/usr/lib64/firefox/libxul.so)
js::gc::ArenaLists::allocateFromArena (/usr/lib64/firefox/libxul.so)
js::gc::GCRuntime::tryNewTenuredThing<JSString, (js::AllowGC)1> (inlined)
js::AllocateString<JSString, (js::AllowGC)1> (/usr/lib64/firefox/libxul.so)
js::Allocate<JSThinInlineString, (js::AllowGC)1> (inlined)
JSThinInlineString::new_<(js::AllowGC)1> (inlined)
AllocateInlineString<(js::AllowGC)1, unsigned char> (inlined)
js::ConcatStrings<(js::AllowGC)1> (/usr/lib64/firefox/libxul.so)
[0x18b26e6bc2bd] (/tmp/perf-17136.map)
#
Trace the next two sched:sched_switch events, four block:*_plug events, the
next block:*_unplug and the next three net:*dev_queue events, this last one
with a backtrace of at most 16 entries, system wide:
# perf trace -e sched:*switch/nr=2/,block:*_plug/nr=4/,block:*_unplug/nr=1/,net:*dev_queue/nr=3,max-stack=16/
0.000 :0/0 sched:sched_switch:swapper/2:0 [120] S ==> rcu_sched:10 [120]
0.015 rcu_sched/10 sched:sched_switch:rcu_sched:10 [120] R ==> swapper/2:0 [120]
254.198 irq/50-iwlwifi/680 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051f600 len=66
__dev_queue_xmit ([kernel.kallsyms])
273.977 :0/0 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051f600 len=78
__dev_queue_xmit ([kernel.kallsyms])
274.007 :0/0 net:net_dev_queue:dev=wlp3s0 skbaddr=0xffff93498051ff00 len=78
__dev_queue_xmit ([kernel.kallsyms])
2930.140 kworker/u16:58/2722 block:block_plug:[kworker/u16:58]
2930.162 kworker/u16:58/2722 block:block_unplug:[kworker/u16:58] 1
4466.094 jbd2/dm-2-8/748 block:block_plug:[jbd2/dm-2-8]
8050.123 kworker/u16:30/2694 block:block_plug:[kworker/u16:30]
8050.271 kworker/u16:30/2694 block:block_plug:[kworker/u16:30]
#
SEE ALSO SEE ALSO
-------- --------
linkperf:perf-record[1], linkperf:perf-script[1] linkperf:perf-record[1], linkperf:perf-script[1]
include ../scripts/Makefile.include include ../scripts/Makefile.include
include ../scripts/Makefile.arch
# The default target of this Makefile is... # The default target of this Makefile is...
all: all:
...@@ -385,6 +386,8 @@ export INSTALL SHELL_PATH ...@@ -385,6 +386,8 @@ export INSTALL SHELL_PATH
SHELL = $(SHELL_PATH) SHELL = $(SHELL_PATH)
linux_uapi_dir := $(srctree)/tools/include/uapi/linux linux_uapi_dir := $(srctree)/tools/include/uapi/linux
asm_generic_uapi_dir := $(srctree)/tools/include/uapi/asm-generic
arch_asm_uapi_dir := $(srctree)/tools/arch/$(ARCH)/include/uapi/asm/
beauty_outdir := $(OUTPUT)trace/beauty/generated beauty_outdir := $(OUTPUT)trace/beauty/generated
beauty_ioctl_outdir := $(beauty_outdir)/ioctl beauty_ioctl_outdir := $(beauty_outdir)/ioctl
...@@ -460,6 +463,18 @@ madvise_behavior_tbl := $(srctree)/tools/perf/trace/beauty/madvise_behavior.sh ...@@ -460,6 +463,18 @@ madvise_behavior_tbl := $(srctree)/tools/perf/trace/beauty/madvise_behavior.sh
$(madvise_behavior_array): $(madvise_hdr_dir)/mman-common.h $(madvise_behavior_tbl) $(madvise_behavior_array): $(madvise_hdr_dir)/mman-common.h $(madvise_behavior_tbl)
$(Q)$(SHELL) '$(madvise_behavior_tbl)' $(madvise_hdr_dir) > $@ $(Q)$(SHELL) '$(madvise_behavior_tbl)' $(madvise_hdr_dir) > $@
mmap_flags_array := $(beauty_outdir)/mmap_flags_array.c
mmap_flags_tbl := $(srctree)/tools/perf/trace/beauty/mmap_flags.sh
$(mmap_flags_array): $(asm_generic_uapi_dir)/mman.h $(asm_generic_uapi_dir)/mman-common.h $(arch_asm_uapi_dir)/mman.h $(mmap_flags_tbl)
$(Q)$(SHELL) '$(mmap_flags_tbl)' $(asm_generic_uapi_dir) $(arch_asm_uapi_dir) > $@
mount_flags_array := $(beauty_outdir)/mount_flags_array.c
mount_flags_tbl := $(srctree)/tools/perf/trace/beauty/mount_flags.sh
$(mount_flags_array): $(linux_uapi_dir)/fs.h $(mount_flags_tbl)
$(Q)$(SHELL) '$(mount_flags_tbl)' $(linux_uapi_dir) > $@
prctl_option_array := $(beauty_outdir)/prctl_option_array.c prctl_option_array := $(beauty_outdir)/prctl_option_array.c
prctl_hdr_dir := $(srctree)/tools/include/uapi/linux/ prctl_hdr_dir := $(srctree)/tools/include/uapi/linux/
prctl_option_tbl := $(srctree)/tools/perf/trace/beauty/prctl_option.sh prctl_option_tbl := $(srctree)/tools/perf/trace/beauty/prctl_option.sh
...@@ -577,6 +592,8 @@ prepare: $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)common-cmds.h archheaders $(drm_ioc ...@@ -577,6 +592,8 @@ prepare: $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)common-cmds.h archheaders $(drm_ioc
$(socket_ipproto_array) \ $(socket_ipproto_array) \
$(vhost_virtio_ioctl_array) \ $(vhost_virtio_ioctl_array) \
$(madvise_behavior_array) \ $(madvise_behavior_array) \
$(mmap_flags_array) \
$(mount_flags_array) \
$(perf_ioctl_array) \ $(perf_ioctl_array) \
$(prctl_option_array) \ $(prctl_option_array) \
$(arch_errno_name_array) $(arch_errno_name_array)
...@@ -863,6 +880,8 @@ clean:: $(LIBTRACEEVENT)-clean $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clea ...@@ -863,6 +880,8 @@ clean:: $(LIBTRACEEVENT)-clean $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clea
$(OUTPUT)tests/llvm-src-{base,kbuild,prologue,relocation}.c \ $(OUTPUT)tests/llvm-src-{base,kbuild,prologue,relocation}.c \
$(OUTPUT)pmu-events/pmu-events.c \ $(OUTPUT)pmu-events/pmu-events.c \
$(OUTPUT)$(madvise_behavior_array) \ $(OUTPUT)$(madvise_behavior_array) \
$(OUTPUT)$(mmap_flags_array) \
$(OUTPUT)$(mount_flags_array) \
$(OUTPUT)$(drm_ioctl_array) \ $(OUTPUT)$(drm_ioctl_array) \
$(OUTPUT)$(pkey_alloc_access_rights_array) \ $(OUTPUT)$(pkey_alloc_access_rights_array) \
$(OUTPUT)$(sndrv_ctl_ioctl_array) \ $(OUTPUT)$(sndrv_ctl_ioctl_array) \
......
...@@ -23,7 +23,7 @@ create_table_from_c() ...@@ -23,7 +23,7 @@ create_table_from_c()
{ {
local sc nr last_sc local sc nr last_sc
create_table_exe=`mktemp /tmp/create-table-XXXXXX` create_table_exe=`mktemp ${TMPDIR:-/tmp}/create-table-XXXXXX`
{ {
......
ifndef NO_DWARF ifndef NO_DWARF
PERF_HAVE_DWARF_REGS := 1 PERF_HAVE_DWARF_REGS := 1
endif endif
PERF_HAVE_JITDUMP := 1
// SPDX-License-Identifier: GPL-2.0
static int is_branch_cond(const char *cond)
{
if (cond[0] == '\0')
return 1;
if (cond[0] == 'a' && cond[1] == '\0')
return 1;
if (cond[0] == 'c' &&
(cond[1] == 'c' || cond[1] == 's') &&
cond[2] == '\0')
return 1;
if (cond[0] == 'e' &&
(cond[1] == '\0' ||
(cond[1] == 'q' && cond[2] == '\0')))
return 1;
if (cond[0] == 'g' &&
(cond[1] == '\0' ||
(cond[1] == 't' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'u' && cond[3] == '\0')))
return 1;
if (cond[0] == 'l' &&
(cond[1] == '\0' ||
(cond[1] == 't' && cond[2] == '\0') ||
(cond[1] == 'u' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'u' && cond[3] == '\0')))
return 1;
if (cond[0] == 'n' &&
(cond[1] == '\0' ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'z' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'g' && cond[3] == '\0')))
return 1;
if (cond[0] == 'b' &&
cond[1] == 'p' &&
cond[2] == 'o' &&
cond[3] == 's' &&
cond[4] == '\0')
return 1;
if (cond[0] == 'v' &&
(cond[1] == 'c' || cond[1] == 's') &&
cond[2] == '\0')
return 1;
if (cond[0] == 'b' &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
return 0;
}
static int is_branch_reg_cond(const char *cond)
{
if ((cond[0] == 'n' || cond[0] == 'l') &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
if (cond[0] == 'z' &&
cond[1] == '\0')
return 1;
if ((cond[0] == 'g' || cond[0] == 'l') &&
cond[1] == 'e' &&
cond[2] == 'z' &&
cond[3] == '\0')
return 1;
if (cond[0] == 'g' &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
return 0;
}
static int is_branch_float_cond(const char *cond)
{
if (cond[0] == '\0')
return 1;
if ((cond[0] == 'a' || cond[0] == 'e' ||
cond[0] == 'z' || cond[0] == 'g' ||
cond[0] == 'l' || cond[0] == 'n' ||
cond[0] == 'o' || cond[0] == 'u') &&
cond[1] == '\0')
return 1;
if (((cond[0] == 'g' && cond[1] == 'e') ||
(cond[0] == 'l' && (cond[1] == 'e' ||
cond[1] == 'g')) ||
(cond[0] == 'n' && (cond[1] == 'e' ||
cond[1] == 'z')) ||
(cond[0] == 'u' && (cond[1] == 'e' ||
cond[1] == 'g' ||
cond[1] == 'l'))) &&
cond[2] == '\0')
return 1;
if (cond[0] == 'u' &&
(cond[1] == 'g' || cond[1] == 'l') &&
cond[2] == 'e' &&
cond[3] == '\0')
return 1;
return 0;
}
static struct ins_ops *sparc__associate_instruction_ops(struct arch *arch, const char *name)
{
struct ins_ops *ops = NULL;
if (!strcmp(name, "call") ||
!strcmp(name, "jmp") ||
!strcmp(name, "jmpl")) {
ops = &call_ops;
} else if (!strcmp(name, "ret") ||
!strcmp(name, "retl") ||
!strcmp(name, "return")) {
ops = &ret_ops;
} else if (!strcmp(name, "mov")) {
ops = &mov_ops;
} else {
if (name[0] == 'c' &&
(name[1] == 'w' || name[1] == 'x'))
name += 2;
if (name[0] == 'b') {
const char *cond = name + 1;
if (cond[0] == 'r') {
if (is_branch_reg_cond(cond + 1))
ops = &jump_ops;
} else if (is_branch_cond(cond)) {
ops = &jump_ops;
}
} else if (name[0] == 'f' && name[1] == 'b') {
if (is_branch_float_cond(name + 2))
ops = &jump_ops;
}
}
if (ops)
arch__associate_ins_ops(arch, name, ops);
return ops;
}
static int sparc__annotate_init(struct arch *arch, char *cpuid __maybe_unused)
{
if (!arch->initialized) {
arch->initialized = true;
arch->associate_instruction_ops = sparc__associate_instruction_ops;
arch->objdump.comment_char = '#';
}
return 0;
}
...@@ -592,6 +592,9 @@ static void record__init_features(struct record *rec) ...@@ -592,6 +592,9 @@ static void record__init_features(struct record *rec)
if (!rec->opts.full_auxtrace) if (!rec->opts.full_auxtrace)
perf_header__clear_feat(&session->header, HEADER_AUXTRACE); perf_header__clear_feat(&session->header, HEADER_AUXTRACE);
if (!(rec->opts.use_clockid && rec->opts.clockid_res_ns))
perf_header__clear_feat(&session->header, HEADER_CLOCKID);
perf_header__clear_feat(&session->header, HEADER_STAT); perf_header__clear_feat(&session->header, HEADER_STAT);
} }
...@@ -897,6 +900,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) ...@@ -897,6 +900,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
record__init_features(rec); record__init_features(rec);
if (rec->opts.use_clockid && rec->opts.clockid_res_ns)
session->header.env.clockid_res_ns = rec->opts.clockid_res_ns;
if (forks) { if (forks) {
err = perf_evlist__prepare_workload(rec->evlist, &opts->target, err = perf_evlist__prepare_workload(rec->evlist, &opts->target,
argv, data->is_pipe, argv, data->is_pipe,
...@@ -1337,6 +1343,19 @@ static const struct clockid_map clockids[] = { ...@@ -1337,6 +1343,19 @@ static const struct clockid_map clockids[] = {
CLOCKID_END, CLOCKID_END,
}; };
static int get_clockid_res(clockid_t clk_id, u64 *res_ns)
{
struct timespec res;
*res_ns = 0;
if (!clock_getres(clk_id, &res))
*res_ns = res.tv_nsec + res.tv_sec * NSEC_PER_SEC;
else
pr_warning("WARNING: Failed to determine specified clock resolution.\n");
return 0;
}
static int parse_clockid(const struct option *opt, const char *str, int unset) static int parse_clockid(const struct option *opt, const char *str, int unset)
{ {
struct record_opts *opts = (struct record_opts *)opt->value; struct record_opts *opts = (struct record_opts *)opt->value;
...@@ -1360,7 +1379,7 @@ static int parse_clockid(const struct option *opt, const char *str, int unset) ...@@ -1360,7 +1379,7 @@ static int parse_clockid(const struct option *opt, const char *str, int unset)
/* if its a number, we're done */ /* if its a number, we're done */
if (sscanf(str, "%d", &opts->clockid) == 1) if (sscanf(str, "%d", &opts->clockid) == 1)
return 0; return get_clockid_res(opts->clockid, &opts->clockid_res_ns);
/* allow a "CLOCK_" prefix to the name */ /* allow a "CLOCK_" prefix to the name */
if (!strncasecmp(str, "CLOCK_", 6)) if (!strncasecmp(str, "CLOCK_", 6))
...@@ -1369,7 +1388,8 @@ static int parse_clockid(const struct option *opt, const char *str, int unset) ...@@ -1369,7 +1388,8 @@ static int parse_clockid(const struct option *opt, const char *str, int unset)
for (cm = clockids; cm->name; cm++) { for (cm = clockids; cm->name; cm++) {
if (!strcasecmp(str, cm->name)) { if (!strcasecmp(str, cm->name)) {
opts->clockid = cm->clockid; opts->clockid = cm->clockid;
return 0; return get_clockid_res(opts->clockid,
&opts->clockid_res_ns);
} }
} }
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <subcmd/pager.h>
#include "sane_ctype.h" #include "sane_ctype.h"
...@@ -912,7 +913,7 @@ static int grab_bb(u8 *buffer, u64 start, u64 end, ...@@ -912,7 +913,7 @@ static int grab_bb(u8 *buffer, u64 start, u64 end,
static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en, static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en,
struct perf_insn *x, u8 *inbuf, int len, struct perf_insn *x, u8 *inbuf, int len,
int insn, FILE *fp) int insn, FILE *fp, int *total_cycles)
{ {
int printed = fprintf(fp, "\t%016" PRIx64 "\t%-30s\t#%s%s%s%s", ip, int printed = fprintf(fp, "\t%016" PRIx64 "\t%-30s\t#%s%s%s%s", ip,
dump_insn(x, ip, inbuf, len, NULL), dump_insn(x, ip, inbuf, len, NULL),
...@@ -921,7 +922,8 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en, ...@@ -921,7 +922,8 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en,
en->flags.in_tx ? " INTX" : "", en->flags.in_tx ? " INTX" : "",
en->flags.abort ? " ABORT" : ""); en->flags.abort ? " ABORT" : "");
if (en->flags.cycles) { if (en->flags.cycles) {
printed += fprintf(fp, " %d cycles", en->flags.cycles); *total_cycles += en->flags.cycles;
printed += fprintf(fp, " %d cycles [%d]", en->flags.cycles, *total_cycles);
if (insn) if (insn)
printed += fprintf(fp, " %.2f IPC", (float)insn / en->flags.cycles); printed += fprintf(fp, " %.2f IPC", (float)insn / en->flags.cycles);
} }
...@@ -978,6 +980,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, ...@@ -978,6 +980,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample,
u8 buffer[MAXBB]; u8 buffer[MAXBB];
unsigned off; unsigned off;
struct symbol *lastsym = NULL; struct symbol *lastsym = NULL;
int total_cycles = 0;
if (!(br && br->nr)) if (!(br && br->nr))
return 0; return 0;
...@@ -998,7 +1001,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, ...@@ -998,7 +1001,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample,
printed += ip__fprintf_sym(br->entries[nr - 1].from, thread, printed += ip__fprintf_sym(br->entries[nr - 1].from, thread,
x.cpumode, x.cpu, &lastsym, attr, fp); x.cpumode, x.cpu, &lastsym, attr, fp);
printed += ip__fprintf_jump(br->entries[nr - 1].from, &br->entries[nr - 1], printed += ip__fprintf_jump(br->entries[nr - 1].from, &br->entries[nr - 1],
&x, buffer, len, 0, fp); &x, buffer, len, 0, fp, &total_cycles);
} }
/* Print all blocks */ /* Print all blocks */
...@@ -1026,7 +1029,8 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, ...@@ -1026,7 +1029,8 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample,
printed += ip__fprintf_sym(ip, thread, x.cpumode, x.cpu, &lastsym, attr, fp); printed += ip__fprintf_sym(ip, thread, x.cpumode, x.cpu, &lastsym, attr, fp);
if (ip == end) { if (ip == end) {
printed += ip__fprintf_jump(ip, &br->entries[i], &x, buffer + off, len - off, insn, fp); printed += ip__fprintf_jump(ip, &br->entries[i], &x, buffer + off, len - off, insn, fp,
&total_cycles);
break; break;
} else { } else {
printed += fprintf(fp, "\t%016" PRIx64 "\t%s\n", ip, printed += fprintf(fp, "\t%016" PRIx64 "\t%s\n", ip,
...@@ -1104,6 +1108,35 @@ static int perf_sample__fprintf_addr(struct perf_sample *sample, ...@@ -1104,6 +1108,35 @@ static int perf_sample__fprintf_addr(struct perf_sample *sample,
return printed; return printed;
} }
static const char *resolve_branch_sym(struct perf_sample *sample,
struct perf_evsel *evsel,
struct thread *thread,
struct addr_location *al,
u64 *ip)
{
struct addr_location addr_al;
struct perf_event_attr *attr = &evsel->attr;
const char *name = NULL;
if (sample->flags & (PERF_IP_FLAG_CALL | PERF_IP_FLAG_TRACE_BEGIN)) {
if (sample_addr_correlates_sym(attr)) {
thread__resolve(thread, &addr_al, sample);
if (addr_al.sym)
name = addr_al.sym->name;
else
*ip = sample->addr;
} else {
*ip = sample->addr;
}
} else if (sample->flags & (PERF_IP_FLAG_RETURN | PERF_IP_FLAG_TRACE_END)) {
if (al->sym)
name = al->sym->name;
else
*ip = sample->ip;
}
return name;
}
static int perf_sample__fprintf_callindent(struct perf_sample *sample, static int perf_sample__fprintf_callindent(struct perf_sample *sample,
struct perf_evsel *evsel, struct perf_evsel *evsel,
struct thread *thread, struct thread *thread,
...@@ -1111,7 +1144,6 @@ static int perf_sample__fprintf_callindent(struct perf_sample *sample, ...@@ -1111,7 +1144,6 @@ static int perf_sample__fprintf_callindent(struct perf_sample *sample,
{ {
struct perf_event_attr *attr = &evsel->attr; struct perf_event_attr *attr = &evsel->attr;
size_t depth = thread_stack__depth(thread); size_t depth = thread_stack__depth(thread);
struct addr_location addr_al;
const char *name = NULL; const char *name = NULL;
static int spacing; static int spacing;
int len = 0; int len = 0;
...@@ -1125,22 +1157,7 @@ static int perf_sample__fprintf_callindent(struct perf_sample *sample, ...@@ -1125,22 +1157,7 @@ static int perf_sample__fprintf_callindent(struct perf_sample *sample,
if (thread->ts && sample->flags & PERF_IP_FLAG_RETURN) if (thread->ts && sample->flags & PERF_IP_FLAG_RETURN)
depth += 1; depth += 1;
if (sample->flags & (PERF_IP_FLAG_CALL | PERF_IP_FLAG_TRACE_BEGIN)) { name = resolve_branch_sym(sample, evsel, thread, al, &ip);
if (sample_addr_correlates_sym(attr)) {
thread__resolve(thread, &addr_al, sample);
if (addr_al.sym)
name = addr_al.sym->name;
else
ip = sample->addr;
} else {
ip = sample->addr;
}
} else if (sample->flags & (PERF_IP_FLAG_RETURN | PERF_IP_FLAG_TRACE_END)) {
if (al->sym)
name = al->sym->name;
else
ip = sample->ip;
}
if (PRINT_FIELD(DSO) && !(PRINT_FIELD(IP) || PRINT_FIELD(ADDR))) { if (PRINT_FIELD(DSO) && !(PRINT_FIELD(IP) || PRINT_FIELD(ADDR))) {
dlen += fprintf(fp, "("); dlen += fprintf(fp, "(");
...@@ -1646,6 +1663,47 @@ static void perf_sample__fprint_metric(struct perf_script *script, ...@@ -1646,6 +1663,47 @@ static void perf_sample__fprint_metric(struct perf_script *script,
} }
} }
static bool show_event(struct perf_sample *sample,
struct perf_evsel *evsel,
struct thread *thread,
struct addr_location *al)
{
int depth = thread_stack__depth(thread);
if (!symbol_conf.graph_function)
return true;
if (thread->filter) {
if (depth <= thread->filter_entry_depth) {
thread->filter = false;
return false;
}
return true;
} else {
const char *s = symbol_conf.graph_function;
u64 ip;
const char *name = resolve_branch_sym(sample, evsel, thread, al,
&ip);
unsigned nlen;
if (!name)
return false;
nlen = strlen(name);
while (*s) {
unsigned len = strcspn(s, ",");
if (nlen == len && !strncmp(name, s, len)) {
thread->filter = true;
thread->filter_entry_depth = depth;
return true;
}
s += len;
if (*s == ',')
s++;
}
return false;
}
}
static void process_event(struct perf_script *script, static void process_event(struct perf_script *script,
struct perf_sample *sample, struct perf_evsel *evsel, struct perf_sample *sample, struct perf_evsel *evsel,
struct addr_location *al, struct addr_location *al,
...@@ -1660,6 +1718,9 @@ static void process_event(struct perf_script *script, ...@@ -1660,6 +1718,9 @@ static void process_event(struct perf_script *script,
if (output[type].fields == 0) if (output[type].fields == 0)
return; return;
if (!show_event(sample, evsel, thread, al))
return;
++es->samples; ++es->samples;
perf_sample__fprintf_start(sample, thread, evsel, perf_sample__fprintf_start(sample, thread, evsel,
...@@ -1737,6 +1798,9 @@ static void process_event(struct perf_script *script, ...@@ -1737,6 +1798,9 @@ static void process_event(struct perf_script *script,
if (PRINT_FIELD(METRIC)) if (PRINT_FIELD(METRIC))
perf_sample__fprint_metric(script, thread, evsel, sample, fp); perf_sample__fprint_metric(script, thread, evsel, sample, fp);
if (verbose)
fflush(fp);
} }
static struct scripting_ops *scripting_ops; static struct scripting_ops *scripting_ops;
...@@ -3100,6 +3164,44 @@ static int perf_script__process_auxtrace_info(struct perf_session *session, ...@@ -3100,6 +3164,44 @@ static int perf_script__process_auxtrace_info(struct perf_session *session,
#define perf_script__process_auxtrace_info 0 #define perf_script__process_auxtrace_info 0
#endif #endif
static int parse_insn_trace(const struct option *opt __maybe_unused,
const char *str __maybe_unused,
int unset __maybe_unused)
{
parse_output_fields(NULL, "+insn,-event,-period", 0);
itrace_parse_synth_opts(opt, "i0ns", 0);
nanosecs = true;
return 0;
}
static int parse_xed(const struct option *opt __maybe_unused,
const char *str __maybe_unused,
int unset __maybe_unused)
{
force_pager("xed -F insn: -A -64 | less");
return 0;
}
static int parse_call_trace(const struct option *opt __maybe_unused,
const char *str __maybe_unused,
int unset __maybe_unused)
{
parse_output_fields(NULL, "-ip,-addr,-event,-period,+callindent", 0);
itrace_parse_synth_opts(opt, "cewp", 0);
nanosecs = true;
return 0;
}
static int parse_callret_trace(const struct option *opt __maybe_unused,
const char *str __maybe_unused,
int unset __maybe_unused)
{
parse_output_fields(NULL, "-ip,-addr,-event,-period,+callindent,+flags", 0);
itrace_parse_synth_opts(opt, "crewp", 0);
nanosecs = true;
return 0;
}
int cmd_script(int argc, const char **argv) int cmd_script(int argc, const char **argv)
{ {
bool show_full_info = false; bool show_full_info = false;
...@@ -3109,7 +3211,10 @@ int cmd_script(int argc, const char **argv) ...@@ -3109,7 +3211,10 @@ int cmd_script(int argc, const char **argv)
char *rec_script_path = NULL; char *rec_script_path = NULL;
char *rep_script_path = NULL; char *rep_script_path = NULL;
struct perf_session *session; struct perf_session *session;
struct itrace_synth_opts itrace_synth_opts = { .set = false, }; struct itrace_synth_opts itrace_synth_opts = {
.set = false,
.default_no_sample = true,
};
char *script_path = NULL; char *script_path = NULL;
const char **__argv; const char **__argv;
int i, j, err = 0; int i, j, err = 0;
...@@ -3184,6 +3289,16 @@ int cmd_script(int argc, const char **argv) ...@@ -3184,6 +3289,16 @@ int cmd_script(int argc, const char **argv)
"system-wide collection from all CPUs"), "system-wide collection from all CPUs"),
OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]", OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
"only consider these symbols"), "only consider these symbols"),
OPT_CALLBACK_OPTARG(0, "insn-trace", &itrace_synth_opts, NULL, NULL,
"Decode instructions from itrace", parse_insn_trace),
OPT_CALLBACK_OPTARG(0, "xed", NULL, NULL, NULL,
"Run xed disassembler on output", parse_xed),
OPT_CALLBACK_OPTARG(0, "call-trace", &itrace_synth_opts, NULL, NULL,
"Decode calls from from itrace", parse_call_trace),
OPT_CALLBACK_OPTARG(0, "call-ret-trace", &itrace_synth_opts, NULL, NULL,
"Decode calls and returns from itrace", parse_callret_trace),
OPT_STRING(0, "graph-function", &symbol_conf.graph_function, "symbol[,symbol...]",
"Only print symbols and callees with --call-trace/--call-ret-trace"),
OPT_STRING(0, "stop-bt", &symbol_conf.bt_stop_list_str, "symbol[,symbol...]", OPT_STRING(0, "stop-bt", &symbol_conf.bt_stop_list_str, "symbol[,symbol...]",
"Stop display of callgraph at these symbols"), "Stop display of callgraph at these symbols"),
OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"), OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"),
...@@ -3417,8 +3532,10 @@ int cmd_script(int argc, const char **argv) ...@@ -3417,8 +3532,10 @@ int cmd_script(int argc, const char **argv)
exit(-1); exit(-1);
} }
if (!script_name) if (!script_name) {
setup_pager(); setup_pager();
use_browser = 0;
}
session = perf_session__new(&data, false, &script.tool); session = perf_session__new(&data, false, &script.tool);
if (session == NULL) if (session == NULL)
...@@ -3439,7 +3556,8 @@ int cmd_script(int argc, const char **argv) ...@@ -3439,7 +3556,8 @@ int cmd_script(int argc, const char **argv)
script.session = session; script.session = session;
script__setup_sample_type(&script); script__setup_sample_type(&script);
if (output[PERF_TYPE_HARDWARE].fields & PERF_OUTPUT_CALLINDENT) if ((output[PERF_TYPE_HARDWARE].fields & PERF_OUTPUT_CALLINDENT) ||
symbol_conf.graph_function)
itrace_synth_opts.thread_stack = true; itrace_synth_opts.thread_stack = true;
session->itrace_synth_opts = &itrace_synth_opts; session->itrace_synth_opts = &itrace_synth_opts;
......
...@@ -409,6 +409,28 @@ static struct perf_evsel *perf_evsel__reset_weak_group(struct perf_evsel *evsel) ...@@ -409,6 +409,28 @@ static struct perf_evsel *perf_evsel__reset_weak_group(struct perf_evsel *evsel)
return leader; return leader;
} }
static bool is_target_alive(struct target *_target,
struct thread_map *threads)
{
struct stat st;
int i;
if (!target__has_task(_target))
return true;
for (i = 0; i < threads->nr; i++) {
char path[PATH_MAX];
scnprintf(path, PATH_MAX, "%s/%d", procfs__mountpoint(),
threads->map[i].pid);
if (!stat(path, &st))
return true;
}
return false;
}
static int __run_perf_stat(int argc, const char **argv, int run_idx) static int __run_perf_stat(int argc, const char **argv, int run_idx)
{ {
int interval = stat_config.interval; int interval = stat_config.interval;
...@@ -579,6 +601,8 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) ...@@ -579,6 +601,8 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx)
enable_counters(); enable_counters();
while (!done) { while (!done) {
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
if (!is_target_alive(&target, evsel_list->threads))
break;
if (timeout) if (timeout)
break; break;
if (interval) { if (interval) {
......
...@@ -1134,11 +1134,6 @@ static int __cmd_top(struct perf_top *top) ...@@ -1134,11 +1134,6 @@ static int __cmd_top(struct perf_top *top)
if (!target__none(&opts->target)) if (!target__none(&opts->target))
perf_evlist__enable(top->evlist); perf_evlist__enable(top->evlist);
/* Wait for a minimal set of events before starting the snapshot */
perf_evlist__poll(top->evlist, 100);
perf_top__mmap_read(top);
ret = -1; ret = -1;
if (pthread_create(&thread, NULL, (use_browser > 0 ? display_thread_tui : if (pthread_create(&thread, NULL, (use_browser > 0 ? display_thread_tui :
display_thread), top)) { display_thread), top)) {
...@@ -1156,6 +1151,11 @@ static int __cmd_top(struct perf_top *top) ...@@ -1156,6 +1151,11 @@ static int __cmd_top(struct perf_top *top)
} }
} }
/* Wait for a minimal set of events before starting the snapshot */
perf_evlist__poll(top->evlist, 100);
perf_top__mmap_read(top);
while (!done) { while (!done) {
u64 hits = top->samples; u64 hits = top->samples;
...@@ -1257,7 +1257,14 @@ int cmd_top(int argc, const char **argv) ...@@ -1257,7 +1257,14 @@ int cmd_top(int argc, const char **argv)
.uses_mmap = true, .uses_mmap = true,
}, },
.proc_map_timeout = 500, .proc_map_timeout = 500,
.overwrite = 1, /*
* FIXME: This will lose PERF_RECORD_MMAP and other metadata
* when we pause, fix that and reenable. Probably using a
* separate evlist with a dummy event, i.e. a non-overwrite
* ring buffer just for metadata events, while PERF_RECORD_SAMPLE
* stays in overwrite mode. -acme
* */
.overwrite = 0,
}, },
.max_stack = sysctl__max_stack(), .max_stack = sysctl__max_stack(),
.annotation_opts = annotation__default_options, .annotation_opts = annotation__default_options,
...@@ -1372,6 +1379,8 @@ int cmd_top(int argc, const char **argv) ...@@ -1372,6 +1379,8 @@ int cmd_top(int argc, const char **argv)
"Show raw trace event output (do not use print fmt or plugins)"), "Show raw trace event output (do not use print fmt or plugins)"),
OPT_BOOLEAN(0, "hierarchy", &symbol_conf.report_hierarchy, OPT_BOOLEAN(0, "hierarchy", &symbol_conf.report_hierarchy,
"Show entries in a hierarchy"), "Show entries in a hierarchy"),
OPT_BOOLEAN(0, "overwrite", &top.record_opts.overwrite,
"Use a backward ring buffer, default: no"),
OPT_BOOLEAN(0, "force", &symbol_conf.force, "don't complain, do it"), OPT_BOOLEAN(0, "force", &symbol_conf.force, "don't complain, do it"),
OPT_UINTEGER(0, "num-thread-synthesize", &top.nr_threads_synthesize, OPT_UINTEGER(0, "num-thread-synthesize", &top.nr_threads_synthesize,
"number of thread to run event synthesize"), "number of thread to run event synthesize"),
......
...@@ -89,6 +89,8 @@ struct trace { ...@@ -89,6 +89,8 @@ struct trace {
u64 base_time; u64 base_time;
FILE *output; FILE *output;
unsigned long nr_events; unsigned long nr_events;
unsigned long nr_events_printed;
unsigned long max_events;
struct strlist *ev_qualifier; struct strlist *ev_qualifier;
struct { struct {
size_t nr; size_t nr;
...@@ -612,6 +614,7 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, ...@@ -612,6 +614,7 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size,
struct syscall_arg_fmt { struct syscall_arg_fmt {
size_t (*scnprintf)(char *bf, size_t size, struct syscall_arg *arg); size_t (*scnprintf)(char *bf, size_t size, struct syscall_arg *arg);
unsigned long (*mask_val)(struct syscall_arg *arg, unsigned long val);
void *parm; void *parm;
const char *name; const char *name;
bool show_zero; bool show_zero;
...@@ -723,6 +726,10 @@ static struct syscall_fmt { ...@@ -723,6 +726,10 @@ static struct syscall_fmt {
.arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ },
[2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ },
[3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ }, }, }, [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ }, }, },
{ .name = "mount",
.arg = { [0] = { .scnprintf = SCA_FILENAME, /* dev_name */ },
[3] = { .scnprintf = SCA_MOUNT_FLAGS, /* flags */
.mask_val = SCAMV_MOUNT_FLAGS, /* flags */ }, }, },
{ .name = "mprotect", { .name = "mprotect",
.arg = { [0] = { .scnprintf = SCA_HEX, /* start */ }, .arg = { [0] = { .scnprintf = SCA_HEX, /* start */ },
[2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, }, }, [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, }, },
...@@ -832,7 +839,8 @@ static struct syscall_fmt { ...@@ -832,7 +839,8 @@ static struct syscall_fmt {
.arg = { [2] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, .arg = { [2] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, },
{ .name = "tkill", { .name = "tkill",
.arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, .arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, },
{ .name = "umount2", .alias = "umount", }, { .name = "umount2", .alias = "umount",
.arg = { [0] = { .scnprintf = SCA_FILENAME, /* name */ }, }, },
{ .name = "uname", .alias = "newuname", }, { .name = "uname", .alias = "newuname", },
{ .name = "unlinkat", { .name = "unlinkat",
.arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, },
...@@ -856,6 +864,18 @@ static struct syscall_fmt *syscall_fmt__find(const char *name) ...@@ -856,6 +864,18 @@ static struct syscall_fmt *syscall_fmt__find(const char *name)
return bsearch(name, syscall_fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp); return bsearch(name, syscall_fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp);
} }
static struct syscall_fmt *syscall_fmt__find_by_alias(const char *alias)
{
int i, nmemb = ARRAY_SIZE(syscall_fmts);
for (i = 0; i < nmemb; ++i) {
if (syscall_fmts[i].alias && strcmp(syscall_fmts[i].alias, alias) == 0)
return &syscall_fmts[i];
}
return NULL;
}
/* /*
* is_exit: is this "exit" or "exit_group"? * is_exit: is this "exit" or "exit_group"?
* is_open: is this "open" or "openat"? To associate the fd returned in sys_exit with the pathname in sys_enter. * is_open: is this "open" or "openat"? To associate the fd returned in sys_exit with the pathname in sys_enter.
...@@ -1485,6 +1505,19 @@ static size_t syscall__scnprintf_name(struct syscall *sc, char *bf, size_t size, ...@@ -1485,6 +1505,19 @@ static size_t syscall__scnprintf_name(struct syscall *sc, char *bf, size_t size,
return scnprintf(bf, size, "arg%d: ", arg->idx); return scnprintf(bf, size, "arg%d: ", arg->idx);
} }
/*
* Check if the value is in fact zero, i.e. mask whatever needs masking, such
* as mount 'flags' argument that needs ignoring some magic flag, see comment
* in tools/perf/trace/beauty/mount_flags.c
*/
static unsigned long syscall__mask_val(struct syscall *sc, struct syscall_arg *arg, unsigned long val)
{
if (sc->arg_fmt && sc->arg_fmt[arg->idx].mask_val)
return sc->arg_fmt[arg->idx].mask_val(arg, val);
return val;
}
static size_t syscall__scnprintf_val(struct syscall *sc, char *bf, size_t size, static size_t syscall__scnprintf_val(struct syscall *sc, char *bf, size_t size,
struct syscall_arg *arg, unsigned long val) struct syscall_arg *arg, unsigned long val)
{ {
...@@ -1533,6 +1566,11 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, ...@@ -1533,6 +1566,11 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size,
continue; continue;
val = syscall_arg__val(&arg, arg.idx); val = syscall_arg__val(&arg, arg.idx);
/*
* Some syscall args need some mask, most don't and
* return val untouched.
*/
val = syscall__mask_val(sc, &arg, val);
/* /*
* Suppress this argument if its value is zero and * Suppress this argument if its value is zero and
...@@ -1664,6 +1702,8 @@ static int trace__printf_interrupted_entry(struct trace *trace) ...@@ -1664,6 +1702,8 @@ static int trace__printf_interrupted_entry(struct trace *trace)
printed += fprintf(trace->output, "%-70s) ...\n", ttrace->entry_str); printed += fprintf(trace->output, "%-70s) ...\n", ttrace->entry_str);
ttrace->entry_pending = false; ttrace->entry_pending = false;
++trace->nr_events_printed;
return printed; return printed;
} }
...@@ -1810,12 +1850,14 @@ static int trace__resolve_callchain(struct trace *trace, struct perf_evsel *evse ...@@ -1810,12 +1850,14 @@ static int trace__resolve_callchain(struct trace *trace, struct perf_evsel *evse
int max_stack = evsel->attr.sample_max_stack ? int max_stack = evsel->attr.sample_max_stack ?
evsel->attr.sample_max_stack : evsel->attr.sample_max_stack :
trace->max_stack; trace->max_stack;
int err;
if (machine__resolve(trace->host, &al, sample) < 0 || if (machine__resolve(trace->host, &al, sample) < 0)
thread__resolve_callchain(al.thread, cursor, evsel, sample, NULL, NULL, max_stack))
return -1; return -1;
return 0; err = thread__resolve_callchain(al.thread, cursor, evsel, sample, NULL, NULL, max_stack);
addr_location__put(&al);
return err;
} }
static int trace__fprintf_callchain(struct trace *trace, struct perf_sample *sample) static int trace__fprintf_callchain(struct trace *trace, struct perf_sample *sample)
...@@ -1940,6 +1982,13 @@ errno_print: { ...@@ -1940,6 +1982,13 @@ errno_print: {
fputc('\n', trace->output); fputc('\n', trace->output);
/*
* We only consider an 'event' for the sake of --max-events a non-filtered
* sys_enter + sys_exit and other tracepoint events.
*/
if (++trace->nr_events_printed == trace->max_events && trace->max_events != ULONG_MAX)
interrupted = true;
if (callchain_ret > 0) if (callchain_ret > 0)
trace__fprintf_callchain(trace, sample); trace__fprintf_callchain(trace, sample);
else if (callchain_ret < 0) else if (callchain_ret < 0)
...@@ -2072,14 +2121,25 @@ static void bpf_output__fprintf(struct trace *trace, ...@@ -2072,14 +2121,25 @@ static void bpf_output__fprintf(struct trace *trace,
{ {
binary__fprintf(sample->raw_data, sample->raw_size, 8, binary__fprintf(sample->raw_data, sample->raw_size, 8,
bpf_output__printer, NULL, trace->output); bpf_output__printer, NULL, trace->output);
++trace->nr_events_printed;
} }
static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,
union perf_event *event __maybe_unused, union perf_event *event __maybe_unused,
struct perf_sample *sample) struct perf_sample *sample)
{ {
struct thread *thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); struct thread *thread;
int callchain_ret = 0; int callchain_ret = 0;
/*
* Check if we called perf_evsel__disable(evsel) due to, for instance,
* this event's max_events having been hit and this is an entry coming
* from the ring buffer that we should discard, since the max events
* have already been considered/printed.
*/
if (evsel->disabled)
return 0;
thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
if (sample->callchain) { if (sample->callchain) {
callchain_ret = trace__resolve_callchain(trace, evsel, sample, &callchain_cursor); callchain_ret = trace__resolve_callchain(trace, evsel, sample, &callchain_cursor);
...@@ -2127,6 +2187,12 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, ...@@ -2127,6 +2187,12 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,
event_format__fprintf(evsel->tp_format, sample->cpu, event_format__fprintf(evsel->tp_format, sample->cpu,
sample->raw_data, sample->raw_size, sample->raw_data, sample->raw_size,
trace->output); trace->output);
++trace->nr_events_printed;
if (evsel->max_events != ULONG_MAX && ++evsel->nr_events_printed == evsel->max_events) {
perf_evsel__disable(evsel);
perf_evsel__close(evsel);
}
} }
} }
...@@ -2137,8 +2203,8 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, ...@@ -2137,8 +2203,8 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel,
trace__fprintf_callchain(trace, sample); trace__fprintf_callchain(trace, sample);
else if (callchain_ret < 0) else if (callchain_ret < 0)
pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel));
thread__put(thread);
out: out:
thread__put(thread);
return 0; return 0;
} }
...@@ -2225,6 +2291,8 @@ static int trace__pgfault(struct trace *trace, ...@@ -2225,6 +2291,8 @@ static int trace__pgfault(struct trace *trace,
trace__fprintf_callchain(trace, sample); trace__fprintf_callchain(trace, sample);
else if (callchain_ret < 0) else if (callchain_ret < 0)
pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel));
++trace->nr_events_printed;
out: out:
err = 0; err = 0;
out_put: out_put:
...@@ -2402,6 +2470,9 @@ static void trace__handle_event(struct trace *trace, union perf_event *event, st ...@@ -2402,6 +2470,9 @@ static void trace__handle_event(struct trace *trace, union perf_event *event, st
tracepoint_handler handler = evsel->handler; tracepoint_handler handler = evsel->handler;
handler(trace, evsel, event, sample); handler(trace, evsel, event, sample);
} }
if (trace->nr_events_printed >= trace->max_events && trace->max_events != ULONG_MAX)
interrupted = true;
} }
static int trace__add_syscall_newtp(struct trace *trace) static int trace__add_syscall_newtp(struct trace *trace)
...@@ -2706,7 +2777,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv) ...@@ -2706,7 +2777,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
int timeout = done ? 100 : -1; int timeout = done ? 100 : -1;
if (!draining && perf_evlist__poll(evlist, timeout) > 0) { if (!draining && perf_evlist__poll(evlist, timeout) > 0) {
if (perf_evlist__filter_pollfd(evlist, POLLERR | POLLHUP) == 0) if (perf_evlist__filter_pollfd(evlist, POLLERR | POLLHUP | POLLNVAL) == 0)
draining = true; draining = true;
goto again; goto again;
...@@ -3138,6 +3209,7 @@ static int trace__parse_events_option(const struct option *opt, const char *str, ...@@ -3138,6 +3209,7 @@ static int trace__parse_events_option(const struct option *opt, const char *str,
int len = strlen(str) + 1, err = -1, list, idx; int len = strlen(str) + 1, err = -1, list, idx;
char *strace_groups_dir = system_path(STRACE_GROUPS_DIR); char *strace_groups_dir = system_path(STRACE_GROUPS_DIR);
char group_name[PATH_MAX]; char group_name[PATH_MAX];
struct syscall_fmt *fmt;
if (strace_groups_dir == NULL) if (strace_groups_dir == NULL)
return -1; return -1;
...@@ -3155,12 +3227,19 @@ static int trace__parse_events_option(const struct option *opt, const char *str, ...@@ -3155,12 +3227,19 @@ static int trace__parse_events_option(const struct option *opt, const char *str,
if (syscalltbl__id(trace->sctbl, s) >= 0 || if (syscalltbl__id(trace->sctbl, s) >= 0 ||
syscalltbl__strglobmatch_first(trace->sctbl, s, &idx) >= 0) { syscalltbl__strglobmatch_first(trace->sctbl, s, &idx) >= 0) {
list = 1; list = 1;
goto do_concat;
}
fmt = syscall_fmt__find_by_alias(s);
if (fmt != NULL) {
list = 1;
s = fmt->name;
} else { } else {
path__join(group_name, sizeof(group_name), strace_groups_dir, s); path__join(group_name, sizeof(group_name), strace_groups_dir, s);
if (access(group_name, R_OK) == 0) if (access(group_name, R_OK) == 0)
list = 1; list = 1;
} }
do_concat:
if (lists[list]) { if (lists[list]) {
sprintf(lists[list] + strlen(lists[list]), ",%s", s); sprintf(lists[list] + strlen(lists[list]), ",%s", s);
} else { } else {
...@@ -3249,6 +3328,7 @@ int cmd_trace(int argc, const char **argv) ...@@ -3249,6 +3328,7 @@ int cmd_trace(int argc, const char **argv)
.trace_syscalls = false, .trace_syscalls = false,
.kernel_syscallchains = false, .kernel_syscallchains = false,
.max_stack = UINT_MAX, .max_stack = UINT_MAX,
.max_events = ULONG_MAX,
}; };
const char *output_name = NULL; const char *output_name = NULL;
const struct option trace_options[] = { const struct option trace_options[] = {
...@@ -3301,6 +3381,8 @@ int cmd_trace(int argc, const char **argv) ...@@ -3301,6 +3381,8 @@ int cmd_trace(int argc, const char **argv)
&record_parse_callchain_opt), &record_parse_callchain_opt),
OPT_BOOLEAN(0, "kernel-syscall-graph", &trace.kernel_syscallchains, OPT_BOOLEAN(0, "kernel-syscall-graph", &trace.kernel_syscallchains,
"Show the kernel callchains on the syscall exit path"), "Show the kernel callchains on the syscall exit path"),
OPT_ULONG(0, "max-events", &trace.max_events,
"Set the maximum number of events to print, exit after that is reached. "),
OPT_UINTEGER(0, "min-stack", &trace.min_stack, OPT_UINTEGER(0, "min-stack", &trace.min_stack,
"Set the minimum stack depth when parsing the callchain, " "Set the minimum stack depth when parsing the callchain, "
"anything below the specified depth will be ignored."), "anything below the specified depth will be ignored."),
......
...@@ -5,6 +5,7 @@ HEADERS=' ...@@ -5,6 +5,7 @@ HEADERS='
include/uapi/drm/drm.h include/uapi/drm/drm.h
include/uapi/drm/i915_drm.h include/uapi/drm/i915_drm.h
include/uapi/linux/fcntl.h include/uapi/linux/fcntl.h
include/uapi/linux/fs.h
include/uapi/linux/kcmp.h include/uapi/linux/kcmp.h
include/uapi/linux/kvm.h include/uapi/linux/kvm.h
include/uapi/linux/in.h include/uapi/linux/in.h
......
...@@ -81,6 +81,7 @@ struct record_opts { ...@@ -81,6 +81,7 @@ struct record_opts {
unsigned initial_delay; unsigned initial_delay;
bool use_clockid; bool use_clockid;
clockid_t clockid; clockid_t clockid;
u64 clockid_res_ns;
unsigned int proc_map_timeout; unsigned int proc_map_timeout;
}; };
......
#!/usr/bin/python2
# call-graph-from-sql.py: create call-graph from sql database
# Copyright (c) 2014-2017, Intel Corporation.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms and conditions of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
# To use this script you will need to have exported data using either the
# export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
# scripts for details.
#
# Following on from the example in the export scripts, a
# call-graph can be displayed for the pt_example database like this:
#
# python tools/perf/scripts/python/call-graph-from-sql.py pt_example
#
# Note that for PostgreSQL, this script supports connecting to remote databases
# by setting hostname, port, username, password, and dbname e.g.
#
# python tools/perf/scripts/python/call-graph-from-sql.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
#
# The result is a GUI window with a tree representing a context-sensitive
# call-graph. Expanding a couple of levels of the tree and adjusting column
# widths to suit will display something like:
#
# Call Graph: pt_example
# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
# v- ls
# v- 2638:2638
# v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
# |- unknown unknown 1 13198 0.1 1 0.0
# >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
# >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
# v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
# >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
# >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
# >- __libc_csu_init ls 1 10354 0.1 10 0.0
# |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
# v- main ls 1 8182043 99.6 180254 99.9
#
# Points to note:
# The top level is a command name (comm)
# The next level is a thread (pid:tid)
# Subsequent levels are functions
# 'Count' is the number of calls
# 'Time' is the elapsed time until the function returns
# Percentages are relative to the level above
# 'Branch Count' is the total number of branches for that function and all
# functions that it calls
import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtSql import *
from decimal import *
class TreeItem():
def __init__(self, db, row, parent_item):
self.db = db
self.row = row
self.parent_item = parent_item
self.query_done = False;
self.child_count = 0
self.child_items = []
self.data = ["", "", "", "", "", "", ""]
self.comm_id = 0
self.thread_id = 0
self.call_path_id = 1
self.branch_count = 0
self.time = 0
if not parent_item:
self.setUpRoot()
def setUpRoot(self):
self.query_done = True
query = QSqlQuery(self.db)
ret = query.exec_('SELECT id, comm FROM comms')
if not ret:
raise Exception("Query failed: " + query.lastError().text())
while query.next():
if not query.value(0):
continue
child_item = TreeItem(self.db, self.child_count, self)
self.child_items.append(child_item)
self.child_count += 1
child_item.setUpLevel1(query.value(0), query.value(1))
def setUpLevel1(self, comm_id, comm):
self.query_done = True;
self.comm_id = comm_id
self.data[0] = comm
self.child_items = []
self.child_count = 0
query = QSqlQuery(self.db)
ret = query.exec_('SELECT thread_id, ( SELECT pid FROM threads WHERE id = thread_id ), ( SELECT tid FROM threads WHERE id = thread_id ) FROM comm_threads WHERE comm_id = ' + str(comm_id))
if not ret:
raise Exception("Query failed: " + query.lastError().text())
while query.next():
child_item = TreeItem(self.db, self.child_count, self)
self.child_items.append(child_item)
self.child_count += 1
child_item.setUpLevel2(comm_id, query.value(0), query.value(1), query.value(2))
def setUpLevel2(self, comm_id, thread_id, pid, tid):
self.comm_id = comm_id
self.thread_id = thread_id
self.data[0] = str(pid) + ":" + str(tid)
def getChildItem(self, row):
return self.child_items[row]
def getParentItem(self):
return self.parent_item
def getRow(self):
return self.row
def timePercent(self, b):
if not self.time:
return "0.0"
x = (b * Decimal(100)) / self.time
return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
def branchPercent(self, b):
if not self.branch_count:
return "0.0"
x = (b * Decimal(100)) / self.branch_count
return str(x.quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
def addChild(self, call_path_id, name, dso, count, time, branch_count):
child_item = TreeItem(self.db, self.child_count, self)
child_item.comm_id = self.comm_id
child_item.thread_id = self.thread_id
child_item.call_path_id = call_path_id
child_item.branch_count = branch_count
child_item.time = time
child_item.data[0] = name
if dso == "[kernel.kallsyms]":
dso = "[kernel]"
child_item.data[1] = dso
child_item.data[2] = str(count)
child_item.data[3] = str(time)
child_item.data[4] = self.timePercent(time)
child_item.data[5] = str(branch_count)
child_item.data[6] = self.branchPercent(branch_count)
self.child_items.append(child_item)
self.child_count += 1
def selectCalls(self):
self.query_done = True;
query = QSqlQuery(self.db)
ret = query.exec_('SELECT id, call_path_id, branch_count, call_time, return_time, '
'( SELECT name FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ), '
'( SELECT short_name FROM dsos WHERE id = ( SELECT dso_id FROM symbols WHERE id = ( SELECT symbol_id FROM call_paths WHERE id = call_path_id ) ) ), '
'( SELECT ip FROM call_paths where id = call_path_id ) '
'FROM calls WHERE parent_call_path_id = ' + str(self.call_path_id) + ' AND comm_id = ' + str(self.comm_id) + ' AND thread_id = ' + str(self.thread_id) +
' ORDER BY call_path_id')
if not ret:
raise Exception("Query failed: " + query.lastError().text())
last_call_path_id = 0
name = ""
dso = ""
count = 0
branch_count = 0
total_branch_count = 0
time = 0
total_time = 0
while query.next():
if query.value(1) == last_call_path_id:
count += 1
branch_count += query.value(2)
time += query.value(4) - query.value(3)
else:
if count:
self.addChild(last_call_path_id, name, dso, count, time, branch_count)
last_call_path_id = query.value(1)
name = query.value(5)
dso = query.value(6)
count = 1
total_branch_count += branch_count
total_time += time
branch_count = query.value(2)
time = query.value(4) - query.value(3)
if count:
self.addChild(last_call_path_id, name, dso, count, time, branch_count)
total_branch_count += branch_count
total_time += time
# Top level does not have time or branch count, so fix that here
if total_branch_count > self.branch_count:
self.branch_count = total_branch_count
if self.branch_count:
for child_item in self.child_items:
child_item.data[6] = self.branchPercent(child_item.branch_count)
if total_time > self.time:
self.time = total_time
if self.time:
for child_item in self.child_items:
child_item.data[4] = self.timePercent(child_item.time)
def childCount(self):
if not self.query_done:
self.selectCalls()
return self.child_count
def columnCount(self):
return 7
def columnHeader(self, column):
headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
return headers[column]
def getData(self, column):
return self.data[column]
class TreeModel(QAbstractItemModel):
def __init__(self, db, parent=None):
super(TreeModel, self).__init__(parent)
self.db = db
self.root = TreeItem(db, 0, None)
def columnCount(self, parent):
return self.root.columnCount()
def rowCount(self, parent):
if parent.isValid():
parent_item = parent.internalPointer()
else:
parent_item = self.root
return parent_item.childCount()
def headerData(self, section, orientation, role):
if role == Qt.TextAlignmentRole:
if section > 1:
return Qt.AlignRight
if role != Qt.DisplayRole:
return None
if orientation != Qt.Horizontal:
return None
return self.root.columnHeader(section)
def parent(self, child):
child_item = child.internalPointer()
if child_item is self.root:
return QModelIndex()
parent_item = child_item.getParentItem()
return self.createIndex(parent_item.getRow(), 0, parent_item)
def index(self, row, column, parent):
if parent.isValid():
parent_item = parent.internalPointer()
else:
parent_item = self.root
child_item = parent_item.getChildItem(row)
return self.createIndex(row, column, child_item)
def data(self, index, role):
if role == Qt.TextAlignmentRole:
if index.column() > 1:
return Qt.AlignRight
if role != Qt.DisplayRole:
return None
index_item = index.internalPointer()
return index_item.getData(index.column())
class MainWindow(QMainWindow):
def __init__(self, db, dbname, parent=None):
super(MainWindow, self).__init__(parent)
self.setObjectName("MainWindow")
self.setWindowTitle("Call Graph: " + dbname)
self.move(100, 100)
self.resize(800, 600)
style = self.style()
icon = style.standardIcon(QStyle.SP_MessageBoxInformation)
self.setWindowIcon(icon);
self.model = TreeModel(db)
self.view = QTreeView()
self.view.setModel(self.model)
self.setCentralWidget(self.view)
if __name__ == '__main__':
if (len(sys.argv) < 2):
print >> sys.stderr, "Usage is: call-graph-from-sql.py <database name>"
raise Exception("Too few arguments")
dbname = sys.argv[1]
is_sqlite3 = False
try:
f = open(dbname)
if f.read(15) == "SQLite format 3":
is_sqlite3 = True
f.close()
except:
pass
if is_sqlite3:
db = QSqlDatabase.addDatabase('QSQLITE')
else:
db = QSqlDatabase.addDatabase('QPSQL')
opts = dbname.split()
for opt in opts:
if '=' in opt:
opt = opt.split('=')
if opt[0] == 'hostname':
db.setHostName(opt[1])
elif opt[0] == 'port':
db.setPort(int(opt[1]))
elif opt[0] == 'username':
db.setUserName(opt[1])
elif opt[0] == 'password':
db.setPassword(opt[1])
elif opt[0] == 'dbname':
dbname = opt[1]
else:
dbname = opt
db.setDatabaseName(dbname)
if not db.open():
raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
app = QApplication(sys.argv)
window = MainWindow(db, dbname)
window.show()
err = app.exec_()
db.close()
sys.exit(err)
...@@ -59,7 +59,7 @@ import datetime ...@@ -59,7 +59,7 @@ import datetime
# pt_example=# \q # pt_example=# \q
# #
# An example of using the database is provided by the script # An example of using the database is provided by the script
# call-graph-from-sql.py. Refer to that script for details. # exported-sql-viewer.py. Refer to that script for details.
# #
# Tables: # Tables:
# #
......
...@@ -40,7 +40,7 @@ import datetime ...@@ -40,7 +40,7 @@ import datetime
# sqlite> .quit # sqlite> .quit
# #
# An example of using the database is provided by the script # An example of using the database is provided by the script
# call-graph-from-sql.py. Refer to that script for details. # exported-sql-viewer.py. Refer to that script for details.
# #
# The database structure is practically the same as created by the script # The database structure is practically the same as created by the script
# export-to-postgresql.py. Refer to that script for details. A notable # export-to-postgresql.py. Refer to that script for details. A notable
......
This diff is collapsed.
...@@ -5,6 +5,7 @@ ifeq ($(SRCARCH),$(filter $(SRCARCH),x86)) ...@@ -5,6 +5,7 @@ ifeq ($(SRCARCH),$(filter $(SRCARCH),x86))
libperf-y += ioctl.o libperf-y += ioctl.o
endif endif
libperf-y += kcmp.o libperf-y += kcmp.o
libperf-y += mount_flags.o
libperf-y += pkey_alloc.o libperf-y += pkey_alloc.o
libperf-y += prctl.o libperf-y += prctl.o
libperf-y += sockaddr.o libperf-y += sockaddr.o
......
...@@ -24,6 +24,7 @@ struct strarray { ...@@ -24,6 +24,7 @@ struct strarray {
} }
size_t strarray__scnprintf(struct strarray *sa, char *bf, size_t size, const char *intfmt, int val); size_t strarray__scnprintf(struct strarray *sa, char *bf, size_t size, const char *intfmt, int val);
size_t strarray__scnprintf_flags(struct strarray *sa, char *bf, size_t size, unsigned long flags);
struct trace; struct trace;
struct thread; struct thread;
...@@ -122,6 +123,12 @@ size_t syscall_arg__scnprintf_kcmp_type(char *bf, size_t size, struct syscall_ar ...@@ -122,6 +123,12 @@ size_t syscall_arg__scnprintf_kcmp_type(char *bf, size_t size, struct syscall_ar
size_t syscall_arg__scnprintf_kcmp_idx(char *bf, size_t size, struct syscall_arg *arg); size_t syscall_arg__scnprintf_kcmp_idx(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_KCMP_IDX syscall_arg__scnprintf_kcmp_idx #define SCA_KCMP_IDX syscall_arg__scnprintf_kcmp_idx
unsigned long syscall_arg__mask_val_mount_flags(struct syscall_arg *arg, unsigned long flags);
#define SCAMV_MOUNT_FLAGS syscall_arg__mask_val_mount_flags
size_t syscall_arg__scnprintf_mount_flags(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_MOUNT_FLAGS syscall_arg__scnprintf_mount_flags
size_t syscall_arg__scnprintf_pkey_alloc_access_rights(char *bf, size_t size, struct syscall_arg *arg); size_t syscall_arg__scnprintf_pkey_alloc_access_rights(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_PKEY_ALLOC_ACCESS_RIGHTS syscall_arg__scnprintf_pkey_alloc_access_rights #define SCA_PKEY_ALLOC_ACCESS_RIGHTS syscall_arg__scnprintf_pkey_alloc_access_rights
......
// SPDX-License-Identifier: LGPL-2.1
/* /*
* trace/beauty/cone.c * trace/beauty/cone.c
* *
* Copyright (C) 2017, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> * Copyright (C) 2017, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
*
* Released under the GPL v2. (and only v2, not any later version)
*/ */
#include "trace/beauty/beauty.h" #include "trace/beauty/beauty.h"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment