add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

17
kernel/tools/perf/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
PERF-BUILD-OPTIONS
PERF-CFLAGS
PERF-GUI-VARS
PERF-VERSION-FILE
perf
perf-help
perf-record
perf-report
perf-stat
perf-top
perf*.1
perf*.xml
perf*.html
common-cmds.h
tags
TAGS
cscope*

30
kernel/tools/perf/CREDITS Normal file
View File

@@ -0,0 +1,30 @@
Most of the infrastructure that 'perf' uses here has been reused
from the Git project, as of version:
66996ec: Sync with 1.6.2.4
Here is an (incomplete!) list of main contributors to those files
in util/* and elsewhere:
Alex Riesen
Christian Couder
Dmitry Potapov
Jeff King
Johannes Schindelin
Johannes Sixt
Junio C Hamano
Linus Torvalds
Matthias Kestenholz
Michal Ostrowski
Miklos Vajna
Petr Baudis
Pierre Habouzit
René Scharfe
Samuel Tardieu
Shawn O. Pearce
Steffen Prohaska
Steve Haslam
Thanks guys!
The full history of the files can be found in the upstream Git commits.

View File

@@ -0,0 +1,302 @@
MAN1_TXT= \
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
$(wildcard perf-*.txt)) \
perf.txt
MAN5_TXT=
MAN7_TXT=
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
DOC_HTML=$(MAN_HTML)
ARTICLES =
# with their own formatting rules.
SP_ARTICLES =
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
SP_ARTICLES += $(API_DOCS)
SP_ARTICLES += technical/api-index
DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT))
DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
# Make the path relative to DESTDIR, not prefix
ifndef DESTDIR
prefix?=$(HOME)
endif
bindir?=$(prefix)/bin
htmldir?=$(prefix)/share/doc/perf-doc
pdfdir?=$(prefix)/share/doc/perf-doc
mandir?=$(prefix)/share/man
man1dir=$(mandir)/man1
man5dir=$(mandir)/man5
man7dir=$(mandir)/man7
ASCIIDOC=asciidoc
ASCIIDOC_EXTRA = --unsafe
MANPAGE_XSL = manpage-normal.xsl
XMLTO_EXTRA =
INSTALL?=install
RM ?= rm -f
DOC_REF = origin/man
HTML_REF = origin/html
infodir?=$(prefix)/share/info
MAKEINFO=makeinfo
INSTALL_INFO=install-info
DOCBOOK2X_TEXI=docbook2x-texi
DBLATEX=dblatex
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
-include ../config.mak.autogen
-include ../config.mak
#
# For asciidoc ...
# -7.1.2, no extra settings are needed.
# 8.0-, set ASCIIDOC8.
#
#
# For docbook-xsl ...
# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
# 1.69.0, no extra settings are needed?
# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP?
# 1.71.1, no extra settings are needed?
# 1.72.0, set DOCBOOK_XSL_172.
# 1.73.0-, set ASCIIDOC_NO_ROFF
#
#
# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
# of 'the ".ft C" problem' in your generated manpages, and you
# instead ended up with weird characters around callouts, try
# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
#
ifdef ASCIIDOC8
ASCIIDOC_EXTRA += -a asciidoc7compatible
endif
ifdef DOCBOOK_XSL_172
ASCIIDOC_EXTRA += -a perf-asciidoc-no-roff
MANPAGE_XSL = manpage-1.72.xsl
else
ifdef ASCIIDOC_NO_ROFF
# docbook-xsl after 1.72 needs the regular XSL, but will not
# pass-thru raw roff codes from asciidoc.conf, so turn them off.
ASCIIDOC_EXTRA += -a perf-asciidoc-no-roff
endif
endif
ifdef MAN_BOLD_LITERAL
XMLTO_EXTRA += -m manpage-bold-literal.xsl
endif
ifdef DOCBOOK_SUPPRESS_SP
XMLTO_EXTRA += -m manpage-suppress-sp.xsl
endif
SHELL_PATH ?= $(SHELL)
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
# http://marc.theaimsgroup.com/?l=perf&m=111558757202243&w=2
#
# Until that version is released you may have to apply the patch
# yourself - yes, all 6 characters of it!
#
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
QUIET_SUBDIR1 =
ifneq ($(findstring $(MAKEFLAGS),w),w)
PRINT_DIR = --no-print-directory
else # "make -w"
NO_SUBDIR = :
endif
ifneq ($(findstring $(MAKEFLAGS),s),s)
ifndef V
QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@;
QUIET_XMLTO = @echo ' ' XMLTO $@;
QUIET_DB2TEXI = @echo ' ' DB2TEXI $@;
QUIET_MAKEINFO = @echo ' ' MAKEINFO $@;
QUIET_DBLATEX = @echo ' ' DBLATEX $@;
QUIET_XSLTPROC = @echo ' ' XSLTPROC $@;
QUIET_GEN = @echo ' ' GEN $@;
QUIET_STDERR = 2> /dev/null
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
$(MAKE) $(PRINT_DIR) -C $$subdir
export V
endif
endif
all: html man
html: $(DOC_HTML)
$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf
man: man1 man5 man7
man1: $(DOC_MAN1)
man5: $(DOC_MAN5)
man7: $(DOC_MAN7)
info: perf.info perfman.info
pdf: user-manual.pdf
install: install-man
install-man: man
$(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
# $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir)
# $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir)
$(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
# $(INSTALL) -m 644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
# $(INSTALL) -m 644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
install-info: info
$(INSTALL) -d -m 755 $(DESTDIR)$(infodir)
$(INSTALL) -m 644 perf.info perfman.info $(DESTDIR)$(infodir)
if test -r $(DESTDIR)$(infodir)/dir; then \
$(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perf.info ;\
$(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perfman.info ;\
else \
echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
fi
install-pdf: pdf
$(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir)
$(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir)
install-html: html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
../PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE
$(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) PERF-VERSION-FILE
-include ../PERF-VERSION-FILE
#
# Determine "include::" file references in asciidoc files.
#
doc.dep : $(wildcard *.txt) build-docdep.perl
$(QUIET_GEN)$(RM) $@+ $@ && \
$(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
mv $@+ $@
-include doc.dep
cmds_txt = cmds-ancillaryinterrogators.txt \
cmds-ancillarymanipulators.txt \
cmds-mainporcelain.txt \
cmds-plumbinginterrogators.txt \
cmds-plumbingmanipulators.txt \
cmds-synchingrepositories.txt \
cmds-synchelpers.txt \
cmds-purehelpers.txt \
cmds-foreignscminterface.txt
$(cmds_txt): cmd-list.made
cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
$(QUIET_GEN)$(RM) $@ && \
$(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \
date >$@
clean:
$(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
$(RM) *.texi *.texi+ *.texi++ perf.info perfman.info
$(RM) howto-index.txt howto/*.html doc.dep
$(RM) technical/api-*.html technical/api-index.txt
$(RM) $(cmds_txt) *.made
$(MAN_HTML): %.html : %.txt
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \
mv $@+ $@
%.1 %.5 %.7 : %.xml
$(QUIET_XMLTO)$(RM) $@ && \
xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
%.xml : %.txt
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \
mv $@+ $@
XSLT = docbook.xsl
XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
user-manual.html: user-manual.xml
$(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
perf.info: user-manual.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
user-manual.texi: user-manual.xml
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
$(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
rm $@++ && \
mv $@+ $@
user-manual.pdf: user-manual.xml
$(QUIET_DBLATEX)$(RM) $@+ $@ && \
$(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< && \
mv $@+ $@
perfman.texi: $(MAN_XML) cat-texi.perl
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
--to-stdout $(xml) &&) true) > $@++ && \
$(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
rm $@++ && \
mv $@+ $@
perfman.info: perfman.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
mv $@+ $@
howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
$(QUIET_GEN)$(RM) $@+ $@ && \
'$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
mv $@+ $@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
$(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 $*.txt
WEBDOC_DEST = /pub/software/tools/perf/docs
$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+ && \
mv $@+ $@
install-webdoc : html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
quick-install: quick-install-man
quick-install-man:
'$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
quick-install-html:
'$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
.PHONY: .FORCE-PERF-VERSION-FILE

View File

@@ -0,0 +1,91 @@
## linkperf: macro
#
# Usage: linkperf:command[manpage-section]
#
# Note, {0} is the manpage section, while {target} is the command.
#
# Show PERF link as: <command>(<section>); if section is defined, else just show
# the command.
[macros]
(?su)[\\]?(?P<name>linkperf):(?P<target>\S*?)\[(?P<attrlist>.*?)\]=
[attributes]
asterisk=&#42;
plus=&#43;
caret=&#94;
startsb=&#91;
endsb=&#93;
tilde=&#126;
ifdef::backend-docbook[]
[linkperf-inlinemacro]
{0%{target}}
{0#<citerefentry>}
{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
{0#</citerefentry>}
endif::backend-docbook[]
ifdef::backend-docbook[]
ifndef::perf-asciidoc-no-roff[]
# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
# v1.72 breaks with this because it replaces dots not in roff requests.
[listingblock]
<example><title>{title}</title>
<literallayout>
ifdef::doctype-manpage[]
&#10;.ft C&#10;
endif::doctype-manpage[]
|
ifdef::doctype-manpage[]
&#10;.ft&#10;
endif::doctype-manpage[]
</literallayout>
{title#}</example>
endif::perf-asciidoc-no-roff[]
ifdef::perf-asciidoc-no-roff[]
ifdef::doctype-manpage[]
# The following two small workarounds insert a simple paragraph after screen
[listingblock]
<example><title>{title}</title>
<literallayout>
|
</literallayout><simpara></simpara>
{title#}</example>
[verseblock]
<formalpara{id? id="{id}"}><title>{title}</title><para>
{title%}<literallayout{id? id="{id}"}>
{title#}<literallayout>
|
</literallayout>
{title#}</para></formalpara>
{title%}<simpara></simpara>
endif::doctype-manpage[]
endif::perf-asciidoc-no-roff[]
endif::backend-docbook[]
ifdef::doctype-manpage[]
ifdef::backend-docbook[]
[header]
template::[header-declarations]
<refentry>
<refmeta>
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">perf</refmiscinfo>
<refmiscinfo class="version">{perf_version}</refmiscinfo>
<refmiscinfo class="manual">perf Manual</refmiscinfo>
</refmeta>
<refnamediv>
<refname>{manname}</refname>
<refpurpose>{manpurpose}</refpurpose>
</refnamediv>
endif::backend-docbook[]
endif::doctype-manpage[]
ifdef::backend-xhtml11[]
[linkperf-inlinemacro]
<a href="{target}.html">{target}{0?({0})}</a>
endif::backend-xhtml11[]

View File

@@ -0,0 +1,225 @@
------------------------------
****** perf by examples ******
------------------------------
[ From an e-mail by Ingo Molnar, http://lkml.org/lkml/2009/8/4/346 ]
First, discovery/enumeration of available counters can be done via
'perf list':
titan:~> perf list
[...]
kmem:kmalloc [Tracepoint event]
kmem:kmem_cache_alloc [Tracepoint event]
kmem:kmalloc_node [Tracepoint event]
kmem:kmem_cache_alloc_node [Tracepoint event]
kmem:kfree [Tracepoint event]
kmem:kmem_cache_free [Tracepoint event]
kmem:mm_page_free_direct [Tracepoint event]
kmem:mm_pagevec_free [Tracepoint event]
kmem:mm_page_alloc [Tracepoint event]
kmem:mm_page_alloc_zone_locked [Tracepoint event]
kmem:mm_page_pcpu_drain [Tracepoint event]
kmem:mm_page_alloc_extfrag [Tracepoint event]
Then any (or all) of the above event sources can be activated and
measured. For example the page alloc/free properties of a 'hackbench
run' are:
titan:~> perf stat -e kmem:mm_page_pcpu_drain -e kmem:mm_page_alloc
-e kmem:mm_pagevec_free -e kmem:mm_page_free_direct ./hackbench 10
Time: 0.575
Performance counter stats for './hackbench 10':
13857 kmem:mm_page_pcpu_drain
27576 kmem:mm_page_alloc
6025 kmem:mm_pagevec_free
20934 kmem:mm_page_free_direct
0.613972165 seconds time elapsed
You can observe the statistical properties as well, by using the
'repeat the workload N times' feature of perf stat:
titan:~> perf stat --repeat 5 -e kmem:mm_page_pcpu_drain -e
kmem:mm_page_alloc -e kmem:mm_pagevec_free -e
kmem:mm_page_free_direct ./hackbench 10
Time: 0.627
Time: 0.644
Time: 0.564
Time: 0.559
Time: 0.626
Performance counter stats for './hackbench 10' (5 runs):
12920 kmem:mm_page_pcpu_drain ( +- 3.359% )
25035 kmem:mm_page_alloc ( +- 3.783% )
6104 kmem:mm_pagevec_free ( +- 0.934% )
18376 kmem:mm_page_free_direct ( +- 4.941% )
0.643954516 seconds time elapsed ( +- 2.363% )
Furthermore, these tracepoints can be used to sample the workload as
well. For example the page allocations done by a 'git gc' can be
captured the following way:
titan:~/git> perf record -f -e kmem:mm_page_alloc -c 1 ./git gc
Counting objects: 1148, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (450/450), done.
Writing objects: 100% (1148/1148), done.
Total 1148 (delta 690), reused 1148 (delta 690)
[ perf record: Captured and wrote 0.267 MB perf.data (~11679 samples) ]
To check which functions generated page allocations:
titan:~/git> perf report
# Samples: 10646
#
# Overhead Command Shared Object
# ........ ............... ..........................
#
23.57% git-repack /lib64/libc-2.5.so
21.81% git /lib64/libc-2.5.so
14.59% git ./git
11.79% git-repack ./git
7.12% git /lib64/ld-2.5.so
3.16% git-repack /lib64/libpthread-2.5.so
2.09% git-repack /bin/bash
1.97% rm /lib64/libc-2.5.so
1.39% mv /lib64/ld-2.5.so
1.37% mv /lib64/libc-2.5.so
1.12% git-repack /lib64/ld-2.5.so
0.95% rm /lib64/ld-2.5.so
0.90% git-update-serv /lib64/libc-2.5.so
0.73% git-update-serv /lib64/ld-2.5.so
0.68% perf /lib64/libpthread-2.5.so
0.64% git-repack /usr/lib64/libz.so.1.2.3
Or to see it on a more finegrained level:
titan:~/git> perf report --sort comm,dso,symbol
# Samples: 10646
#
# Overhead Command Shared Object Symbol
# ........ ............... .......................... ......
#
9.35% git-repack ./git [.] insert_obj_hash
9.12% git ./git [.] insert_obj_hash
7.31% git /lib64/libc-2.5.so [.] memcpy
6.34% git-repack /lib64/libc-2.5.so [.] _int_malloc
6.24% git-repack /lib64/libc-2.5.so [.] memcpy
5.82% git-repack /lib64/libc-2.5.so [.] __GI___fork
5.47% git /lib64/libc-2.5.so [.] _int_malloc
2.99% git /lib64/libc-2.5.so [.] memset
Furthermore, call-graph sampling can be done too, of page
allocations - to see precisely what kind of page allocations there
are:
titan:~/git> perf record -f -g -e kmem:mm_page_alloc -c 1 ./git gc
Counting objects: 1148, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (450/450), done.
Writing objects: 100% (1148/1148), done.
Total 1148 (delta 690), reused 1148 (delta 690)
[ perf record: Captured and wrote 0.963 MB perf.data (~42069 samples) ]
titan:~/git> perf report -g
# Samples: 10686
#
# Overhead Command Shared Object
# ........ ............... ..........................
#
23.25% git-repack /lib64/libc-2.5.so
|
|--50.00%-- _int_free
|
|--37.50%-- __GI___fork
| make_child
|
|--12.50%-- ptmalloc_unlock_all2
| make_child
|
--6.25%-- __GI_strcpy
21.61% git /lib64/libc-2.5.so
|
|--30.00%-- __GI_read
| |
| --83.33%-- git_config_from_file
| git_config
| |
[...]
Or you can observe the whole system's page allocations for 10
seconds:
titan:~/git> perf stat -a -e kmem:mm_page_pcpu_drain -e
kmem:mm_page_alloc -e kmem:mm_pagevec_free -e
kmem:mm_page_free_direct sleep 10
Performance counter stats for 'sleep 10':
171585 kmem:mm_page_pcpu_drain
322114 kmem:mm_page_alloc
73623 kmem:mm_pagevec_free
254115 kmem:mm_page_free_direct
10.000591410 seconds time elapsed
Or observe how fluctuating the page allocations are, via statistical
analysis done over ten 1-second intervals:
titan:~/git> perf stat --repeat 10 -a -e kmem:mm_page_pcpu_drain -e
kmem:mm_page_alloc -e kmem:mm_pagevec_free -e
kmem:mm_page_free_direct sleep 1
Performance counter stats for 'sleep 1' (10 runs):
17254 kmem:mm_page_pcpu_drain ( +- 3.709% )
34394 kmem:mm_page_alloc ( +- 4.617% )
7509 kmem:mm_pagevec_free ( +- 4.820% )
25653 kmem:mm_page_free_direct ( +- 3.672% )
1.058135029 seconds time elapsed ( +- 3.089% )
Or you can annotate the recorded 'git gc' run on a per symbol basis
and check which instructions/source-code generated page allocations:
titan:~/git> perf annotate __GI___fork
------------------------------------------------
Percent | Source code & Disassembly of libc-2.5.so
------------------------------------------------
:
:
: Disassembly of section .plt:
: Disassembly of section .text:
:
: 00000031a2e95560 <__fork>:
[...]
0.00 : 31a2e95602: b8 38 00 00 00 mov $0x38,%eax
0.00 : 31a2e95607: 0f 05 syscall
83.42 : 31a2e95609: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax
0.00 : 31a2e9560f: 0f 87 4d 01 00 00 ja 31a2e95762 <__fork+0x202>
0.00 : 31a2e95615: 85 c0 test %eax,%eax
( this shows that 83.42% of __GI___fork's page allocations come from
the 0x38 system call it performs. )
etc. etc. - a lot more is possible. I could list a dozen of
other different usecases straight away - neither of which is
possible via /proc/vmstat.
/proc/vmstat is not in the same league really, in terms of
expressive power of system analysis and performance
analysis.
All that the above results needed were those new tracepoints
in include/tracing/events/kmem.h.
Ingo

View File

@@ -0,0 +1,14 @@
<!-- manpage-1.72.xsl:
special settings for manpages rendered from asciidoc+docbook
handles peculiarities in docbook-xsl 1.72.0 -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:import href="manpage-base.xsl"/>
<!-- these are the special values for the roff control characters
needed for docbook-xsl 1.72.0 -->
<xsl:param name="git.docbook.backslash">&#x2593;</xsl:param>
<xsl:param name="git.docbook.dot" >&#x2302;</xsl:param>
</xsl:stylesheet>

View File

@@ -0,0 +1,35 @@
<!-- manpage-base.xsl:
special formatting for manpages rendered from asciidoc+docbook -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!-- these params silence some output from xmlto -->
<xsl:param name="man.output.quietly" select="1"/>
<xsl:param name="refentry.meta.get.quietly" select="1"/>
<!-- convert asciidoc callouts to man page format;
git.docbook.backslash and git.docbook.dot params
must be supplied by another XSL file or other means -->
<xsl:template match="co">
<xsl:value-of select="concat(
$git.docbook.backslash,'fB(',
substring-after(@id,'-'),')',
$git.docbook.backslash,'fR')"/>
</xsl:template>
<xsl:template match="calloutlist">
<xsl:value-of select="$git.docbook.dot"/>
<xsl:text>sp&#10;</xsl:text>
<xsl:apply-templates/>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="callout">
<xsl:value-of select="concat(
$git.docbook.backslash,'fB',
substring-after(@arearefs,'-'),
'. ',$git.docbook.backslash,'fR')"/>
<xsl:apply-templates/>
<xsl:value-of select="$git.docbook.dot"/>
<xsl:text>br&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,17 @@
<!-- manpage-bold-literal.xsl:
special formatting for manpages rendered from asciidoc+docbook -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!-- render literal text as bold (instead of plain or monospace);
this makes literal text easier to distinguish in manpages
viewed on a tty -->
<xsl:template match="literal">
<xsl:value-of select="$git.docbook.backslash"/>
<xsl:text>fB</xsl:text>
<xsl:apply-templates/>
<xsl:value-of select="$git.docbook.backslash"/>
<xsl:text>fR</xsl:text>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,13 @@
<!-- manpage-normal.xsl:
special settings for manpages rendered from asciidoc+docbook
handles anything we want to keep away from docbook-xsl 1.72.0 -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:import href="manpage-base.xsl"/>
<!-- these are the normal values for the roff control characters -->
<xsl:param name="git.docbook.backslash">\</xsl:param>
<xsl:param name="git.docbook.dot" >.</xsl:param>
</xsl:stylesheet>

View File

@@ -0,0 +1,21 @@
<!-- manpage-suppress-sp.xsl:
special settings for manpages rendered from asciidoc+docbook
handles erroneous, inline .sp in manpage output of some
versions of docbook-xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!-- attempt to work around spurious .sp at the tail of the line
that some versions of docbook stylesheets seem to add -->
<xsl:template match="simpara">
<xsl:variable name="content">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="normalize-space($content)"/>
<xsl:if test="not(ancestor::authorblurb) and
not(ancestor::personblurb)">
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,29 @@
perf-annotate(1)
==============
NAME
----
perf-annotate - Read perf.data (created by perf record) and display annotated code
SYNOPSIS
--------
[verse]
'perf annotate' [-i <file> | --input=file] symbol_name
DESCRIPTION
-----------
This command reads the input file and displays an annotated version of the
code. If the object file has debug symbols then the source code will be
displayed alongside assembly code.
If there is no debug info in the object, then annotated assembly is displayed.
OPTIONS
-------
-i::
--input=::
Input file name. (default: perf.data)
SEE ALSO
--------
linkperf:perf-record[1]

View File

@@ -0,0 +1,38 @@
perf-help(1)
============
NAME
----
perf-help - display help information about perf
SYNOPSIS
--------
'perf help' [-a|--all] [COMMAND]
DESCRIPTION
-----------
With no options and no COMMAND given, the synopsis of the 'perf'
command and a list of the most commonly used perf commands are printed
on the standard output.
If the option '--all' or '-a' is given, then all available commands are
printed on the standard output.
If a perf command is named, a manual page for that command is brought
up. The 'man' program is used by default for this purpose, but this
can be overridden by other options or configuration variables.
Note that `perf --help ...` is identical to `perf help ...` because the
former is internally converted into the latter.
OPTIONS
-------
-a::
--all::
Prints all the available commands on the standard output. This
option supersedes any other option.
PERF
----
Part of the linkperf:perf[1] suite

View File

@@ -0,0 +1,25 @@
perf-list(1)
============
NAME
----
perf-list - List all symbolic event types
SYNOPSIS
--------
[verse]
'perf list'
DESCRIPTION
-----------
This command displays the symbolic event types which can be selected in the
various perf commands with the -e option.
OPTIONS
-------
None
SEE ALSO
--------
linkperf:perf-stat[1], linkperf:perf-top[1],
linkperf:perf-record[1]

View File

@@ -0,0 +1,100 @@
perf-record(1)
==============
NAME
----
perf-record - Run a command and record its profile into perf.data
SYNOPSIS
--------
[verse]
'perf record' [-e <EVENT> | --event=EVENT] [-l] [-a] <command>
'perf record' [-e <EVENT> | --event=EVENT] [-l] [-a] -- <command> [<options>]
DESCRIPTION
-----------
This command runs a command and gathers a performance counter profile
from it, into perf.data - without displaying anything.
This file can then be inspected later on, using 'perf report'.
OPTIONS
-------
<command>...::
Any command you can specify in a shell.
-e::
--event=::
Select the PMU event. Selection can be a symbolic event name
(use 'perf list' to list all events) or a raw PMU
event (eventsel+umask) in the form of rNNN where NNN is a
hexadecimal event descriptor.
-a::
System-wide collection.
-l::
Scale counter values.
-p::
--pid=::
Record events on existing pid.
-r::
--realtime=::
Collect data with this RT SCHED_FIFO priority.
-A::
--append::
Append to the output file to do incremental profiling.
-f::
--force::
Overwrite existing data file.
-c::
--count=::
Event period to sample.
-o::
--output=::
Output file name.
-i::
--inherit::
Child tasks inherit counters.
-F::
--freq=::
Profile at this frequency.
-m::
--mmap-pages=::
Number of mmap data pages.
-g::
--call-graph::
Do call-graph (stack chain/backtrace) recording.
-v::
--verbose::
Be more verbose (show counter open errors, etc).
-s::
--stat::
Per thread counts.
-d::
--data::
Sample addresses.
-n::
--no-samples::
Don't sample.
-R::
--raw-samples::
Collect raw sample records from all opened counters (typically for tracepoint counters).
SEE ALSO
--------
linkperf:perf-stat[1], linkperf:perf-list[1]

View File

@@ -0,0 +1,66 @@
perf-report(1)
==============
NAME
----
perf-report - Read perf.data (created by perf record) and display the profile
SYNOPSIS
--------
[verse]
'perf report' [-i <file> | --input=file]
DESCRIPTION
-----------
This command displays the performance counter profile information recorded
via perf record.
OPTIONS
-------
-i::
--input=::
Input file name. (default: perf.data)
-d::
--dsos=::
Only consider symbols in these dsos. CSV that understands
file://filename entries.
-n
--show-nr-samples
Show the number of samples for each symbol
-T
--threads
Show per-thread event counters
-C::
--comms=::
Only consider symbols in these comms. CSV that understands
file://filename entries.
-S::
--symbols=::
Only consider these symbols. CSV that understands
file://filename entries.
-w::
--field-width=::
Force each column width to the provided list, for large terminal
readability.
-t::
--field-separator=::
Use a special separator character and don't pad with spaces, replacing
all occurances of this separator in symbol names (and other output)
with a '.' character, that thus it's the only non valid separator.
-g [type,min]::
--call-graph::
Display callchains using type and min percent threshold.
type can be either:
- flat: single column, linear exposure of callchains.
- graph: use a graph tree, displaying absolute overhead rates.
- fractal: like graph, but displays relative rates. Each branch of
the tree is considered as a new profiled object. +
Default: fractal,0.5.
SEE ALSO
--------
linkperf:perf-stat[1]

View File

@@ -0,0 +1,41 @@
perf-sched(1)
==============
NAME
----
perf-sched - Tool to trace/measure scheduler properties (latencies)
SYNOPSIS
--------
[verse]
'perf sched' {record|latency|replay|trace}
DESCRIPTION
-----------
There's four variants of perf sched:
'perf sched record <command>' to record the scheduling events
of an arbitrary workload.
'perf sched latency' to report the per task scheduling latencies
and other scheduling properties of the workload.
'perf sched trace' to see a detailed trace of the workload that
was recorded.
'perf sched replay' to simulate the workload that was recorded
via perf sched record. (this is done by starting up mockup threads
that mimic the workload based on the events in the trace. These
threads can then replay the timings (CPU runtime and sleep patterns)
of the workload as it occured when it was recorded - and can repeat
it a number of times, measuring its performance.)
OPTIONS
-------
-D::
--dump-raw-trace=::
Display verbose dump of the sched data.
SEE ALSO
--------
linkperf:perf-record[1]

View File

@@ -0,0 +1,66 @@
perf-stat(1)
============
NAME
----
perf-stat - Run a command and gather performance counter statistics
SYNOPSIS
--------
[verse]
'perf stat' [-e <EVENT> | --event=EVENT] [-S] [-a] <command>
'perf stat' [-e <EVENT> | --event=EVENT] [-S] [-a] -- <command> [<options>]
DESCRIPTION
-----------
This command runs a command and gathers performance counter statistics
from it.
OPTIONS
-------
<command>...::
Any command you can specify in a shell.
-e::
--event=::
Select the PMU event. Selection can be a symbolic event name
(use 'perf list' to list all events) or a raw PMU
event (eventsel+umask) in the form of rNNN where NNN is a
hexadecimal event descriptor.
-i::
--inherit::
child tasks inherit counters
-p::
--pid=<pid>::
stat events on existing pid
-a::
system-wide collection
-c::
scale counter values
EXAMPLES
--------
$ perf stat -- make -j
Performance counter stats for 'make -j':
8117.370256 task clock ticks # 11.281 CPU utilization factor
678 context switches # 0.000 M/sec
133 CPU migrations # 0.000 M/sec
235724 pagefaults # 0.029 M/sec
24821162526 CPU cycles # 3057.784 M/sec
18687303457 instructions # 2302.138 M/sec
172158895 cache references # 21.209 M/sec
27075259 cache misses # 3.335 M/sec
Wall-clock time elapsed: 719.554352 msecs
SEE ALSO
--------
linkperf:perf-top[1], linkperf:perf-list[1]

View File

@@ -0,0 +1,41 @@
perf-timechart(1)
=================
NAME
----
perf-timechart - Tool to visualize total system behavior during a workload
SYNOPSIS
--------
[verse]
'perf timechart' {record}
DESCRIPTION
-----------
There are two variants of perf timechart:
'perf timechart record <command>' to record the system level events
of an arbitrary workload.
'perf timechart' to turn a trace into a Scalable Vector Graphics file,
that can be viewed with popular SVG viewers such as 'Inkscape'.
OPTIONS
-------
-o::
--output=::
Select the output file (default: output.svg)
-i::
--input=::
Select the input file (default: perf.data)
-w::
--width=::
Select the width of the SVG file (default: 1000)
-p::
--power-only::
Only output the CPU power section of the diagram
SEE ALSO
--------
linkperf:perf-record[1]

View File

@@ -0,0 +1,125 @@
perf-top(1)
===========
NAME
----
perf-top - System profiling tool.
SYNOPSIS
--------
[verse]
'perf top' [-e <EVENT> | --event=EVENT] [<options>]
DESCRIPTION
-----------
This command generates and displays a performance counter profile in realtime.
OPTIONS
-------
-a::
--all-cpus::
System-wide collection. (default)
-c <count>::
--count=<count>::
Event period to sample.
-C <cpu>::
--CPU=<cpu>::
CPU to profile.
-d <seconds>::
--delay=<seconds>::
Number of seconds to delay between refreshes.
-e <event>::
--event=<event>::
Select the PMU event. Selection can be a symbolic event name
(use 'perf list' to list all events) or a raw PMU
event (eventsel+umask) in the form of rNNN where NNN is a
hexadecimal event descriptor.
-E <entries>::
--entries=<entries>::
Display this many functions.
-f <count>::
--count-filter=<count>::
Only display functions with more events than this.
-F <freq>::
--freq=<freq>::
Profile at this frequency.
-i::
--inherit::
Child tasks inherit counters, only makes sens with -p option.
-k <path>::
--vmlinux=<path>::
Path to vmlinux. Required for annotation functionality.
-m <pages>::
--mmap-pages=<pages>::
Number of mmapped data pages.
-p <pid>::
--pid=<pid>::
Profile events on existing pid.
-r <priority>::
--realtime=<priority>::
Collect data with this RT SCHED_FIFO priority.
-s <symbol>::
--sym-annotate=<symbol>::
Annotate this symbol. Requires -k option.
-v::
--verbose::
Be more verbose (show counter open errors, etc).
-z::
--zero::
Zero history across display updates.
INTERACTIVE PROMPTING KEYS
--------------------------
[d]::
Display refresh delay.
[e]::
Number of entries to display.
[E]::
Event to display when multiple counters are active.
[f]::
Profile display filter (>= hit count).
[F]::
Annotation display filter (>= % of total).
[s]::
Annotate symbol.
[S]::
Stop annotation, return to full profile display.
[w]::
Toggle between weighted sum and individual count[E]r profile.
[z]::
Toggle event count zeroing across display updates.
[qQ]::
Quit.
Pressing any unmapped key displays a menu, and prompts for input.
SEE ALSO
--------
linkperf:perf-stat[1], linkperf:perf-list[1]

View File

@@ -0,0 +1,25 @@
perf-trace(1)
==============
NAME
----
perf-trace - Read perf.data (created by perf record) and display trace output
SYNOPSIS
--------
[verse]
'perf trace' [-i <file> | --input=file] symbol_name
DESCRIPTION
-----------
This command reads the input file and displays the trace recorded.
OPTIONS
-------
-D::
--dump-raw-trace=::
Display verbose dump of the trace data.
SEE ALSO
--------
linkperf:perf-record[1]

View File

@@ -0,0 +1,24 @@
perf(1)
=======
NAME
----
perf - Performance analysis tools for Linux
SYNOPSIS
--------
[verse]
'perf' [--version] [--help] COMMAND [ARGS]
DESCRIPTION
-----------
Performance counters for Linux are are a new kernel-based subsystem
that provide a framework for all things performance analysis. It
covers hardware level (CPU/PMU, Performance Monitoring Unit) features
and software features (software counters, tracepoints) as well.
SEE ALSO
--------
linkperf:perf-stat[1], linkperf:perf-top[1],
linkperf:perf-record[1], linkperf:perf-report[1],
linkperf:perf-list[1]

1056
kernel/tools/perf/Makefile Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,464 @@
/*
* builtin-help.c
*
* Builtin help command
*/
#include "perf.h"
#include "util/cache.h"
#include "builtin.h"
#include "util/exec_cmd.h"
#include "common-cmds.h"
#include "util/parse-options.h"
#include "util/run-command.h"
#include "util/help.h"
static struct man_viewer_list {
struct man_viewer_list *next;
char name[FLEX_ARRAY];
} *man_viewer_list;
static struct man_viewer_info_list {
struct man_viewer_info_list *next;
const char *info;
char name[FLEX_ARRAY];
} *man_viewer_info_list;
enum help_format {
HELP_FORMAT_MAN,
HELP_FORMAT_INFO,
HELP_FORMAT_WEB,
};
static int show_all = 0;
static enum help_format help_format = HELP_FORMAT_MAN;
static struct option builtin_help_options[] = {
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
HELP_FORMAT_WEB),
OPT_SET_INT('i', "info", &help_format, "show info page",
HELP_FORMAT_INFO),
OPT_END(),
};
static const char * const builtin_help_usage[] = {
"perf help [--all] [--man|--web|--info] [command]",
NULL
};
static enum help_format parse_help_format(const char *format)
{
if (!strcmp(format, "man"))
return HELP_FORMAT_MAN;
if (!strcmp(format, "info"))
return HELP_FORMAT_INFO;
if (!strcmp(format, "web") || !strcmp(format, "html"))
return HELP_FORMAT_WEB;
die("unrecognized help format '%s'", format);
}
static const char *get_man_viewer_info(const char *name)
{
struct man_viewer_info_list *viewer;
for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
{
if (!strcasecmp(name, viewer->name))
return viewer->info;
}
return NULL;
}
static int check_emacsclient_version(void)
{
struct strbuf buffer = STRBUF_INIT;
struct child_process ec_process;
const char *argv_ec[] = { "emacsclient", "--version", NULL };
int version;
/* emacsclient prints its version number on stderr */
memset(&ec_process, 0, sizeof(ec_process));
ec_process.argv = argv_ec;
ec_process.err = -1;
ec_process.stdout_to_stderr = 1;
if (start_command(&ec_process)) {
fprintf(stderr, "Failed to start emacsclient.\n");
return -1;
}
strbuf_read(&buffer, ec_process.err, 20);
close(ec_process.err);
/*
* Don't bother checking return value, because "emacsclient --version"
* seems to always exits with code 1.
*/
finish_command(&ec_process);
if (prefixcmp(buffer.buf, "emacsclient")) {
fprintf(stderr, "Failed to parse emacsclient version.\n");
strbuf_release(&buffer);
return -1;
}
strbuf_remove(&buffer, 0, strlen("emacsclient"));
version = atoi(buffer.buf);
if (version < 22) {
fprintf(stderr,
"emacsclient version '%d' too old (< 22).\n",
version);
strbuf_release(&buffer);
return -1;
}
strbuf_release(&buffer);
return 0;
}
static void exec_woman_emacs(const char* path, const char *page)
{
if (!check_emacsclient_version()) {
/* This works only with emacsclient version >= 22. */
struct strbuf man_page = STRBUF_INIT;
if (!path)
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
execlp(path, "emacsclient", "-e", man_page.buf, NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
}
static void exec_man_konqueror(const char* path, const char *page)
{
const char *display = getenv("DISPLAY");
if (display && *display) {
struct strbuf man_page = STRBUF_INIT;
const char *filename = "kfmclient";
/* It's simpler to launch konqueror using kfmclient. */
if (path) {
const char *file = strrchr(path, '/');
if (file && !strcmp(file + 1, "konqueror")) {
char *new = strdup(path);
char *dest = strrchr(new, '/');
/* strlen("konqueror") == strlen("kfmclient") */
strcpy(dest + 1, "kfmclient");
path = new;
}
if (file)
filename = file;
} else
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
execlp(path, filename, "newTab", man_page.buf, NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
}
static void exec_man_man(const char* path, const char *page)
{
if (!path)
path = "man";
execlp(path, "man", page, NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
static void exec_man_cmd(const char *cmd, const char *page)
{
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
warning("failed to exec '%s': %s", cmd, strerror(errno));
}
static void add_man_viewer(const char *name)
{
struct man_viewer_list **p = &man_viewer_list;
size_t len = strlen(name);
while (*p)
p = &((*p)->next);
*p = calloc(1, (sizeof(**p) + len + 1));
strncpy((*p)->name, name, len);
}
static int supported_man_viewer(const char *name, size_t len)
{
return (!strncasecmp("man", name, len) ||
!strncasecmp("woman", name, len) ||
!strncasecmp("konqueror", name, len));
}
static void do_add_man_viewer_info(const char *name,
size_t len,
const char *value)
{
struct man_viewer_info_list *new = calloc(1, sizeof(*new) + len + 1);
strncpy(new->name, name, len);
new->info = strdup(value);
new->next = man_viewer_info_list;
man_viewer_info_list = new;
}
static int add_man_viewer_path(const char *name,
size_t len,
const char *value)
{
if (supported_man_viewer(name, len))
do_add_man_viewer_info(name, len, value);
else
warning("'%s': path for unsupported man viewer.\n"
"Please consider using 'man.<tool>.cmd' instead.",
name);
return 0;
}
static int add_man_viewer_cmd(const char *name,
size_t len,
const char *value)
{
if (supported_man_viewer(name, len))
warning("'%s': cmd for supported man viewer.\n"
"Please consider using 'man.<tool>.path' instead.",
name);
else
do_add_man_viewer_info(name, len, value);
return 0;
}
static int add_man_viewer_info(const char *var, const char *value)
{
const char *name = var + 4;
const char *subkey = strrchr(name, '.');
if (!subkey)
return error("Config with no key for man viewer: %s", name);
if (!strcmp(subkey, ".path")) {
if (!value)
return config_error_nonbool(var);
return add_man_viewer_path(name, subkey - name, value);
}
if (!strcmp(subkey, ".cmd")) {
if (!value)
return config_error_nonbool(var);
return add_man_viewer_cmd(name, subkey - name, value);
}
warning("'%s': unsupported man viewer sub key.", subkey);
return 0;
}
static int perf_help_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
help_format = parse_help_format(value);
return 0;
}
if (!strcmp(var, "man.viewer")) {
if (!value)
return config_error_nonbool(var);
add_man_viewer(value);
return 0;
}
if (!prefixcmp(var, "man."))
return add_man_viewer_info(var, value);
return perf_default_config(var, value, cb);
}
static struct cmdnames main_cmds, other_cmds;
void list_common_cmds_help(void)
{
unsigned int i, longest = 0;
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
if (longest < strlen(common_cmds[i].name))
longest = strlen(common_cmds[i].name);
}
puts(" The most commonly used perf commands are:");
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
printf(" %s ", common_cmds[i].name);
mput_char(' ', longest - strlen(common_cmds[i].name));
puts(common_cmds[i].help);
}
}
static int is_perf_command(const char *s)
{
return is_in_cmdlist(&main_cmds, s) ||
is_in_cmdlist(&other_cmds, s);
}
static const char *prepend(const char *prefix, const char *cmd)
{
size_t pre_len = strlen(prefix);
size_t cmd_len = strlen(cmd);
char *p = malloc(pre_len + cmd_len + 1);
memcpy(p, prefix, pre_len);
strcpy(p + pre_len, cmd);
return p;
}
static const char *cmd_to_page(const char *perf_cmd)
{
if (!perf_cmd)
return "perf";
else if (!prefixcmp(perf_cmd, "perf"))
return perf_cmd;
else if (is_perf_command(perf_cmd))
return prepend("perf-", perf_cmd);
else
return prepend("perf-", perf_cmd);
}
static void setup_man_path(void)
{
struct strbuf new_path = STRBUF_INIT;
const char *old_path = getenv("MANPATH");
/* We should always put ':' after our path. If there is no
* old_path, the ':' at the end will let 'man' to try
* system-wide paths after ours to find the manual page. If
* there is old_path, we need ':' as delimiter. */
strbuf_addstr(&new_path, system_path(PERF_MAN_PATH));
strbuf_addch(&new_path, ':');
if (old_path)
strbuf_addstr(&new_path, old_path);
setenv("MANPATH", new_path.buf, 1);
strbuf_release(&new_path);
}
static void exec_viewer(const char *name, const char *page)
{
const char *info = get_man_viewer_info(name);
if (!strcasecmp(name, "man"))
exec_man_man(info, page);
else if (!strcasecmp(name, "woman"))
exec_woman_emacs(info, page);
else if (!strcasecmp(name, "konqueror"))
exec_man_konqueror(info, page);
else if (info)
exec_man_cmd(info, page);
else
warning("'%s': unknown man viewer.", name);
}
static void show_man_page(const char *perf_cmd)
{
struct man_viewer_list *viewer;
const char *page = cmd_to_page(perf_cmd);
const char *fallback = getenv("PERF_MAN_VIEWER");
setup_man_path();
for (viewer = man_viewer_list; viewer; viewer = viewer->next)
{
exec_viewer(viewer->name, page); /* will return when unable */
}
if (fallback)
exec_viewer(fallback, page);
exec_viewer("man", page);
die("no man viewer handled the request");
}
static void show_info_page(const char *perf_cmd)
{
const char *page = cmd_to_page(perf_cmd);
setenv("INFOPATH", system_path(PERF_INFO_PATH), 1);
execlp("info", "info", "perfman", page, NULL);
}
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
const char *html_path = system_path(PERF_HTML_PATH);
/* Check that we have a perf documentation directory. */
if (stat(mkpath("%s/perf.html", html_path), &st)
|| !S_ISREG(st.st_mode))
die("'%s': not a documentation directory.", html_path);
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
}
/*
* If open_html is not defined in a platform-specific way (see for
* example compat/mingw.h), we use the script web--browse to display
* HTML.
*/
#ifndef open_html
static void open_html(const char *path)
{
execl_perf_cmd("web--browse", "-c", "help.browser", path, NULL);
}
#endif
static void show_html_page(const char *perf_cmd)
{
const char *page = cmd_to_page(perf_cmd);
struct strbuf page_path; /* it leaks but we exec bellow */
get_html_page_path(&page_path, page);
open_html(page_path.buf);
}
int cmd_help(int argc, const char **argv, const char *prefix __used)
{
const char *alias;
load_command_list("perf-", &main_cmds, &other_cmds);
perf_config(perf_help_config, NULL);
argc = parse_options(argc, argv, builtin_help_options,
builtin_help_usage, 0);
if (show_all) {
printf("\n usage: %s\n\n", perf_usage_string);
list_commands("perf commands", &main_cmds, &other_cmds);
printf(" %s\n\n", perf_more_info_string);
return 0;
}
if (!argv[0]) {
printf("\n usage: %s\n\n", perf_usage_string);
list_common_cmds_help();
printf("\n %s\n\n", perf_more_info_string);
return 0;
}
alias = alias_lookup(argv[0]);
if (alias && !is_perf_command(argv[0])) {
printf("`perf %s' is aliased to `%s'\n", argv[0], alias);
return 0;
}
switch (help_format) {
case HELP_FORMAT_MAN:
show_man_page(argv[0]);
break;
case HELP_FORMAT_INFO:
show_info_page(argv[0]);
break;
case HELP_FORMAT_WEB:
show_html_page(argv[0]);
default:
break;
}
return 0;
}

View File

@@ -0,0 +1,21 @@
/*
* builtin-list.c
*
* Builtin list command: list all event types
*
* Copyright (C) 2009, Thomas Gleixner <tglx@linutronix.de>
* Copyright (C) 2008-2009, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
*/
#include "builtin.h"
#include "perf.h"
#include "util/parse-events.h"
#include "util/cache.h"
int cmd_list(int argc __used, const char **argv __used, const char *prefix __used)
{
setup_pager();
print_events();
return 0;
}

View File

@@ -0,0 +1,742 @@
/*
* builtin-record.c
*
* Builtin record command: Record the profile of a workload
* (or a CPU, or a PID) into the perf.data output file - for
* later analysis via perf report.
*/
#include "builtin.h"
#include "perf.h"
#include "util/util.h"
#include "util/parse-options.h"
#include "util/parse-events.h"
#include "util/string.h"
#include "util/header.h"
#include "util/event.h"
#include "util/debug.h"
#include "util/trace-event.h"
#include <unistd.h>
#include <sched.h>
#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
static int fd[MAX_NR_CPUS][MAX_COUNTERS];
static long default_interval = 100000;
static int nr_cpus = 0;
static unsigned int page_size;
static unsigned int mmap_pages = 128;
static int freq = 0;
static int output;
static const char *output_name = "perf.data";
static int group = 0;
static unsigned int realtime_prio = 0;
static int raw_samples = 0;
static int system_wide = 0;
static int profile_cpu = -1;
static pid_t target_pid = -1;
static pid_t child_pid = -1;
static int inherit = 1;
static int force = 0;
static int append_file = 0;
static int call_graph = 0;
static int inherit_stat = 0;
static int no_samples = 0;
static int sample_address = 0;
static int multiplex = 0;
static int multiplex_fd = -1;
static long samples;
static struct timeval last_read;
static struct timeval this_read;
static u64 bytes_written;
static struct pollfd event_array[MAX_NR_CPUS * MAX_COUNTERS];
static int nr_poll;
static int nr_cpu;
static int file_new = 1;
struct perf_header *header;
struct mmap_data {
int counter;
void *base;
unsigned int mask;
unsigned int prev;
};
static struct mmap_data mmap_array[MAX_NR_CPUS][MAX_COUNTERS];
static unsigned long mmap_read_head(struct mmap_data *md)
{
struct perf_event_mmap_page *pc = md->base;
long head;
head = pc->data_head;
rmb();
return head;
}
static void mmap_write_tail(struct mmap_data *md, unsigned long tail)
{
struct perf_event_mmap_page *pc = md->base;
/*
* ensure all reads are done before we write the tail out.
*/
/* mb(); */
pc->data_tail = tail;
}
static void write_output(void *buf, size_t size)
{
while (size) {
int ret = write(output, buf, size);
if (ret < 0)
die("failed to write");
size -= ret;
buf += ret;
bytes_written += ret;
}
}
static void mmap_read(struct mmap_data *md)
{
unsigned int head = mmap_read_head(md);
unsigned int old = md->prev;
unsigned char *data = md->base + page_size;
unsigned long size;
void *buf;
int diff;
gettimeofday(&this_read, NULL);
/*
* If we're further behind than half the buffer, there's a chance
* the writer will bite our tail and mess up the samples under us.
*
* If we somehow ended up ahead of the head, we got messed up.
*
* In either case, truncate and restart at head.
*/
diff = head - old;
if (diff < 0) {
struct timeval iv;
unsigned long msecs;
timersub(&this_read, &last_read, &iv);
msecs = iv.tv_sec*1000 + iv.tv_usec/1000;
fprintf(stderr, "WARNING: failed to keep up with mmap data."
" Last read %lu msecs ago.\n", msecs);
/*
* head points to a known good entry, start there.
*/
old = head;
}
last_read = this_read;
if (old != head)
samples++;
size = head - old;
if ((old & md->mask) + size != (head & md->mask)) {
buf = &data[old & md->mask];
size = md->mask + 1 - (old & md->mask);
old += size;
write_output(buf, size);
}
buf = &data[old & md->mask];
size = head - old;
old += size;
write_output(buf, size);
md->prev = old;
mmap_write_tail(md, old);
}
static volatile int done = 0;
static volatile int signr = -1;
static void sig_handler(int sig)
{
done = 1;
signr = sig;
}
static void sig_atexit(void)
{
if (child_pid != -1)
kill(child_pid, SIGTERM);
if (signr == -1)
return;
signal(signr, SIG_DFL);
kill(getpid(), signr);
}
static pid_t pid_synthesize_comm_event(pid_t pid, int full)
{
struct comm_event comm_ev;
char filename[PATH_MAX];
char bf[BUFSIZ];
FILE *fp;
size_t size = 0;
DIR *tasks;
struct dirent dirent, *next;
pid_t tgid = 0;
snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
fp = fopen(filename, "r");
if (fp == NULL) {
/*
* We raced with a task exiting - just return:
*/
if (verbose)
fprintf(stderr, "couldn't open %s\n", filename);
return 0;
}
memset(&comm_ev, 0, sizeof(comm_ev));
while (!comm_ev.comm[0] || !comm_ev.pid) {
if (fgets(bf, sizeof(bf), fp) == NULL)
goto out_failure;
if (memcmp(bf, "Name:", 5) == 0) {
char *name = bf + 5;
while (*name && isspace(*name))
++name;
size = strlen(name) - 1;
memcpy(comm_ev.comm, name, size++);
} else if (memcmp(bf, "Tgid:", 5) == 0) {
char *tgids = bf + 5;
while (*tgids && isspace(*tgids))
++tgids;
tgid = comm_ev.pid = atoi(tgids);
}
}
comm_ev.header.type = PERF_RECORD_COMM;
size = ALIGN(size, sizeof(u64));
comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size);
if (!full) {
comm_ev.tid = pid;
write_output(&comm_ev, comm_ev.header.size);
goto out_fclose;
}
snprintf(filename, sizeof(filename), "/proc/%d/task", pid);
tasks = opendir(filename);
while (!readdir_r(tasks, &dirent, &next) && next) {
char *end;
pid = strtol(dirent.d_name, &end, 10);
if (*end)
continue;
comm_ev.tid = pid;
write_output(&comm_ev, comm_ev.header.size);
}
closedir(tasks);
out_fclose:
fclose(fp);
return tgid;
out_failure:
fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n",
filename);
exit(EXIT_FAILURE);
}
static void pid_synthesize_mmap_samples(pid_t pid, pid_t tgid)
{
char filename[PATH_MAX];
FILE *fp;
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
fp = fopen(filename, "r");
if (fp == NULL) {
/*
* We raced with a task exiting - just return:
*/
if (verbose)
fprintf(stderr, "couldn't open %s\n", filename);
return;
}
while (1) {
char bf[BUFSIZ], *pbf = bf;
struct mmap_event mmap_ev = {
.header = { .type = PERF_RECORD_MMAP },
};
int n;
size_t size;
if (fgets(bf, sizeof(bf), fp) == NULL)
break;
/* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
n = hex2u64(pbf, &mmap_ev.start);
if (n < 0)
continue;
pbf += n + 1;
n = hex2u64(pbf, &mmap_ev.len);
if (n < 0)
continue;
pbf += n + 3;
if (*pbf == 'x') { /* vm_exec */
char *execname = strchr(bf, '/');
/* Catch VDSO */
if (execname == NULL)
execname = strstr(bf, "[vdso]");
if (execname == NULL)
continue;
size = strlen(execname);
execname[size - 1] = '\0'; /* Remove \n */
memcpy(mmap_ev.filename, execname, size);
size = ALIGN(size, sizeof(u64));
mmap_ev.len -= mmap_ev.start;
mmap_ev.header.size = (sizeof(mmap_ev) -
(sizeof(mmap_ev.filename) - size));
mmap_ev.pid = tgid;
mmap_ev.tid = pid;
write_output(&mmap_ev, mmap_ev.header.size);
}
}
fclose(fp);
}
static void synthesize_all(void)
{
DIR *proc;
struct dirent dirent, *next;
proc = opendir("/proc");
while (!readdir_r(proc, &dirent, &next) && next) {
char *end;
pid_t pid, tgid;
pid = strtol(dirent.d_name, &end, 10);
if (*end) /* only interested in proper numerical dirents */
continue;
tgid = pid_synthesize_comm_event(pid, 1);
pid_synthesize_mmap_samples(pid, tgid);
}
closedir(proc);
}
static int group_fd;
static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int nr)
{
struct perf_header_attr *h_attr;
if (nr < header->attrs) {
h_attr = header->attr[nr];
} else {
h_attr = perf_header_attr__new(a);
perf_header__add_attr(header, h_attr);
}
return h_attr;
}
static void create_counter(int counter, int cpu, pid_t pid)
{
struct perf_event_attr *attr = attrs + counter;
struct perf_header_attr *h_attr;
int track = !counter; /* only the first counter needs these */
struct {
u64 count;
u64 time_enabled;
u64 time_running;
u64 id;
} read_data;
attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
PERF_FORMAT_TOTAL_TIME_RUNNING |
PERF_FORMAT_ID;
attr->sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID;
if (freq) {
attr->sample_type |= PERF_SAMPLE_PERIOD;
attr->freq = 1;
attr->sample_freq = freq;
}
if (no_samples)
attr->sample_freq = 0;
if (inherit_stat)
attr->inherit_stat = 1;
if (sample_address)
attr->sample_type |= PERF_SAMPLE_ADDR;
if (call_graph)
attr->sample_type |= PERF_SAMPLE_CALLCHAIN;
if (raw_samples) {
attr->sample_type |= PERF_SAMPLE_TIME;
attr->sample_type |= PERF_SAMPLE_RAW;
attr->sample_type |= PERF_SAMPLE_CPU;
}
attr->mmap = track;
attr->comm = track;
attr->inherit = (cpu < 0) && inherit;
attr->disabled = 1;
try_again:
fd[nr_cpu][counter] = sys_perf_event_open(attr, pid, cpu, group_fd, 0);
if (fd[nr_cpu][counter] < 0) {
int err = errno;
if (err == EPERM || err == EACCES)
die("Permission error - are you root?\n");
else if (err == ENODEV && profile_cpu != -1)
die("No such device - did you specify an out-of-range profile CPU?\n");
/*
* If it's cycles then fall back to hrtimer
* based cpu-clock-tick sw counter, which
* is always available even if no PMU support:
*/
if (attr->type == PERF_TYPE_HARDWARE
&& attr->config == PERF_COUNT_HW_CPU_CYCLES) {
if (verbose)
warning(" ... trying to fall back to cpu-clock-ticks\n");
attr->type = PERF_TYPE_SOFTWARE;
attr->config = PERF_COUNT_SW_CPU_CLOCK;
goto try_again;
}
printf("\n");
error("perfcounter syscall returned with %d (%s)\n",
fd[nr_cpu][counter], strerror(err));
die("No CONFIG_PERF_EVENTS=y kernel support configured?\n");
exit(-1);
}
h_attr = get_header_attr(attr, counter);
if (!file_new) {
if (memcmp(&h_attr->attr, attr, sizeof(*attr))) {
fprintf(stderr, "incompatible append\n");
exit(-1);
}
}
if (read(fd[nr_cpu][counter], &read_data, sizeof(read_data)) == -1) {
perror("Unable to read perf file descriptor\n");
exit(-1);
}
perf_header_attr__add_id(h_attr, read_data.id);
assert(fd[nr_cpu][counter] >= 0);
fcntl(fd[nr_cpu][counter], F_SETFL, O_NONBLOCK);
/*
* First counter acts as the group leader:
*/
if (group && group_fd == -1)
group_fd = fd[nr_cpu][counter];
if (multiplex && multiplex_fd == -1)
multiplex_fd = fd[nr_cpu][counter];
if (multiplex && fd[nr_cpu][counter] != multiplex_fd) {
int ret;
ret = ioctl(fd[nr_cpu][counter], PERF_EVENT_IOC_SET_OUTPUT, multiplex_fd);
assert(ret != -1);
} else {
event_array[nr_poll].fd = fd[nr_cpu][counter];
event_array[nr_poll].events = POLLIN;
nr_poll++;
mmap_array[nr_cpu][counter].counter = counter;
mmap_array[nr_cpu][counter].prev = 0;
mmap_array[nr_cpu][counter].mask = mmap_pages*page_size - 1;
mmap_array[nr_cpu][counter].base = mmap(NULL, (mmap_pages+1)*page_size,
PROT_READ|PROT_WRITE, MAP_SHARED, fd[nr_cpu][counter], 0);
if (mmap_array[nr_cpu][counter].base == MAP_FAILED) {
error("failed to mmap with %d (%s)\n", errno, strerror(errno));
exit(-1);
}
}
ioctl(fd[nr_cpu][counter], PERF_EVENT_IOC_ENABLE);
}
static void open_counters(int cpu, pid_t pid)
{
int counter;
group_fd = -1;
for (counter = 0; counter < nr_counters; counter++)
create_counter(counter, cpu, pid);
nr_cpu++;
}
static void atexit_header(void)
{
header->data_size += bytes_written;
perf_header__write(header, output);
}
static int __cmd_record(int argc, const char **argv)
{
int i, counter;
struct stat st;
pid_t pid = 0;
int flags;
int ret;
unsigned long waking = 0;
page_size = sysconf(_SC_PAGE_SIZE);
nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
assert(nr_cpus <= MAX_NR_CPUS);
assert(nr_cpus >= 0);
atexit(sig_atexit);
signal(SIGCHLD, sig_handler);
signal(SIGINT, sig_handler);
if (!stat(output_name, &st) && st.st_size) {
if (!force && !append_file) {
fprintf(stderr, "Error, output file %s exists, use -A to append or -f to overwrite.\n",
output_name);
exit(-1);
}
} else {
append_file = 0;
}
flags = O_CREAT|O_RDWR;
if (append_file)
file_new = 0;
else
flags |= O_TRUNC;
output = open(output_name, flags, S_IRUSR|S_IWUSR);
if (output < 0) {
perror("failed to create output file");
exit(-1);
}
if (!file_new)
header = perf_header__read(output);
else
header = perf_header__new();
if (raw_samples) {
read_tracing_data(attrs, nr_counters);
} else {
for (i = 0; i < nr_counters; i++) {
if (attrs[i].sample_type & PERF_SAMPLE_RAW) {
read_tracing_data(attrs, nr_counters);
break;
}
}
}
atexit(atexit_header);
if (!system_wide) {
pid = target_pid;
if (pid == -1)
pid = getpid();
open_counters(profile_cpu, pid);
} else {
if (profile_cpu != -1) {
open_counters(profile_cpu, target_pid);
} else {
for (i = 0; i < nr_cpus; i++)
open_counters(i, target_pid);
}
}
if (file_new)
perf_header__write(header, output);
if (!system_wide) {
pid_t tgid = pid_synthesize_comm_event(pid, 0);
pid_synthesize_mmap_samples(pid, tgid);
} else
synthesize_all();
if (target_pid == -1 && argc) {
pid = fork();
if (pid < 0)
perror("failed to fork");
if (!pid) {
if (execvp(argv[0], (char **)argv)) {
perror(argv[0]);
exit(-1);
}
}
child_pid = pid;
}
if (realtime_prio) {
struct sched_param param;
param.sched_priority = realtime_prio;
if (sched_setscheduler(0, SCHED_FIFO, &param)) {
printf("Could not set realtime priority.\n");
exit(-1);
}
}
for (;;) {
int hits = samples;
for (i = 0; i < nr_cpu; i++) {
for (counter = 0; counter < nr_counters; counter++) {
if (mmap_array[i][counter].base)
mmap_read(&mmap_array[i][counter]);
}
}
if (hits == samples) {
if (done)
break;
ret = poll(event_array, nr_poll, -1);
waking++;
}
if (done) {
for (i = 0; i < nr_cpu; i++) {
for (counter = 0; counter < nr_counters; counter++)
ioctl(fd[i][counter], PERF_EVENT_IOC_DISABLE);
}
}
}
fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking);
/*
* Approximate RIP event size: 24 bytes.
*/
fprintf(stderr,
"[ perf record: Captured and wrote %.3f MB %s (~%lld samples) ]\n",
(double)bytes_written / 1024.0 / 1024.0,
output_name,
bytes_written / 24);
return 0;
}
static const char * const record_usage[] = {
"perf record [<options>] [<command>]",
"perf record [<options>] -- <command> [<options>]",
NULL
};
static const struct option options[] = {
OPT_CALLBACK('e', "event", NULL, "event",
"event selector. use 'perf list' to list available events",
parse_events),
OPT_INTEGER('p', "pid", &target_pid,
"record events on existing pid"),
OPT_INTEGER('r', "realtime", &realtime_prio,
"collect data with this RT SCHED_FIFO priority"),
OPT_BOOLEAN('R', "raw-samples", &raw_samples,
"collect raw sample records from all opened counters"),
OPT_BOOLEAN('a', "all-cpus", &system_wide,
"system-wide collection from all CPUs"),
OPT_BOOLEAN('A', "append", &append_file,
"append to the output file to do incremental profiling"),
OPT_INTEGER('C', "profile_cpu", &profile_cpu,
"CPU to profile on"),
OPT_BOOLEAN('f', "force", &force,
"overwrite existing data file"),
OPT_LONG('c', "count", &default_interval,
"event period to sample"),
OPT_STRING('o', "output", &output_name, "file",
"output file name"),
OPT_BOOLEAN('i', "inherit", &inherit,
"child tasks inherit counters"),
OPT_INTEGER('F', "freq", &freq,
"profile at this frequency"),
OPT_INTEGER('m', "mmap-pages", &mmap_pages,
"number of mmap data pages"),
OPT_BOOLEAN('g', "call-graph", &call_graph,
"do call-graph (stack chain/backtrace) recording"),
OPT_BOOLEAN('v', "verbose", &verbose,
"be more verbose (show counter open errors, etc)"),
OPT_BOOLEAN('s', "stat", &inherit_stat,
"per thread counts"),
OPT_BOOLEAN('d', "data", &sample_address,
"Sample addresses"),
OPT_BOOLEAN('n', "no-samples", &no_samples,
"don't sample"),
OPT_BOOLEAN('M', "multiplex", &multiplex,
"multiplex counter output in a single channel"),
OPT_END()
};
int cmd_record(int argc, const char **argv, const char *prefix __used)
{
int counter;
argc = parse_options(argc, argv, options, record_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (!argc && target_pid == -1 && !system_wide)
usage_with_options(record_usage, options);
if (!nr_counters) {
nr_counters = 1;
attrs[0].type = PERF_TYPE_HARDWARE;
attrs[0].config = PERF_COUNT_HW_CPU_CYCLES;
}
for (counter = 0; counter < nr_counters; counter++) {
if (attrs[counter].sample_period)
continue;
attrs[counter].sample_period = default_interval;
}
return __cmd_record(argc, argv);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,517 @@
/*
* builtin-stat.c
*
* Builtin stat command: Give a precise performance counters summary
* overview about any workload, CPU or specific PID.
*
* Sample output:
$ perf stat ~/hackbench 10
Time: 0.104
Performance counter stats for '/home/mingo/hackbench':
1255.538611 task clock ticks # 10.143 CPU utilization factor
54011 context switches # 0.043 M/sec
385 CPU migrations # 0.000 M/sec
17755 pagefaults # 0.014 M/sec
3808323185 CPU cycles # 3033.219 M/sec
1575111190 instructions # 1254.530 M/sec
17367895 cache references # 13.833 M/sec
7674421 cache misses # 6.112 M/sec
Wall-clock time elapsed: 123.786620 msecs
*
* Copyright (C) 2008, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
*
* Improvements and fixes by:
*
* Arjan van de Ven <arjan@linux.intel.com>
* Yanmin Zhang <yanmin.zhang@intel.com>
* Wu Fengguang <fengguang.wu@intel.com>
* Mike Galbraith <efault@gmx.de>
* Paul Mackerras <paulus@samba.org>
* Jaswinder Singh Rajput <jaswinder@kernel.org>
*
* Released under the GPL v2. (and only v2, not any later version)
*/
#include "perf.h"
#include "builtin.h"
#include "util/util.h"
#include "util/parse-options.h"
#include "util/parse-events.h"
#include "util/event.h"
#include "util/debug.h"
#include <sys/prctl.h>
#include <math.h>
static struct perf_event_attr default_attrs[] = {
{ .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK },
{ .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CONTEXT_SWITCHES},
{ .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CPU_MIGRATIONS },
{ .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_PAGE_FAULTS },
{ .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES },
{ .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS },
{ .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_REFERENCES},
{ .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_MISSES },
};
static int system_wide = 0;
static unsigned int nr_cpus = 0;
static int run_idx = 0;
static int run_count = 1;
static int inherit = 1;
static int scale = 1;
static pid_t target_pid = -1;
static pid_t child_pid = -1;
static int null_run = 0;
static int fd[MAX_NR_CPUS][MAX_COUNTERS];
static int event_scaled[MAX_COUNTERS];
struct stats
{
double n, mean, M2;
};
static void update_stats(struct stats *stats, u64 val)
{
double delta;
stats->n++;
delta = val - stats->mean;
stats->mean += delta / stats->n;
stats->M2 += delta*(val - stats->mean);
}
static double avg_stats(struct stats *stats)
{
return stats->mean;
}
/*
* http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
*
* (\Sum n_i^2) - ((\Sum n_i)^2)/n
* s^2 = -------------------------------
* n - 1
*
* http://en.wikipedia.org/wiki/Stddev
*
* The std dev of the mean is related to the std dev by:
*
* s
* s_mean = -------
* sqrt(n)
*
*/
static double stddev_stats(struct stats *stats)
{
double variance = stats->M2 / (stats->n - 1);
double variance_mean = variance / stats->n;
return sqrt(variance_mean);
}
struct stats event_res_stats[MAX_COUNTERS][3];
struct stats runtime_nsecs_stats;
struct stats walltime_nsecs_stats;
struct stats runtime_cycles_stats;
#define MATCH_EVENT(t, c, counter) \
(attrs[counter].type == PERF_TYPE_##t && \
attrs[counter].config == PERF_COUNT_##c)
#define ERR_PERF_OPEN \
"Error: counter %d, sys_perf_event_open() syscall returned with %d (%s)\n"
static void create_perf_stat_counter(int counter, int pid)
{
struct perf_event_attr *attr = attrs + counter;
if (scale)
attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
PERF_FORMAT_TOTAL_TIME_RUNNING;
if (system_wide) {
unsigned int cpu;
for (cpu = 0; cpu < nr_cpus; cpu++) {
fd[cpu][counter] = sys_perf_event_open(attr, -1, cpu, -1, 0);
if (fd[cpu][counter] < 0 && verbose)
fprintf(stderr, ERR_PERF_OPEN, counter,
fd[cpu][counter], strerror(errno));
}
} else {
attr->inherit = inherit;
attr->disabled = 1;
attr->enable_on_exec = 1;
fd[0][counter] = sys_perf_event_open(attr, pid, -1, -1, 0);
if (fd[0][counter] < 0 && verbose)
fprintf(stderr, ERR_PERF_OPEN, counter,
fd[0][counter], strerror(errno));
}
}
/*
* Does the counter have nsecs as a unit?
*/
static inline int nsec_counter(int counter)
{
if (MATCH_EVENT(SOFTWARE, SW_CPU_CLOCK, counter) ||
MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter))
return 1;
return 0;
}
/*
* Read out the results of a single counter:
*/
static void read_counter(int counter)
{
u64 count[3], single_count[3];
unsigned int cpu;
size_t res, nv;
int scaled;
int i;
count[0] = count[1] = count[2] = 0;
nv = scale ? 3 : 1;
for (cpu = 0; cpu < nr_cpus; cpu++) {
if (fd[cpu][counter] < 0)
continue;
res = read(fd[cpu][counter], single_count, nv * sizeof(u64));
assert(res == nv * sizeof(u64));
close(fd[cpu][counter]);
fd[cpu][counter] = -1;
count[0] += single_count[0];
if (scale) {
count[1] += single_count[1];
count[2] += single_count[2];
}
}
scaled = 0;
if (scale) {
if (count[2] == 0) {
event_scaled[counter] = -1;
count[0] = 0;
return;
}
if (count[2] < count[1]) {
event_scaled[counter] = 1;
count[0] = (unsigned long long)
((double)count[0] * count[1] / count[2] + 0.5);
}
}
for (i = 0; i < 3; i++)
update_stats(&event_res_stats[counter][i], count[i]);
if (verbose) {
fprintf(stderr, "%s: %Ld %Ld %Ld\n", event_name(counter),
count[0], count[1], count[2]);
}
/*
* Save the full runtime - to allow normalization during printout:
*/
if (MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter))
update_stats(&runtime_nsecs_stats, count[0]);
if (MATCH_EVENT(HARDWARE, HW_CPU_CYCLES, counter))
update_stats(&runtime_cycles_stats, count[0]);
}
static int run_perf_stat(int argc __used, const char **argv)
{
unsigned long long t0, t1;
int status = 0;
int counter;
int pid;
int child_ready_pipe[2], go_pipe[2];
char buf;
if (!system_wide)
nr_cpus = 1;
if (pipe(child_ready_pipe) < 0 || pipe(go_pipe) < 0) {
perror("failed to create pipes");
exit(1);
}
if ((pid = fork()) < 0)
perror("failed to fork");
if (!pid) {
close(child_ready_pipe[0]);
close(go_pipe[1]);
fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC);
/*
* Do a dummy execvp to get the PLT entry resolved,
* so we avoid the resolver overhead on the real
* execvp call.
*/
execvp("", (char **)argv);
/*
* Tell the parent we're ready to go
*/
close(child_ready_pipe[1]);
/*
* Wait until the parent tells us to go.
*/
if (read(go_pipe[0], &buf, 1) == -1)
perror("unable to read pipe");
execvp(argv[0], (char **)argv);
perror(argv[0]);
exit(-1);
}
child_pid = pid;
/*
* Wait for the child to be ready to exec.
*/
close(child_ready_pipe[1]);
close(go_pipe[0]);
if (read(child_ready_pipe[0], &buf, 1) == -1)
perror("unable to read pipe");
close(child_ready_pipe[0]);
for (counter = 0; counter < nr_counters; counter++)
create_perf_stat_counter(counter, pid);
/*
* Enable counters and exec the command:
*/
t0 = rdclock();
close(go_pipe[1]);
wait(&status);
t1 = rdclock();
update_stats(&walltime_nsecs_stats, t1 - t0);
for (counter = 0; counter < nr_counters; counter++)
read_counter(counter);
return WEXITSTATUS(status);
}
static void print_noise(int counter, double avg)
{
if (run_count == 1)
return;
fprintf(stderr, " ( +- %7.3f%% )",
100 * stddev_stats(&event_res_stats[counter][0]) / avg);
}
static void nsec_printout(int counter, double avg)
{
double msecs = avg / 1e6;
fprintf(stderr, " %14.6f %-24s", msecs, event_name(counter));
if (MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter)) {
fprintf(stderr, " # %10.3f CPUs ",
avg / avg_stats(&walltime_nsecs_stats));
}
}
static void abs_printout(int counter, double avg)
{
double total, ratio = 0.0;
fprintf(stderr, " %14.0f %-24s", avg, event_name(counter));
if (MATCH_EVENT(HARDWARE, HW_INSTRUCTIONS, counter)) {
total = avg_stats(&runtime_cycles_stats);
if (total)
ratio = avg / total;
fprintf(stderr, " # %10.3f IPC ", ratio);
} else {
total = avg_stats(&runtime_nsecs_stats);
if (total)
ratio = 1000.0 * avg / total;
fprintf(stderr, " # %10.3f M/sec", ratio);
}
}
/*
* Print out the results of a single counter:
*/
static void print_counter(int counter)
{
double avg = avg_stats(&event_res_stats[counter][0]);
int scaled = event_scaled[counter];
if (scaled == -1) {
fprintf(stderr, " %14s %-24s\n",
"<not counted>", event_name(counter));
return;
}
if (nsec_counter(counter))
nsec_printout(counter, avg);
else
abs_printout(counter, avg);
print_noise(counter, avg);
if (scaled) {
double avg_enabled, avg_running;
avg_enabled = avg_stats(&event_res_stats[counter][1]);
avg_running = avg_stats(&event_res_stats[counter][2]);
fprintf(stderr, " (scaled from %.2f%%)",
100 * avg_running / avg_enabled);
}
fprintf(stderr, "\n");
}
static void print_stat(int argc, const char **argv)
{
int i, counter;
fflush(stdout);
fprintf(stderr, "\n");
fprintf(stderr, " Performance counter stats for \'%s", argv[0]);
for (i = 1; i < argc; i++)
fprintf(stderr, " %s", argv[i]);
fprintf(stderr, "\'");
if (run_count > 1)
fprintf(stderr, " (%d runs)", run_count);
fprintf(stderr, ":\n\n");
for (counter = 0; counter < nr_counters; counter++)
print_counter(counter);
fprintf(stderr, "\n");
fprintf(stderr, " %14.9f seconds time elapsed",
avg_stats(&walltime_nsecs_stats)/1e9);
if (run_count > 1) {
fprintf(stderr, " ( +- %7.3f%% )",
100*stddev_stats(&walltime_nsecs_stats) /
avg_stats(&walltime_nsecs_stats));
}
fprintf(stderr, "\n\n");
}
static volatile int signr = -1;
static void skip_signal(int signo)
{
signr = signo;
}
static void sig_atexit(void)
{
if (child_pid != -1)
kill(child_pid, SIGTERM);
if (signr == -1)
return;
signal(signr, SIG_DFL);
kill(getpid(), signr);
}
static const char * const stat_usage[] = {
"perf stat [<options>] <command>",
NULL
};
static const struct option options[] = {
OPT_CALLBACK('e', "event", NULL, "event",
"event selector. use 'perf list' to list available events",
parse_events),
OPT_BOOLEAN('i', "inherit", &inherit,
"child tasks inherit counters"),
OPT_INTEGER('p', "pid", &target_pid,
"stat events on existing pid"),
OPT_BOOLEAN('a', "all-cpus", &system_wide,
"system-wide collection from all CPUs"),
OPT_BOOLEAN('c', "scale", &scale,
"scale/normalize counters"),
OPT_BOOLEAN('v', "verbose", &verbose,
"be more verbose (show counter open errors, etc)"),
OPT_INTEGER('r', "repeat", &run_count,
"repeat command and print average + stddev (max: 100)"),
OPT_BOOLEAN('n', "null", &null_run,
"null run - dont start any counters"),
OPT_END()
};
int cmd_stat(int argc, const char **argv, const char *prefix __used)
{
int status;
argc = parse_options(argc, argv, options, stat_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (!argc)
usage_with_options(stat_usage, options);
if (run_count <= 0)
usage_with_options(stat_usage, options);
/* Set attrs and nr_counters if no event is selected and !null_run */
if (!null_run && !nr_counters) {
memcpy(attrs, default_attrs, sizeof(default_attrs));
nr_counters = ARRAY_SIZE(default_attrs);
}
nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
assert(nr_cpus <= MAX_NR_CPUS);
assert((int)nr_cpus >= 0);
/*
* We dont want to block the signals - that would cause
* child tasks to inherit that and Ctrl-C would not work.
* What we want is for Ctrl-C to work in the exec()-ed
* task, but being ignored by perf stat itself:
*/
atexit(sig_atexit);
signal(SIGINT, skip_signal);
signal(SIGALRM, skip_signal);
signal(SIGABRT, skip_signal);
status = 0;
for (run_idx = 0; run_idx < run_count; run_idx++) {
if (run_count != 1 && verbose)
fprintf(stderr, "[ perf stat: executing run #%d ... ]\n", run_idx + 1);
status = run_perf_stat(argc, argv);
}
print_stat(argc, argv);
return status;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,291 @@
#include "builtin.h"
#include "util/util.h"
#include "util/cache.h"
#include "util/symbol.h"
#include "util/thread.h"
#include "util/header.h"
#include "util/parse-options.h"
#include "perf.h"
#include "util/debug.h"
#include "util/trace-event.h"
static char const *input_name = "perf.data";
static int input;
static unsigned long page_size;
static unsigned long mmap_window = 32;
static unsigned long total = 0;
static unsigned long total_comm = 0;
static struct rb_root threads;
static struct thread *last_match;
static struct perf_header *header;
static u64 sample_type;
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread;
thread = threads__findnew(event->comm.pid, &threads, &last_match);
dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->comm.comm, event->comm.pid);
if (thread == NULL ||
thread__set_comm(thread, event->comm.comm)) {
dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n");
return -1;
}
total_comm++;
return 0;
}
static int
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
int show = 0;
struct dso *dso = NULL;
struct thread *thread;
u64 ip = event->ip.ip;
u64 timestamp = -1;
u32 cpu = -1;
u64 period = 1;
void *more_data = event->ip.__more_data;
int cpumode;
thread = threads__findnew(event->ip.pid, &threads, &last_match);
if (sample_type & PERF_SAMPLE_TIME) {
timestamp = *(u64 *)more_data;
more_data += sizeof(u64);
}
if (sample_type & PERF_SAMPLE_CPU) {
cpu = *(u32 *)more_data;
more_data += sizeof(u32);
more_data += sizeof(u32); /* reserved */
}
if (sample_type & PERF_SAMPLE_PERIOD) {
period = *(u64 *)more_data;
more_data += sizeof(u64);
}
dump_printf("%p [%p]: PERF_RECORD_SAMPLE (IP, %d): %d/%d: %p period: %Ld\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->header.misc,
event->ip.pid, event->ip.tid,
(void *)(long)ip,
(long long)period);
dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid);
if (thread == NULL) {
eprintf("problem processing %d event, skipping it.\n",
event->header.type);
return -1;
}
cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
if (cpumode == PERF_RECORD_MISC_KERNEL) {
show = SHOW_KERNEL;
level = 'k';
dso = kernel_dso;
dump_printf(" ...... dso: %s\n", dso->name);
} else if (cpumode == PERF_RECORD_MISC_USER) {
show = SHOW_USER;
level = '.';
} else {
show = SHOW_HV;
level = 'H';
dso = hypervisor_dso;
dump_printf(" ...... dso: [hypervisor]\n");
}
if (sample_type & PERF_SAMPLE_RAW) {
struct {
u32 size;
char data[0];
} *raw = more_data;
/*
* FIXME: better resolve from pid from the struct trace_entry
* field, although it should be the same than this perf
* event pid
*/
print_event(cpu, raw->data, raw->size, timestamp, thread->comm);
}
total += period;
return 0;
}
static int
process_event(event_t *event, unsigned long offset, unsigned long head)
{
trace_event(event);
switch (event->header.type) {
case PERF_RECORD_MMAP ... PERF_RECORD_LOST:
return 0;
case PERF_RECORD_COMM:
return process_comm_event(event, offset, head);
case PERF_RECORD_EXIT ... PERF_RECORD_READ:
return 0;
case PERF_RECORD_SAMPLE:
return process_sample_event(event, offset, head);
case PERF_RECORD_MAX:
default:
return -1;
}
return 0;
}
static int __cmd_trace(void)
{
int ret, rc = EXIT_FAILURE;
unsigned long offset = 0;
unsigned long head = 0;
struct stat perf_stat;
event_t *event;
uint32_t size;
char *buf;
trace_report();
register_idle_thread(&threads, &last_match);
input = open(input_name, O_RDONLY);
if (input < 0) {
perror("failed to open file");
exit(-1);
}
ret = fstat(input, &perf_stat);
if (ret < 0) {
perror("failed to stat file");
exit(-1);
}
if (!perf_stat.st_size) {
fprintf(stderr, "zero-sized file, nothing to do!\n");
exit(0);
}
header = perf_header__read(input);
head = header->data_offset;
sample_type = perf_header__sample_type(header);
if (!(sample_type & PERF_SAMPLE_RAW))
die("No trace sample to read. Did you call perf record "
"without -R?");
if (load_kernel() < 0) {
perror("failed to load kernel symbols");
return EXIT_FAILURE;
}
remap:
buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ,
MAP_SHARED, input, offset);
if (buf == MAP_FAILED) {
perror("failed to mmap file");
exit(-1);
}
more:
event = (event_t *)(buf + head);
if (head + event->header.size >= page_size * mmap_window) {
unsigned long shift = page_size * (head / page_size);
int res;
res = munmap(buf, page_size * mmap_window);
assert(res == 0);
offset += shift;
head -= shift;
goto remap;
}
size = event->header.size;
if (!size || process_event(event, offset, head) < 0) {
/*
* assume we lost track of the stream, check alignment, and
* increment a single u64 in the hope to catch on again 'soon'.
*/
if (unlikely(head & 7))
head &= ~7ULL;
size = 8;
}
head += size;
if (offset + head < (unsigned long)perf_stat.st_size)
goto more;
rc = EXIT_SUCCESS;
close(input);
return rc;
}
static const char * const annotate_usage[] = {
"perf trace [<options>] <command>",
NULL
};
static const struct option options[] = {
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
"dump raw trace in ASCII"),
OPT_BOOLEAN('v', "verbose", &verbose,
"be more verbose (show symbol address, etc)"),
OPT_END()
};
int cmd_trace(int argc, const char **argv, const char *prefix __used)
{
symbol__init();
page_size = getpagesize();
argc = parse_options(argc, argv, options, annotate_usage, 0);
if (argc) {
/*
* Special case: if there's an argument left then assume tha
* it's a symbol filter:
*/
if (argc > 1)
usage_with_options(annotate_usage, options);
}
setup_pager();
return __cmd_trace();
}

View File

@@ -0,0 +1,29 @@
#ifndef BUILTIN_H
#define BUILTIN_H
#include "util/util.h"
#include "util/strbuf.h"
extern const char perf_version_string[];
extern const char perf_usage_string[];
extern const char perf_more_info_string[];
extern void list_common_cmds_help(void);
extern const char *help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int check_pager_config(const char *cmd);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_help(int argc, const char **argv, const char *prefix);
extern int cmd_sched(int argc, const char **argv, const char *prefix);
extern int cmd_list(int argc, const char **argv, const char *prefix);
extern int cmd_record(int argc, const char **argv, const char *prefix);
extern int cmd_report(int argc, const char **argv, const char *prefix);
extern int cmd_stat(int argc, const char **argv, const char *prefix);
extern int cmd_timechart(int argc, const char **argv, const char *prefix);
extern int cmd_top(int argc, const char **argv, const char *prefix);
extern int cmd_trace(int argc, const char **argv, const char *prefix);
extern int cmd_version(int argc, const char **argv, const char *prefix);
#endif

View File

@@ -0,0 +1,13 @@
#
# List of known perf commands.
# command name category [deprecated] [common]
#
perf-annotate mainporcelain common
perf-list mainporcelain common
perf-sched mainporcelain common
perf-record mainporcelain common
perf-report mainporcelain common
perf-stat mainporcelain common
perf-timechart mainporcelain common
perf-top mainporcelain common
perf-trace mainporcelain common

View File

@@ -0,0 +1,460 @@
Performance Counters for Linux
------------------------------
Performance counters are special hardware registers available on most modern
CPUs. These registers count the number of certain types of hw events: such
as instructions executed, cachemisses suffered, or branches mis-predicted -
without slowing down the kernel or applications. These registers can also
trigger interrupts when a threshold number of events have passed - and can
thus be used to profile the code that runs on that CPU.
The Linux Performance Counter subsystem provides an abstraction of these
hardware capabilities. It provides per task and per CPU counters, counter
groups, and it provides event capabilities on top of those. It
provides "virtual" 64-bit counters, regardless of the width of the
underlying hardware counters.
Performance counters are accessed via special file descriptors.
There's one file descriptor per virtual counter used.
The special file descriptor is opened via the perf_event_open()
system call:
int sys_perf_event_open(struct perf_event_hw_event *hw_event_uptr,
pid_t pid, int cpu, int group_fd,
unsigned long flags);
The syscall returns the new fd. The fd can be used via the normal
VFS system calls: read() can be used to read the counter, fcntl()
can be used to set the blocking mode, etc.
Multiple counters can be kept open at a time, and the counters
can be poll()ed.
When creating a new counter fd, 'perf_event_hw_event' is:
struct perf_event_hw_event {
/*
* The MSB of the config word signifies if the rest contains cpu
* specific (raw) counter configuration data, if unset, the next
* 7 bits are an event type and the rest of the bits are the event
* identifier.
*/
__u64 config;
__u64 irq_period;
__u32 record_type;
__u32 read_format;
__u64 disabled : 1, /* off by default */
inherit : 1, /* children inherit it */
pinned : 1, /* must always be on PMU */
exclusive : 1, /* only group on PMU */
exclude_user : 1, /* don't count user */
exclude_kernel : 1, /* ditto kernel */
exclude_hv : 1, /* ditto hypervisor */
exclude_idle : 1, /* don't count when idle */
mmap : 1, /* include mmap data */
munmap : 1, /* include munmap data */
comm : 1, /* include comm data */
__reserved_1 : 52;
__u32 extra_config_len;
__u32 wakeup_events; /* wakeup every n events */
__u64 __reserved_2;
__u64 __reserved_3;
};
The 'config' field specifies what the counter should count. It
is divided into 3 bit-fields:
raw_type: 1 bit (most significant bit) 0x8000_0000_0000_0000
type: 7 bits (next most significant) 0x7f00_0000_0000_0000
event_id: 56 bits (least significant) 0x00ff_ffff_ffff_ffff
If 'raw_type' is 1, then the counter will count a hardware event
specified by the remaining 63 bits of event_config. The encoding is
machine-specific.
If 'raw_type' is 0, then the 'type' field says what kind of counter
this is, with the following encoding:
enum perf_event_types {
PERF_TYPE_HARDWARE = 0,
PERF_TYPE_SOFTWARE = 1,
PERF_TYPE_TRACEPOINT = 2,
};
A counter of PERF_TYPE_HARDWARE will count the hardware event
specified by 'event_id':
/*
* Generalized performance counter event types, used by the hw_event.event_id
* parameter of the sys_perf_event_open() syscall:
*/
enum hw_event_ids {
/*
* Common hardware events, generalized by the kernel:
*/
PERF_COUNT_HW_CPU_CYCLES = 0,
PERF_COUNT_HW_INSTRUCTIONS = 1,
PERF_COUNT_HW_CACHE_REFERENCES = 2,
PERF_COUNT_HW_CACHE_MISSES = 3,
PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4,
PERF_COUNT_HW_BRANCH_MISSES = 5,
PERF_COUNT_HW_BUS_CYCLES = 6,
};
These are standardized types of events that work relatively uniformly
on all CPUs that implement Performance Counters support under Linux,
although there may be variations (e.g., different CPUs might count
cache references and misses at different levels of the cache hierarchy).
If a CPU is not able to count the selected event, then the system call
will return -EINVAL.
More hw_event_types are supported as well, but they are CPU-specific
and accessed as raw events. For example, to count "External bus
cycles while bus lock signal asserted" events on Intel Core CPUs, pass
in a 0x4064 event_id value and set hw_event.raw_type to 1.
A counter of type PERF_TYPE_SOFTWARE will count one of the available
software events, selected by 'event_id':
/*
* Special "software" counters provided by the kernel, even if the hardware
* does not support performance counters. These counters measure various
* physical and sw events of the kernel (and allow the profiling of them as
* well):
*/
enum sw_event_ids {
PERF_COUNT_SW_CPU_CLOCK = 0,
PERF_COUNT_SW_TASK_CLOCK = 1,
PERF_COUNT_SW_PAGE_FAULTS = 2,
PERF_COUNT_SW_CONTEXT_SWITCHES = 3,
PERF_COUNT_SW_CPU_MIGRATIONS = 4,
PERF_COUNT_SW_PAGE_FAULTS_MIN = 5,
PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6,
};
Counters of the type PERF_TYPE_TRACEPOINT are available when the ftrace event
tracer is available, and event_id values can be obtained from
/debug/tracing/events/*/*/id
Counters come in two flavours: counting counters and sampling
counters. A "counting" counter is one that is used for counting the
number of events that occur, and is characterised by having
irq_period = 0.
A read() on a counter returns the current value of the counter and possible
additional values as specified by 'read_format', each value is a u64 (8 bytes)
in size.
/*
* Bits that can be set in hw_event.read_format to request that
* reads on the counter should return the indicated quantities,
* in increasing order of bit value, after the counter value.
*/
enum perf_event_read_format {
PERF_FORMAT_TOTAL_TIME_ENABLED = 1,
PERF_FORMAT_TOTAL_TIME_RUNNING = 2,
};
Using these additional values one can establish the overcommit ratio for a
particular counter allowing one to take the round-robin scheduling effect
into account.
A "sampling" counter is one that is set up to generate an interrupt
every N events, where N is given by 'irq_period'. A sampling counter
has irq_period > 0. The record_type controls what data is recorded on each
interrupt:
/*
* Bits that can be set in hw_event.record_type to request information
* in the overflow packets.
*/
enum perf_event_record_format {
PERF_RECORD_IP = 1U << 0,
PERF_RECORD_TID = 1U << 1,
PERF_RECORD_TIME = 1U << 2,
PERF_RECORD_ADDR = 1U << 3,
PERF_RECORD_GROUP = 1U << 4,
PERF_RECORD_CALLCHAIN = 1U << 5,
};
Such (and other) events will be recorded in a ring-buffer, which is
available to user-space using mmap() (see below).
The 'disabled' bit specifies whether the counter starts out disabled
or enabled. If it is initially disabled, it can be enabled by ioctl
or prctl (see below).
The 'inherit' bit, if set, specifies that this counter should count
events on descendant tasks as well as the task specified. This only
applies to new descendents, not to any existing descendents at the
time the counter is created (nor to any new descendents of existing
descendents).
The 'pinned' bit, if set, specifies that the counter should always be
on the CPU if at all possible. It only applies to hardware counters
and only to group leaders. If a pinned counter cannot be put onto the
CPU (e.g. because there are not enough hardware counters or because of
a conflict with some other event), then the counter goes into an
'error' state, where reads return end-of-file (i.e. read() returns 0)
until the counter is subsequently enabled or disabled.
The 'exclusive' bit, if set, specifies that when this counter's group
is on the CPU, it should be the only group using the CPU's counters.
In future, this will allow sophisticated monitoring programs to supply
extra configuration information via 'extra_config_len' to exploit
advanced features of the CPU's Performance Monitor Unit (PMU) that are
not otherwise accessible and that might disrupt other hardware
counters.
The 'exclude_user', 'exclude_kernel' and 'exclude_hv' bits provide a
way to request that counting of events be restricted to times when the
CPU is in user, kernel and/or hypervisor mode.
The 'mmap' and 'munmap' bits allow recording of PROT_EXEC mmap/munmap
operations, these can be used to relate userspace IP addresses to actual
code, even after the mapping (or even the whole process) is gone,
these events are recorded in the ring-buffer (see below).
The 'comm' bit allows tracking of process comm data on process creation.
This too is recorded in the ring-buffer (see below).
The 'pid' parameter to the perf_event_open() system call allows the
counter to be specific to a task:
pid == 0: if the pid parameter is zero, the counter is attached to the
current task.
pid > 0: the counter is attached to a specific task (if the current task
has sufficient privilege to do so)
pid < 0: all tasks are counted (per cpu counters)
The 'cpu' parameter allows a counter to be made specific to a CPU:
cpu >= 0: the counter is restricted to a specific CPU
cpu == -1: the counter counts on all CPUs
(Note: the combination of 'pid == -1' and 'cpu == -1' is not valid.)
A 'pid > 0' and 'cpu == -1' counter is a per task counter that counts
events of that task and 'follows' that task to whatever CPU the task
gets schedule to. Per task counters can be created by any user, for
their own tasks.
A 'pid == -1' and 'cpu == x' counter is a per CPU counter that counts
all events on CPU-x. Per CPU counters need CAP_SYS_ADMIN privilege.
The 'flags' parameter is currently unused and must be zero.
The 'group_fd' parameter allows counter "groups" to be set up. A
counter group has one counter which is the group "leader". The leader
is created first, with group_fd = -1 in the perf_event_open call
that creates it. The rest of the group members are created
subsequently, with group_fd giving the fd of the group leader.
(A single counter on its own is created with group_fd = -1 and is
considered to be a group with only 1 member.)
A counter group is scheduled onto the CPU as a unit, that is, it will
only be put onto the CPU if all of the counters in the group can be
put onto the CPU. This means that the values of the member counters
can be meaningfully compared, added, divided (to get ratios), etc.,
with each other, since they have counted events for the same set of
executed instructions.
Like stated, asynchronous events, like counter overflow or PROT_EXEC mmap
tracking are logged into a ring-buffer. This ring-buffer is created and
accessed through mmap().
The mmap size should be 1+2^n pages, where the first page is a meta-data page
(struct perf_event_mmap_page) that contains various bits of information such
as where the ring-buffer head is.
/*
* Structure of the page that can be mapped via mmap
*/
struct perf_event_mmap_page {
__u32 version; /* version number of this structure */
__u32 compat_version; /* lowest version this is compat with */
/*
* Bits needed to read the hw counters in user-space.
*
* u32 seq;
* s64 count;
*
* do {
* seq = pc->lock;
*
* barrier()
* if (pc->index) {
* count = pmc_read(pc->index - 1);
* count += pc->offset;
* } else
* goto regular_read;
*
* barrier();
* } while (pc->lock != seq);
*
* NOTE: for obvious reason this only works on self-monitoring
* processes.
*/
__u32 lock; /* seqlock for synchronization */
__u32 index; /* hardware counter identifier */
__s64 offset; /* add to hardware counter value */
/*
* Control data for the mmap() data buffer.
*
* User-space reading this value should issue an rmb(), on SMP capable
* platforms, after reading this value -- see perf_event_wakeup().
*/
__u32 data_head; /* head in the data section */
};
NOTE: the hw-counter userspace bits are arch specific and are currently only
implemented on powerpc.
The following 2^n pages are the ring-buffer which contains events of the form:
#define PERF_RECORD_MISC_KERNEL (1 << 0)
#define PERF_RECORD_MISC_USER (1 << 1)
#define PERF_RECORD_MISC_OVERFLOW (1 << 2)
struct perf_event_header {
__u32 type;
__u16 misc;
__u16 size;
};
enum perf_event_type {
/*
* The MMAP events record the PROT_EXEC mappings so that we can
* correlate userspace IPs to code. They have the following structure:
*
* struct {
* struct perf_event_header header;
*
* u32 pid, tid;
* u64 addr;
* u64 len;
* u64 pgoff;
* char filename[];
* };
*/
PERF_RECORD_MMAP = 1,
PERF_RECORD_MUNMAP = 2,
/*
* struct {
* struct perf_event_header header;
*
* u32 pid, tid;
* char comm[];
* };
*/
PERF_RECORD_COMM = 3,
/*
* When header.misc & PERF_RECORD_MISC_OVERFLOW the event_type field
* will be PERF_RECORD_*
*
* struct {
* struct perf_event_header header;
*
* { u64 ip; } && PERF_RECORD_IP
* { u32 pid, tid; } && PERF_RECORD_TID
* { u64 time; } && PERF_RECORD_TIME
* { u64 addr; } && PERF_RECORD_ADDR
*
* { u64 nr;
* { u64 event, val; } cnt[nr]; } && PERF_RECORD_GROUP
*
* { u16 nr,
* hv,
* kernel,
* user;
* u64 ips[nr]; } && PERF_RECORD_CALLCHAIN
* };
*/
};
NOTE: PERF_RECORD_CALLCHAIN is arch specific and currently only implemented
on x86.
Notification of new events is possible through poll()/select()/epoll() and
fcntl() managing signals.
Normally a notification is generated for every page filled, however one can
additionally set perf_event_hw_event.wakeup_events to generate one every
so many counter overflow events.
Future work will include a splice() interface to the ring-buffer.
Counters can be enabled and disabled in two ways: via ioctl and via
prctl. When a counter is disabled, it doesn't count or generate
events but does continue to exist and maintain its count value.
An individual counter or counter group can be enabled with
ioctl(fd, PERF_EVENT_IOC_ENABLE);
or disabled with
ioctl(fd, PERF_EVENT_IOC_DISABLE);
Enabling or disabling the leader of a group enables or disables the
whole group; that is, while the group leader is disabled, none of the
counters in the group will count. Enabling or disabling a member of a
group other than the leader only affects that counter - disabling an
non-leader stops that counter from counting but doesn't affect any
other counter.
Additionally, non-inherited overflow counters can use
ioctl(fd, PERF_EVENT_IOC_REFRESH, nr);
to enable a counter for 'nr' events, after which it gets disabled again.
A process can enable or disable all the counter groups that are
attached to it, using prctl:
prctl(PR_TASK_PERF_EVENTS_ENABLE);
prctl(PR_TASK_PERF_EVENTS_DISABLE);
This applies to all counters on the current process, whether created
by this process or by another, and doesn't affect any counters that
this process has created on other processes. It only enables or
disables the group leaders, not any other members in the groups.
Arch requirements
-----------------
If your architecture does not have hardware performance metrics, you can
still use the generic software counters based on hrtimers for sampling.
So to start with, in order to add HAVE_PERF_EVENTS to your Kconfig, you
will need at least this:
- asm/perf_event.h - a basic stub will suffice at first
- support for atomic64 types (and associated helper functions)
- set_perf_event_pending() implemented
If your architecture does have hardware capabilities, you can override the
weak stub hw_perf_event_init() to register hardware counters.
Architectures that have d-cache aliassing issues, such as Sparc and ARM,
should select PERF_USE_VMALLOC in order to avoid these for perf mmap().

503
kernel/tools/perf/perf.c Normal file
View File

@@ -0,0 +1,503 @@
/*
* perf.c
*
* Performance analysis utility.
*
* This is the main hub from which the sub-commands (perf stat,
* perf top, perf record, perf report, etc.) are started.
*/
#include "builtin.h"
#include "util/exec_cmd.h"
#include "util/cache.h"
#include "util/quote.h"
#include "util/run-command.h"
#include "util/parse-events.h"
#include "util/string.h"
const char perf_usage_string[] =
"perf [--version] [--help] COMMAND [ARGS]";
const char perf_more_info_string[] =
"See 'perf help COMMAND' for more information on a specific command.";
static int use_pager = -1;
struct pager_config {
const char *cmd;
int val;
};
static char debugfs_mntpt[MAXPATHLEN];
static int pager_command_config(const char *var, const char *value, void *data)
{
struct pager_config *c = data;
if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd))
c->val = perf_config_bool(var, value);
return 0;
}
/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
int check_pager_config(const char *cmd)
{
struct pager_config c;
c.cmd = cmd;
c.val = -1;
perf_config(pager_command_config, &c);
return c.val;
}
static void commit_pager_choice(void) {
switch (use_pager) {
case 0:
setenv("PERF_PAGER", "cat", 1);
break;
case 1:
/* setup_pager(); */
break;
default:
break;
}
}
static void set_debugfs_path(void)
{
char *path;
path = getenv(PERF_DEBUGFS_ENVIRONMENT);
snprintf(debugfs_path, MAXPATHLEN, "%s/%s", path ?: debugfs_mntpt,
"tracing/events");
}
static int handle_options(const char*** argv, int* argc, int* envchanged)
{
int handled = 0;
while (*argc > 0) {
const char *cmd = (*argv)[0];
if (cmd[0] != '-')
break;
/*
* For legacy reasons, the "version" and "help"
* commands can be written with "--" prepended
* to make them look like flags.
*/
if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
break;
/*
* Check remaining flags.
*/
if (!prefixcmp(cmd, "--exec-path")) {
cmd += 11;
if (*cmd == '=')
perf_set_argv_exec_path(cmd + 1);
else {
puts(perf_exec_path());
exit(0);
}
} else if (!strcmp(cmd, "--html-path")) {
puts(system_path(PERF_HTML_PATH));
exit(0);
} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
use_pager = 1;
} else if (!strcmp(cmd, "--no-pager")) {
use_pager = 0;
if (envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "--perf-dir")) {
if (*argc < 2) {
fprintf(stderr, "No directory given for --perf-dir.\n" );
usage(perf_usage_string);
}
setenv(PERF_DIR_ENVIRONMENT, (*argv)[1], 1);
if (envchanged)
*envchanged = 1;
(*argv)++;
(*argc)--;
handled++;
} else if (!prefixcmp(cmd, "--perf-dir=")) {
setenv(PERF_DIR_ENVIRONMENT, cmd + 10, 1);
if (envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "--work-tree")) {
if (*argc < 2) {
fprintf(stderr, "No directory given for --work-tree.\n" );
usage(perf_usage_string);
}
setenv(PERF_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
if (envchanged)
*envchanged = 1;
(*argv)++;
(*argc)--;
} else if (!prefixcmp(cmd, "--work-tree=")) {
setenv(PERF_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
if (envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "--debugfs-dir")) {
if (*argc < 2) {
fprintf(stderr, "No directory given for --debugfs-dir.\n");
usage(perf_usage_string);
}
strncpy(debugfs_mntpt, (*argv)[1], MAXPATHLEN);
debugfs_mntpt[MAXPATHLEN - 1] = '\0';
if (envchanged)
*envchanged = 1;
(*argv)++;
(*argc)--;
} else if (!prefixcmp(cmd, "--debugfs-dir=")) {
strncpy(debugfs_mntpt, cmd + 14, MAXPATHLEN);
debugfs_mntpt[MAXPATHLEN - 1] = '\0';
if (envchanged)
*envchanged = 1;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(perf_usage_string);
}
(*argv)++;
(*argc)--;
handled++;
}
return handled;
}
static int handle_alias(int *argcp, const char ***argv)
{
int envchanged = 0, ret = 0, saved_errno = errno;
int count, option_count;
const char** new_argv;
const char *alias_command;
char *alias_string;
alias_command = (*argv)[0];
alias_string = alias_lookup(alias_command);
if (alias_string) {
if (alias_string[0] == '!') {
if (*argcp > 1) {
struct strbuf buf;
strbuf_init(&buf, PATH_MAX);
strbuf_addstr(&buf, alias_string);
sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
free(alias_string);
alias_string = buf.buf;
}
ret = system(alias_string + 1);
if (ret >= 0 && WIFEXITED(ret) &&
WEXITSTATUS(ret) != 127)
exit(WEXITSTATUS(ret));
die("Failed to run '%s' when expanding alias '%s'",
alias_string + 1, alias_command);
}
count = split_cmdline(alias_string, &new_argv);
if (count < 0)
die("Bad alias.%s string", alias_command);
option_count = handle_options(&new_argv, &count, &envchanged);
if (envchanged)
die("alias '%s' changes environment variables\n"
"You can use '!perf' in the alias to do this.",
alias_command);
memmove(new_argv - option_count, new_argv,
count * sizeof(char *));
new_argv -= option_count;
if (count < 1)
die("empty alias for %s", alias_command);
if (!strcmp(alias_command, new_argv[0]))
die("recursive alias: %s", alias_command);
new_argv = realloc(new_argv, sizeof(char*) *
(count + *argcp + 1));
/* insert after command name */
memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
new_argv[count+*argcp] = NULL;
*argv = new_argv;
*argcp += count - 1;
ret = 1;
}
errno = saved_errno;
return ret;
}
const char perf_version_string[] = PERF_VERSION;
#define RUN_SETUP (1<<0)
#define USE_PAGER (1<<1)
/*
* require working tree to be present -- anything uses this needs
* RUN_SETUP for reading from the configuration file.
*/
#define NEED_WORK_TREE (1<<2)
struct cmd_struct {
const char *cmd;
int (*fn)(int, const char **, const char *);
int option;
};
static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
{
int status;
struct stat st;
const char *prefix;
prefix = NULL;
if (p->option & RUN_SETUP)
prefix = NULL; /* setup_perf_directory(); */
if (use_pager == -1 && p->option & RUN_SETUP)
use_pager = check_pager_config(p->cmd);
if (use_pager == -1 && p->option & USE_PAGER)
use_pager = 1;
commit_pager_choice();
set_debugfs_path();
status = p->fn(argc, argv, prefix);
if (status)
return status & 0xff;
/* Somebody closed stdout? */
if (fstat(fileno(stdout), &st))
return 0;
/* Ignore write errors for pipes and sockets.. */
if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
return 0;
/* Check for ENOSPC and EIO errors.. */
if (fflush(stdout))
die("write failure on standard output: %s", strerror(errno));
if (ferror(stdout))
die("unknown write failure on standard output");
if (fclose(stdout))
die("close failed on standard output: %s", strerror(errno));
return 0;
}
static void handle_internal_command(int argc, const char **argv)
{
const char *cmd = argv[0];
static struct cmd_struct commands[] = {
{ "help", cmd_help, 0 },
{ "list", cmd_list, 0 },
{ "record", cmd_record, 0 },
{ "report", cmd_report, 0 },
{ "stat", cmd_stat, 0 },
{ "timechart", cmd_timechart, 0 },
{ "top", cmd_top, 0 },
{ "annotate", cmd_annotate, 0 },
{ "version", cmd_version, 0 },
{ "trace", cmd_trace, 0 },
{ "sched", cmd_sched, 0 },
};
unsigned int i;
static const char ext[] = STRIP_EXTENSION;
if (sizeof(ext) > 1) {
i = strlen(argv[0]) - strlen(ext);
if (i > 0 && !strcmp(argv[0] + i, ext)) {
char *argv0 = strdup(argv[0]);
argv[0] = cmd = argv0;
argv0[i] = '\0';
}
}
/* Turn "perf cmd --help" into "perf help cmd" */
if (argc > 1 && !strcmp(argv[1], "--help")) {
argv[1] = argv[0];
argv[0] = cmd = "help";
}
for (i = 0; i < ARRAY_SIZE(commands); i++) {
struct cmd_struct *p = commands+i;
if (strcmp(p->cmd, cmd))
continue;
exit(run_builtin(p, argc, argv));
}
}
static void execv_dashed_external(const char **argv)
{
struct strbuf cmd = STRBUF_INIT;
const char *tmp;
int status;
strbuf_addf(&cmd, "perf-%s", argv[0]);
/*
* argv[0] must be the perf command, but the argv array
* belongs to the caller, and may be reused in
* subsequent loop iterations. Save argv[0] and
* restore it on error.
*/
tmp = argv[0];
argv[0] = cmd.buf;
/*
* if we fail because the command is not found, it is
* OK to return. Otherwise, we just pass along the status code.
*/
status = run_command_v_opt(argv, 0);
if (status != -ERR_RUN_COMMAND_EXEC) {
if (IS_RUN_COMMAND_ERR(status))
die("unable to run '%s'", argv[0]);
exit(-status);
}
errno = ENOENT; /* as if we called execvp */
argv[0] = tmp;
strbuf_release(&cmd);
}
static int run_argv(int *argcp, const char ***argv)
{
int done_alias = 0;
while (1) {
/* See if it's an internal command */
handle_internal_command(*argcp, *argv);
/* .. then try the external ones */
execv_dashed_external(*argv);
/* It could be an alias -- this works around the insanity
* of overriding "perf log" with "perf show" by having
* alias.log = show
*/
if (done_alias || !handle_alias(argcp, argv))
break;
done_alias = 1;
}
return done_alias;
}
/* mini /proc/mounts parser: searching for "^blah /mount/point debugfs" */
static void get_debugfs_mntpt(void)
{
FILE *file;
char fs_type[100];
char debugfs[MAXPATHLEN];
/*
* try the standard location
*/
if (valid_debugfs_mount("/sys/kernel/debug/") == 0) {
strcpy(debugfs_mntpt, "/sys/kernel/debug/");
return;
}
/*
* try the sane location
*/
if (valid_debugfs_mount("/debug/") == 0) {
strcpy(debugfs_mntpt, "/debug/");
return;
}
/*
* give up and parse /proc/mounts
*/
file = fopen("/proc/mounts", "r");
if (file == NULL)
return;
while (fscanf(file, "%*s %"
STR(MAXPATHLEN)
"s %99s %*s %*d %*d\n",
debugfs, fs_type) == 2) {
if (strcmp(fs_type, "debugfs") == 0)
break;
}
fclose(file);
if (strcmp(fs_type, "debugfs") == 0) {
strncpy(debugfs_mntpt, debugfs, MAXPATHLEN);
debugfs_mntpt[MAXPATHLEN - 1] = '\0';
}
}
int main(int argc, const char **argv)
{
const char *cmd;
cmd = perf_extract_argv0_path(argv[0]);
if (!cmd)
cmd = "perf-help";
/* get debugfs mount point from /proc/mounts */
get_debugfs_mntpt();
/*
* "perf-xxxx" is the same as "perf xxxx", but we obviously:
*
* - cannot take flags in between the "perf" and the "xxxx".
* - cannot execute it externally (since it would just do
* the same thing over again)
*
* So we just directly call the internal command handler, and
* die if that one cannot handle it.
*/
if (!prefixcmp(cmd, "perf-")) {
cmd += 5;
argv[0] = cmd;
handle_internal_command(argc, argv);
die("cannot handle %s internally", cmd);
}
/* Look for flags.. */
argv++;
argc--;
handle_options(&argv, &argc, NULL);
commit_pager_choice();
set_debugfs_path();
if (argc > 0) {
if (!prefixcmp(argv[0], "--"))
argv[0] += 2;
} else {
/* The user didn't specify a command; give them help */
printf("\n usage: %s\n\n", perf_usage_string);
list_common_cmds_help();
printf("\n %s\n\n", perf_more_info_string);
exit(1);
}
cmd = argv[0];
/*
* We use PATH to find perf commands, but we prepend some higher
* precidence paths: the "--exec-path" option, the PERF_EXEC_PATH
* environment, and the $(perfexecdir) from the Makefile at build
* time.
*/
setup_path();
while (1) {
static int done_help = 0;
static int was_alias = 0;
was_alias = run_argv(&argc, &argv);
if (errno != ENOENT)
break;
if (was_alias) {
fprintf(stderr, "Expansion of alias '%s' failed; "
"'%s' is not a perf-command\n",
cmd, argv[0]);
exit(1);
}
if (!done_help) {
cmd = argv[0] = help_unknown_cmd(cmd);
done_help = 1;
} else
break;
}
fprintf(stderr, "Failed to run command '%s': %s\n",
cmd, strerror(errno));
return 1;
}

110
kernel/tools/perf/perf.h Normal file
View File

@@ -0,0 +1,110 @@
#ifndef _PERF_PERF_H
#define _PERF_PERF_H
#if defined(__i386__)
#include "../../arch/x86/include/asm/unistd.h"
#define rmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory")
#define cpu_relax() asm volatile("rep; nop" ::: "memory");
#endif
#if defined(__x86_64__)
#include "../../arch/x86/include/asm/unistd.h"
#define rmb() asm volatile("lfence" ::: "memory")
#define cpu_relax() asm volatile("rep; nop" ::: "memory");
#endif
#ifdef __powerpc__
#include "../../arch/powerpc/include/asm/unistd.h"
#define rmb() asm volatile ("sync" ::: "memory")
#define cpu_relax() asm volatile ("" ::: "memory");
#endif
#ifdef __s390__
#include "../../arch/s390/include/asm/unistd.h"
#define rmb() asm volatile("bcr 15,0" ::: "memory")
#define cpu_relax() asm volatile("" ::: "memory");
#endif
#ifdef __sh__
#include "../../arch/sh/include/asm/unistd.h"
#if defined(__SH4A__) || defined(__SH5__)
# define rmb() asm volatile("synco" ::: "memory")
#else
# define rmb() asm volatile("" ::: "memory")
#endif
#define cpu_relax() asm volatile("" ::: "memory")
#endif
#ifdef __hppa__
#include "../../arch/parisc/include/asm/unistd.h"
#define rmb() asm volatile("" ::: "memory")
#define cpu_relax() asm volatile("" ::: "memory");
#endif
#ifdef __sparc__
#include "../../arch/sparc/include/asm/unistd.h"
#define rmb() asm volatile("":::"memory")
#define cpu_relax() asm volatile("":::"memory")
#endif
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include "../../include/linux/perf_event.h"
#include "util/types.h"
/*
* prctl(PR_TASK_PERF_EVENTS_DISABLE) will (cheaply) disable all
* counters in the current task.
*/
#define PR_TASK_PERF_EVENTS_DISABLE 31
#define PR_TASK_PERF_EVENTS_ENABLE 32
#ifndef NSEC_PER_SEC
# define NSEC_PER_SEC 1000000000ULL
#endif
static inline unsigned long long rdclock(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
/*
* Pick up some kernel type conventions:
*/
#define __user
#define asmlinkage
#define __used __attribute__((__unused__))
#define unlikely(x) __builtin_expect(!!(x), 0)
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
static inline int
sys_perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd,
unsigned long flags)
{
attr->size = sizeof(*attr);
return syscall(__NR_perf_event_open, attr, pid, cpu,
group_fd, flags);
}
#define MAX_COUNTERS 256
#define MAX_NR_CPUS 256
struct ip_callchain {
u64 nr;
u64 ips[0];
};
#endif

View File

@@ -0,0 +1,42 @@
#!/bin/sh
GVF=PERF-VERSION-FILE
DEF_VER=v0.0.2.PERF
LF='
'
# First see if there is a version file (included in release tarballs),
# then try git-describe, then default.
if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
elif test -d .git -o -f .git &&
VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
case "$VN" in
*$LF*) (exit 1) ;;
v[0-9]*)
git update-index -q --refresh
test -z "$(git diff-index --name-only HEAD --)" ||
VN="$VN-dirty" ;;
esac
then
VN=$(echo "$VN" | sed -e 's/-/./g');
else
VN="$DEF_VER"
fi
VN=$(expr "$VN" : v*'\(.*\)')
if test -r $GVF
then
VC=$(sed -e 's/^PERF_VERSION = //' <$GVF)
else
VC=unset
fi
test "$VN" = "$VC" || {
echo >&2 "PERF_VERSION = $VN"
echo "PERF_VERSION = $VN" >$GVF
}

View File

@@ -0,0 +1,118 @@
#include "cache.h"
/*
* Do not use this for inspecting *tracked* content. When path is a
* symlink to a directory, we do not want to say it is a directory when
* dealing with tracked content in the working tree.
*/
static int is_directory(const char *path)
{
struct stat st;
return (!stat(path, &st) && S_ISDIR(st.st_mode));
}
/* We allow "recursive" symbolic links. Only within reason, though. */
#define MAXDEPTH 5
const char *make_absolute_path(const char *path)
{
static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
char cwd[1024] = "";
int buf_index = 1, len;
int depth = MAXDEPTH;
char *last_elem = NULL;
struct stat st;
if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
die ("Too long path: %.*s", 60, path);
while (depth--) {
if (!is_directory(buf)) {
char *last_slash = strrchr(buf, '/');
if (last_slash) {
*last_slash = '\0';
last_elem = xstrdup(last_slash + 1);
} else {
last_elem = xstrdup(buf);
*buf = '\0';
}
}
if (*buf) {
if (!*cwd && !getcwd(cwd, sizeof(cwd)))
die ("Could not get current working directory");
if (chdir(buf))
die ("Could not switch to '%s'", buf);
}
if (!getcwd(buf, PATH_MAX))
die ("Could not get current working directory");
if (last_elem) {
len = strlen(buf);
if (len + strlen(last_elem) + 2 > PATH_MAX)
die ("Too long path name: '%s/%s'",
buf, last_elem);
buf[len] = '/';
strcpy(buf + len + 1, last_elem);
free(last_elem);
last_elem = NULL;
}
if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
len = readlink(buf, next_buf, PATH_MAX);
if (len < 0)
die ("Invalid symlink: %s", buf);
if (PATH_MAX <= len)
die("symbolic link too long: %s", buf);
next_buf[len] = '\0';
buf = next_buf;
buf_index = 1 - buf_index;
next_buf = bufs[buf_index];
} else
break;
}
if (*cwd && chdir(cwd))
die ("Could not change back to '%s'", cwd);
return buf;
}
static const char *get_pwd_cwd(void)
{
static char cwd[PATH_MAX + 1];
char *pwd;
struct stat cwd_stat, pwd_stat;
if (getcwd(cwd, PATH_MAX) == NULL)
return NULL;
pwd = getenv("PWD");
if (pwd && strcmp(pwd, cwd)) {
stat(cwd, &cwd_stat);
if (!stat(pwd, &pwd_stat) &&
pwd_stat.st_dev == cwd_stat.st_dev &&
pwd_stat.st_ino == cwd_stat.st_ino) {
strlcpy(cwd, pwd, PATH_MAX);
}
}
return cwd;
}
const char *make_nonrelative_path(const char *path)
{
static char buf[PATH_MAX + 1];
if (is_absolute_path(path)) {
if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
} else {
const char *cwd = get_pwd_cwd();
if (!cwd)
die("Cannot determine the current working directory");
if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
}
return buf;
}

View File

@@ -0,0 +1,77 @@
#include "cache.h"
static const char *alias_key;
static char *alias_val;
static int alias_lookup_cb(const char *k, const char *v, void *cb __used)
{
if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
if (!v)
return config_error_nonbool(k);
alias_val = strdup(v);
return 0;
}
return 0;
}
char *alias_lookup(const char *alias)
{
alias_key = alias;
alias_val = NULL;
perf_config(alias_lookup_cb, NULL);
return alias_val;
}
int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
char quoted = 0;
*argv = malloc(sizeof(char*) * size);
/* split alias_string */
(*argv)[count++] = cmdline;
for (src = dst = 0; cmdline[src];) {
char c = cmdline[src];
if (!quoted && isspace(c)) {
cmdline[dst++] = 0;
while (cmdline[++src]
&& isspace(cmdline[src]))
; /* skip */
if (count >= size) {
size += 16;
*argv = realloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if (!quoted && (c == '\'' || c == '"')) {
quoted = c;
src++;
} else if (c == quoted) {
quoted = 0;
src++;
} else {
if (c == '\\' && quoted != '\'') {
src++;
c = cmdline[src];
if (!c) {
free(*argv);
*argv = NULL;
return error("cmdline ends with \\");
}
}
cmdline[dst++] = c;
src++;
}
}
cmdline[dst] = 0;
if (quoted) {
free(*argv);
*argv = NULL;
return error("unclosed quote");
}
return count;
}

View File

@@ -0,0 +1,120 @@
#ifndef CACHE_H
#define CACHE_H
#include "util.h"
#include "strbuf.h"
#include "../perf.h"
#define PERF_DIR_ENVIRONMENT "PERF_DIR"
#define PERF_WORK_TREE_ENVIRONMENT "PERF_WORK_TREE"
#define DEFAULT_PERF_DIR_ENVIRONMENT ".perf"
#define DB_ENVIRONMENT "PERF_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "PERF_INDEX_FILE"
#define GRAFT_ENVIRONMENT "PERF_GRAFT_FILE"
#define TEMPLATE_DIR_ENVIRONMENT "PERF_TEMPLATE_DIR"
#define CONFIG_ENVIRONMENT "PERF_CONFIG"
#define EXEC_PATH_ENVIRONMENT "PERF_EXEC_PATH"
#define CEILING_DIRECTORIES_ENVIRONMENT "PERF_CEILING_DIRECTORIES"
#define PERFATTRIBUTES_FILE ".perfattributes"
#define INFOATTRIBUTES_FILE "info/attributes"
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
#define PERF_DEBUGFS_ENVIRONMENT "PERF_DEBUGFS_DIR"
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int perf_default_config(const char *, const char *, void *);
extern int perf_config_from_file(config_fn_t fn, const char *, void *);
extern int perf_config(config_fn_t fn, void *);
extern int perf_parse_ulong(const char *, unsigned long *);
extern int perf_config_int(const char *, const char *);
extern unsigned long perf_config_ulong(const char *, const char *);
extern int perf_config_bool_or_int(const char *, const char *, int *);
extern int perf_config_bool(const char *, const char *);
extern int perf_config_string(const char **, const char *, const char *);
extern int perf_config_set(const char *, const char *);
extern int perf_config_set_multivar(const char *, const char *, const char *, int);
extern int perf_config_rename_section(const char *, const char *);
extern const char *perf_etc_perfconfig(void);
extern int check_repository_format_version(const char *var, const char *value, void *cb);
extern int perf_config_system(void);
extern int perf_config_global(void);
extern int config_error_nonbool(const char *);
extern const char *config_exclusive_filename;
#define MAX_PERFNAME (1000)
extern char perf_default_email[MAX_PERFNAME];
extern char perf_default_name[MAX_PERFNAME];
extern int user_ident_explicitly_given;
extern const char *perf_log_output_encoding;
extern const char *perf_mailmap_file;
/* IO helper functions */
extern void maybe_flush_or_die(FILE *, const char *);
extern int copy_fd(int ifd, int ofd);
extern int copy_file(const char *dst, const char *src, int mode);
extern ssize_t write_in_full(int fd, const void *buf, size_t count);
extern void write_or_die(int fd, const void *buf, size_t count);
extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
extern void fsync_or_die(int fd, const char *);
/* pager.c */
extern void setup_pager(void);
extern const char *pager_program;
extern int pager_in_use(void);
extern int pager_use_color;
extern const char *editor_program;
extern const char *excludes_file;
char *alias_lookup(const char *alias);
int split_cmdline(char *cmdline, const char ***argv);
#define alloc_nr(x) (((x)+16)*3/2)
/*
* Realloc the buffer pointed at by variable 'x' so that it can hold
* at least 'nr' entries; the number of entries currently allocated
* is 'alloc', using the standard growing factor alloc_nr() macro.
*
* DO NOT USE any expression with side-effect for 'x' or 'alloc'.
*/
#define ALLOC_GROW(x, nr, alloc) \
do { \
if ((nr) > alloc) { \
if (alloc_nr(alloc) < (nr)) \
alloc = (nr); \
else \
alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
} while(0)
static inline int is_absolute_path(const char *path)
{
return path[0] == '/';
}
const char *make_absolute_path(const char *path);
const char *make_nonrelative_path(const char *path);
const char *make_relative_path(const char *abs, const char *base);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, const char *prefix_list);
char *strip_path_suffix(const char *path, const char *suffix);
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern char *perf_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
/* perf_mkstemp() - create tmp file honoring TMPDIR variable */
extern int perf_mkstemp(char *path, size_t len, const char *template);
extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
extern char *perf_snpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
extern char *perf_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
extern size_t strlcpy(char *dest, const char *src, size_t size);
#endif /* CACHE_H */

View File

@@ -0,0 +1,345 @@
/*
* Copyright (C) 2009, Frederic Weisbecker <fweisbec@gmail.com>
*
* Handle the callchains from the stream in an ad-hoc radix tree and then
* sort them in an rbtree.
*
* Using a radix for code path provides a fast retrieval and factorizes
* memory use. Also that lets us use the paths in a hierarchical graph view.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <math.h>
#include "callchain.h"
#define chain_for_each_child(child, parent) \
list_for_each_entry(child, &parent->children, brothers)
static void
rb_insert_callchain(struct rb_root *root, struct callchain_node *chain,
enum chain_mode mode)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
struct callchain_node *rnode;
u64 chain_cumul = cumul_hits(chain);
while (*p) {
u64 rnode_cumul;
parent = *p;
rnode = rb_entry(parent, struct callchain_node, rb_node);
rnode_cumul = cumul_hits(rnode);
switch (mode) {
case CHAIN_FLAT:
if (rnode->hit < chain->hit)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
break;
case CHAIN_GRAPH_ABS: /* Falldown */
case CHAIN_GRAPH_REL:
if (rnode_cumul < chain_cumul)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
break;
case CHAIN_NONE:
default:
break;
}
}
rb_link_node(&chain->rb_node, parent, p);
rb_insert_color(&chain->rb_node, root);
}
static void
__sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
u64 min_hit)
{
struct callchain_node *child;
chain_for_each_child(child, node)
__sort_chain_flat(rb_root, child, min_hit);
if (node->hit && node->hit >= min_hit)
rb_insert_callchain(rb_root, node, CHAIN_FLAT);
}
/*
* Once we get every callchains from the stream, we can now
* sort them by hit
*/
static void
sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
u64 min_hit, struct callchain_param *param __used)
{
__sort_chain_flat(rb_root, node, min_hit);
}
static void __sort_chain_graph_abs(struct callchain_node *node,
u64 min_hit)
{
struct callchain_node *child;
node->rb_root = RB_ROOT;
chain_for_each_child(child, node) {
__sort_chain_graph_abs(child, min_hit);
if (cumul_hits(child) >= min_hit)
rb_insert_callchain(&node->rb_root, child,
CHAIN_GRAPH_ABS);
}
}
static void
sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_node *chain_root,
u64 min_hit, struct callchain_param *param __used)
{
__sort_chain_graph_abs(chain_root, min_hit);
rb_root->rb_node = chain_root->rb_root.rb_node;
}
static void __sort_chain_graph_rel(struct callchain_node *node,
double min_percent)
{
struct callchain_node *child;
u64 min_hit;
node->rb_root = RB_ROOT;
min_hit = ceil(node->children_hit * min_percent);
chain_for_each_child(child, node) {
__sort_chain_graph_rel(child, min_percent);
if (cumul_hits(child) >= min_hit)
rb_insert_callchain(&node->rb_root, child,
CHAIN_GRAPH_REL);
}
}
static void
sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_node *chain_root,
u64 min_hit __used, struct callchain_param *param)
{
__sort_chain_graph_rel(chain_root, param->min_percent / 100.0);
rb_root->rb_node = chain_root->rb_root.rb_node;
}
int register_callchain_param(struct callchain_param *param)
{
switch (param->mode) {
case CHAIN_GRAPH_ABS:
param->sort = sort_chain_graph_abs;
break;
case CHAIN_GRAPH_REL:
param->sort = sort_chain_graph_rel;
break;
case CHAIN_FLAT:
param->sort = sort_chain_flat;
break;
case CHAIN_NONE:
default:
return -1;
}
return 0;
}
/*
* Create a child for a parent. If inherit_children, then the new child
* will become the new parent of it's parent children
*/
static struct callchain_node *
create_child(struct callchain_node *parent, bool inherit_children)
{
struct callchain_node *new;
new = malloc(sizeof(*new));
if (!new) {
perror("not enough memory to create child for code path tree");
return NULL;
}
new->parent = parent;
INIT_LIST_HEAD(&new->children);
INIT_LIST_HEAD(&new->val);
if (inherit_children) {
struct callchain_node *next;
list_splice(&parent->children, &new->children);
INIT_LIST_HEAD(&parent->children);
chain_for_each_child(next, new)
next->parent = new;
}
list_add_tail(&new->brothers, &parent->children);
return new;
}
/*
* Fill the node with callchain values
*/
static void
fill_node(struct callchain_node *node, struct ip_callchain *chain,
int start, struct symbol **syms)
{
unsigned int i;
for (i = start; i < chain->nr; i++) {
struct callchain_list *call;
call = malloc(sizeof(*call));
if (!call) {
perror("not enough memory for the code path tree");
return;
}
call->ip = chain->ips[i];
call->sym = syms[i];
list_add_tail(&call->list, &node->val);
}
node->val_nr = chain->nr - start;
if (!node->val_nr)
printf("Warning: empty node in callchain tree\n");
}
static void
add_child(struct callchain_node *parent, struct ip_callchain *chain,
int start, struct symbol **syms)
{
struct callchain_node *new;
new = create_child(parent, false);
fill_node(new, chain, start, syms);
new->children_hit = 0;
new->hit = 1;
}
/*
* Split the parent in two parts (a new child is created) and
* give a part of its callchain to the created child.
* Then create another child to host the given callchain of new branch
*/
static void
split_add_child(struct callchain_node *parent, struct ip_callchain *chain,
struct callchain_list *to_split, int idx_parents, int idx_local,
struct symbol **syms)
{
struct callchain_node *new;
struct list_head *old_tail;
unsigned int idx_total = idx_parents + idx_local;
/* split */
new = create_child(parent, true);
/* split the callchain and move a part to the new child */
old_tail = parent->val.prev;
list_del_range(&to_split->list, old_tail);
new->val.next = &to_split->list;
new->val.prev = old_tail;
to_split->list.prev = &new->val;
old_tail->next = &new->val;
/* split the hits */
new->hit = parent->hit;
new->children_hit = parent->children_hit;
parent->children_hit = cumul_hits(new);
new->val_nr = parent->val_nr - idx_local;
parent->val_nr = idx_local;
/* create a new child for the new branch if any */
if (idx_total < chain->nr) {
parent->hit = 0;
add_child(parent, chain, idx_total, syms);
parent->children_hit++;
} else {
parent->hit = 1;
}
}
static int
__append_chain(struct callchain_node *root, struct ip_callchain *chain,
unsigned int start, struct symbol **syms);
static void
__append_chain_children(struct callchain_node *root, struct ip_callchain *chain,
struct symbol **syms, unsigned int start)
{
struct callchain_node *rnode;
/* lookup in childrens */
chain_for_each_child(rnode, root) {
unsigned int ret = __append_chain(rnode, chain, start, syms);
if (!ret)
goto inc_children_hit;
}
/* nothing in children, add to the current node */
add_child(root, chain, start, syms);
inc_children_hit:
root->children_hit++;
}
static int
__append_chain(struct callchain_node *root, struct ip_callchain *chain,
unsigned int start, struct symbol **syms)
{
struct callchain_list *cnode;
unsigned int i = start;
bool found = false;
/*
* Lookup in the current node
* If we have a symbol, then compare the start to match
* anywhere inside a function.
*/
list_for_each_entry(cnode, &root->val, list) {
if (i == chain->nr)
break;
if (cnode->sym && syms[i]) {
if (cnode->sym->start != syms[i]->start)
break;
} else if (cnode->ip != chain->ips[i])
break;
if (!found)
found = true;
i++;
}
/* matches not, relay on the parent */
if (!found)
return -1;
/* we match only a part of the node. Split it and add the new chain */
if (i - start < root->val_nr) {
split_add_child(root, chain, cnode, start, i - start, syms);
return 0;
}
/* we match 100% of the path, increment the hit */
if (i - start == root->val_nr && i == chain->nr) {
root->hit++;
return 0;
}
/* We match the node and still have a part remaining */
__append_chain_children(root, chain, syms, i);
return 0;
}
void append_chain(struct callchain_node *root, struct ip_callchain *chain,
struct symbol **syms)
{
if (!chain->nr)
return;
__append_chain_children(root, chain, syms, 0);
}

View File

@@ -0,0 +1,65 @@
#ifndef __PERF_CALLCHAIN_H
#define __PERF_CALLCHAIN_H
#include "../perf.h"
#include <linux/list.h>
#include <linux/rbtree.h>
#include "util.h"
#include "symbol.h"
enum chain_mode {
CHAIN_NONE,
CHAIN_FLAT,
CHAIN_GRAPH_ABS,
CHAIN_GRAPH_REL
};
struct callchain_node {
struct callchain_node *parent;
struct list_head brothers;
struct list_head children;
struct list_head val;
struct rb_node rb_node; /* to sort nodes in an rbtree */
struct rb_root rb_root; /* sorted tree of children */
unsigned int val_nr;
u64 hit;
u64 children_hit;
};
struct callchain_param;
typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_node *,
u64, struct callchain_param *);
struct callchain_param {
enum chain_mode mode;
double min_percent;
sort_chain_func_t sort;
};
struct callchain_list {
u64 ip;
struct symbol *sym;
struct list_head list;
};
static inline void callchain_init(struct callchain_node *node)
{
INIT_LIST_HEAD(&node->brothers);
INIT_LIST_HEAD(&node->children);
INIT_LIST_HEAD(&node->val);
node->children_hit = 0;
node->parent = NULL;
node->hit = 0;
}
static inline u64 cumul_hits(struct callchain_node *node)
{
return node->hit + node->children_hit;
}
int register_callchain_param(struct callchain_param *param);
void append_chain(struct callchain_node *root, struct ip_callchain *chain,
struct symbol **syms);
#endif

View File

@@ -0,0 +1,276 @@
#include "cache.h"
#include "color.h"
int perf_use_color_default = -1;
static int parse_color(const char *name, int len)
{
static const char * const color_names[] = {
"normal", "black", "red", "green", "yellow",
"blue", "magenta", "cyan", "white"
};
char *end;
int i;
for (i = 0; i < (int)ARRAY_SIZE(color_names); i++) {
const char *str = color_names[i];
if (!strncasecmp(name, str, len) && !str[len])
return i - 1;
}
i = strtol(name, &end, 10);
if (end - name == len && i >= -1 && i <= 255)
return i;
return -2;
}
static int parse_attr(const char *name, int len)
{
static const int attr_values[] = { 1, 2, 4, 5, 7 };
static const char * const attr_names[] = {
"bold", "dim", "ul", "blink", "reverse"
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
const char *str = attr_names[i];
if (!strncasecmp(name, str, len) && !str[len])
return attr_values[i];
}
return -1;
}
void color_parse(const char *value, const char *var, char *dst)
{
color_parse_mem(value, strlen(value), var, dst);
}
void color_parse_mem(const char *value, int value_len, const char *var,
char *dst)
{
const char *ptr = value;
int len = value_len;
int attr = -1;
int fg = -2;
int bg = -2;
if (!strncasecmp(value, "reset", len)) {
strcpy(dst, PERF_COLOR_RESET);
return;
}
/* [fg [bg]] [attr] */
while (len > 0) {
const char *word = ptr;
int val, wordlen = 0;
while (len > 0 && !isspace(word[wordlen])) {
wordlen++;
len--;
}
ptr = word + wordlen;
while (len > 0 && isspace(*ptr)) {
ptr++;
len--;
}
val = parse_color(word, wordlen);
if (val >= -1) {
if (fg == -2) {
fg = val;
continue;
}
if (bg == -2) {
bg = val;
continue;
}
goto bad;
}
val = parse_attr(word, wordlen);
if (val < 0 || attr != -1)
goto bad;
attr = val;
}
if (attr >= 0 || fg >= 0 || bg >= 0) {
int sep = 0;
*dst++ = '\033';
*dst++ = '[';
if (attr >= 0) {
*dst++ = '0' + attr;
sep++;
}
if (fg >= 0) {
if (sep++)
*dst++ = ';';
if (fg < 8) {
*dst++ = '3';
*dst++ = '0' + fg;
} else {
dst += sprintf(dst, "38;5;%d", fg);
}
}
if (bg >= 0) {
if (sep++)
*dst++ = ';';
if (bg < 8) {
*dst++ = '4';
*dst++ = '0' + bg;
} else {
dst += sprintf(dst, "48;5;%d", bg);
}
}
*dst++ = 'm';
}
*dst = 0;
return;
bad:
die("bad color value '%.*s' for variable '%s'", value_len, value, var);
}
int perf_config_colorbool(const char *var, const char *value, int stdout_is_tty)
{
if (value) {
if (!strcasecmp(value, "never"))
return 0;
if (!strcasecmp(value, "always"))
return 1;
if (!strcasecmp(value, "auto"))
goto auto_color;
}
/* Missing or explicit false to turn off colorization */
if (!perf_config_bool(var, value))
return 0;
/* any normal truth value defaults to 'auto' */
auto_color:
if (stdout_is_tty < 0)
stdout_is_tty = isatty(1);
if (stdout_is_tty || (pager_in_use() && pager_use_color)) {
char *term = getenv("TERM");
if (term && strcmp(term, "dumb"))
return 1;
}
return 0;
}
int perf_color_default_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "color.ui")) {
perf_use_color_default = perf_config_colorbool(var, value, -1);
return 0;
}
return perf_default_config(var, value, cb);
}
static int __color_vfprintf(FILE *fp, const char *color, const char *fmt,
va_list args, const char *trail)
{
int r = 0;
/*
* Auto-detect:
*/
if (perf_use_color_default < 0) {
if (isatty(1) || pager_in_use())
perf_use_color_default = 1;
else
perf_use_color_default = 0;
}
if (perf_use_color_default && *color)
r += fprintf(fp, "%s", color);
r += vfprintf(fp, fmt, args);
if (perf_use_color_default && *color)
r += fprintf(fp, "%s", PERF_COLOR_RESET);
if (trail)
r += fprintf(fp, "%s", trail);
return r;
}
int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args)
{
return __color_vfprintf(fp, color, fmt, args, NULL);
}
int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = color_vfprintf(fp, color, fmt, args);
va_end(args);
return r;
}
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = __color_vfprintf(fp, color, fmt, args, "\n");
va_end(args);
return r;
}
/*
* This function splits the buffer by newlines and colors the lines individually.
*
* Returns 0 on success.
*/
int color_fwrite_lines(FILE *fp, const char *color,
size_t count, const char *buf)
{
if (!*color)
return fwrite(buf, count, 1, fp) != 1;
while (count) {
char *p = memchr(buf, '\n', count);
if (p != buf && (fputs(color, fp) < 0 ||
fwrite(buf, p ? (size_t)(p - buf) : count, 1, fp) != 1 ||
fputs(PERF_COLOR_RESET, fp) < 0))
return -1;
if (!p)
return 0;
if (fputc('\n', fp) < 0)
return -1;
count -= p + 1 - buf;
buf = p + 1;
}
return 0;
}
const char *get_percent_color(double percent)
{
const char *color = PERF_COLOR_NORMAL;
/*
* We color high-overhead entries in red, mid-overhead
* entries in green - and keep the low overhead places
* normal:
*/
if (percent >= MIN_RED)
color = PERF_COLOR_RED;
else {
if (percent > MIN_GREEN)
color = PERF_COLOR_GREEN;
}
return color;
}
int percent_color_fprintf(FILE *fp, const char *fmt, double percent)
{
int r;
const char *color;
color = get_percent_color(percent);
r = color_fprintf(fp, color, fmt, percent);
return r;
}

View File

@@ -0,0 +1,42 @@
#ifndef COLOR_H
#define COLOR_H
/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
#define COLOR_MAXLEN 24
#define PERF_COLOR_NORMAL ""
#define PERF_COLOR_RESET "\033[m"
#define PERF_COLOR_BOLD "\033[1m"
#define PERF_COLOR_RED "\033[31m"
#define PERF_COLOR_GREEN "\033[32m"
#define PERF_COLOR_YELLOW "\033[33m"
#define PERF_COLOR_BLUE "\033[34m"
#define PERF_COLOR_MAGENTA "\033[35m"
#define PERF_COLOR_CYAN "\033[36m"
#define PERF_COLOR_BG_RED "\033[41m"
#define MIN_GREEN 0.5
#define MIN_RED 5.0
/*
* This variable stores the value of color.ui
*/
extern int perf_use_color_default;
/*
* Use this instead of perf_default_config if you need the value of color.ui.
*/
int perf_color_default_config(const char *var, const char *value, void *cb);
int perf_config_colorbool(const char *var, const char *value, int stdout_is_tty);
void color_parse(const char *value, const char *var, char *dst);
void color_parse_mem(const char *value, int len, const char *var, char *dst);
int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args);
int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
int percent_color_fprintf(FILE *fp, const char *fmt, double percent);
const char *get_percent_color(double percent);
#endif /* COLOR_H */

View File

@@ -0,0 +1,879 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
* Copyright (C) Johannes Schindelin, 2005
*
*/
#include "util.h"
#include "cache.h"
#include "exec_cmd.h"
#define MAXNAME (256)
static FILE *config_file;
static const char *config_file_name;
static int config_linenr;
static int config_file_eof;
const char *config_exclusive_filename = NULL;
static int get_next_char(void)
{
int c;
FILE *f;
c = '\n';
if ((f = config_file) != NULL) {
c = fgetc(f);
if (c == '\r') {
/* DOS like systems */
c = fgetc(f);
if (c != '\n') {
ungetc(c, f);
c = '\r';
}
}
if (c == '\n')
config_linenr++;
if (c == EOF) {
config_file_eof = 1;
c = '\n';
}
}
return c;
}
static char *parse_value(void)
{
static char value[1024];
int quote = 0, comment = 0, space = 0;
size_t len = 0;
for (;;) {
int c = get_next_char();
if (len >= sizeof(value) - 1)
return NULL;
if (c == '\n') {
if (quote)
return NULL;
value[len] = 0;
return value;
}
if (comment)
continue;
if (isspace(c) && !quote) {
space = 1;
continue;
}
if (!quote) {
if (c == ';' || c == '#') {
comment = 1;
continue;
}
}
if (space) {
if (len)
value[len++] = ' ';
space = 0;
}
if (c == '\\') {
c = get_next_char();
switch (c) {
case '\n':
continue;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'n':
c = '\n';
break;
/* Some characters escape as themselves */
case '\\': case '"':
break;
/* Reject unknown escape sequences */
default:
return NULL;
}
value[len++] = c;
continue;
}
if (c == '"') {
quote = 1-quote;
continue;
}
value[len++] = c;
}
}
static inline int iskeychar(int c)
{
return isalnum(c) || c == '-';
}
static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
{
int c;
char *value;
/* Get the full name */
for (;;) {
c = get_next_char();
if (config_file_eof)
break;
if (!iskeychar(c))
break;
name[len++] = tolower(c);
if (len >= MAXNAME)
return -1;
}
name[len] = 0;
while (c == ' ' || c == '\t')
c = get_next_char();
value = NULL;
if (c != '\n') {
if (c != '=')
return -1;
value = parse_value();
if (!value)
return -1;
}
return fn(name, value, data);
}
static int get_extended_base_var(char *name, int baselen, int c)
{
do {
if (c == '\n')
return -1;
c = get_next_char();
} while (isspace(c));
/* We require the format to be '[base "extension"]' */
if (c != '"')
return -1;
name[baselen++] = '.';
for (;;) {
int ch = get_next_char();
if (ch == '\n')
return -1;
if (ch == '"')
break;
if (ch == '\\') {
ch = get_next_char();
if (ch == '\n')
return -1;
}
name[baselen++] = ch;
if (baselen > MAXNAME / 2)
return -1;
}
/* Final ']' */
if (get_next_char() != ']')
return -1;
return baselen;
}
static int get_base_var(char *name)
{
int baselen = 0;
for (;;) {
int c = get_next_char();
if (config_file_eof)
return -1;
if (c == ']')
return baselen;
if (isspace(c))
return get_extended_base_var(name, baselen, c);
if (!iskeychar(c) && c != '.')
return -1;
if (baselen > MAXNAME / 2)
return -1;
name[baselen++] = tolower(c);
}
}
static int perf_parse_file(config_fn_t fn, void *data)
{
int comment = 0;
int baselen = 0;
static char var[MAXNAME];
/* U+FEFF Byte Order Mark in UTF8 */
static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
const unsigned char *bomptr = utf8_bom;
for (;;) {
int c = get_next_char();
if (bomptr && *bomptr) {
/* We are at the file beginning; skip UTF8-encoded BOM
* if present. Sane editors won't put this in on their
* own, but e.g. Windows Notepad will do it happily. */
if ((unsigned char) c == *bomptr) {
bomptr++;
continue;
} else {
/* Do not tolerate partial BOM. */
if (bomptr != utf8_bom)
break;
/* No BOM at file beginning. Cool. */
bomptr = NULL;
}
}
if (c == '\n') {
if (config_file_eof)
return 0;
comment = 0;
continue;
}
if (comment || isspace(c))
continue;
if (c == '#' || c == ';') {
comment = 1;
continue;
}
if (c == '[') {
baselen = get_base_var(var);
if (baselen <= 0)
break;
var[baselen++] = '.';
var[baselen] = 0;
continue;
}
if (!isalpha(c))
break;
var[baselen] = tolower(c);
if (get_value(fn, data, var, baselen+1) < 0)
break;
}
die("bad config file line %d in %s", config_linenr, config_file_name);
}
static int parse_unit_factor(const char *end, unsigned long *val)
{
if (!*end)
return 1;
else if (!strcasecmp(end, "k")) {
*val *= 1024;
return 1;
}
else if (!strcasecmp(end, "m")) {
*val *= 1024 * 1024;
return 1;
}
else if (!strcasecmp(end, "g")) {
*val *= 1024 * 1024 * 1024;
return 1;
}
return 0;
}
static int perf_parse_long(const char *value, long *ret)
{
if (value && *value) {
char *end;
long val = strtol(value, &end, 0);
unsigned long factor = 1;
if (!parse_unit_factor(end, &factor))
return 0;
*ret = val * factor;
return 1;
}
return 0;
}
int perf_parse_ulong(const char *value, unsigned long *ret)
{
if (value && *value) {
char *end;
unsigned long val = strtoul(value, &end, 0);
if (!parse_unit_factor(end, &val))
return 0;
*ret = val;
return 1;
}
return 0;
}
static void die_bad_config(const char *name)
{
if (config_file_name)
die("bad config value for '%s' in %s", name, config_file_name);
die("bad config value for '%s'", name);
}
int perf_config_int(const char *name, const char *value)
{
long ret = 0;
if (!perf_parse_long(value, &ret))
die_bad_config(name);
return ret;
}
unsigned long perf_config_ulong(const char *name, const char *value)
{
unsigned long ret;
if (!perf_parse_ulong(value, &ret))
die_bad_config(name);
return ret;
}
int perf_config_bool_or_int(const char *name, const char *value, int *is_bool)
{
*is_bool = 1;
if (!value)
return 1;
if (!*value)
return 0;
if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
return 1;
if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
return 0;
*is_bool = 0;
return perf_config_int(name, value);
}
int perf_config_bool(const char *name, const char *value)
{
int discard;
return !!perf_config_bool_or_int(name, value, &discard);
}
int perf_config_string(const char **dest, const char *var, const char *value)
{
if (!value)
return config_error_nonbool(var);
*dest = strdup(value);
return 0;
}
static int perf_default_core_config(const char *var __used, const char *value __used)
{
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
int perf_default_config(const char *var, const char *value, void *dummy __used)
{
if (!prefixcmp(var, "core."))
return perf_default_core_config(var, value);
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
int perf_config_from_file(config_fn_t fn, const char *filename, void *data)
{
int ret;
FILE *f = fopen(filename, "r");
ret = -1;
if (f) {
config_file = f;
config_file_name = filename;
config_linenr = 1;
config_file_eof = 0;
ret = perf_parse_file(fn, data);
fclose(f);
config_file_name = NULL;
}
return ret;
}
const char *perf_etc_perfconfig(void)
{
static const char *system_wide;
if (!system_wide)
system_wide = system_path(ETC_PERFCONFIG);
return system_wide;
}
static int perf_env_bool(const char *k, int def)
{
const char *v = getenv(k);
return v ? perf_config_bool(k, v) : def;
}
int perf_config_system(void)
{
return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0);
}
int perf_config_global(void)
{
return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0);
}
int perf_config(config_fn_t fn, void *data)
{
int ret = 0, found = 0;
char *repo_config = NULL;
const char *home = NULL;
/* Setting $PERF_CONFIG makes perf read _only_ the given config file. */
if (config_exclusive_filename)
return perf_config_from_file(fn, config_exclusive_filename, data);
if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) {
ret += perf_config_from_file(fn, perf_etc_perfconfig(),
data);
found += 1;
}
home = getenv("HOME");
if (perf_config_global() && home) {
char *user_config = strdup(mkpath("%s/.perfconfig", home));
if (!access(user_config, R_OK)) {
ret += perf_config_from_file(fn, user_config, data);
found += 1;
}
free(user_config);
}
repo_config = perf_pathdup("config");
if (!access(repo_config, R_OK)) {
ret += perf_config_from_file(fn, repo_config, data);
found += 1;
}
free(repo_config);
if (found == 0)
return -1;
return ret;
}
/*
* Find all the stuff for perf_config_set() below.
*/
#define MAX_MATCHES 512
static struct {
int baselen;
char* key;
int do_not_match;
regex_t* value_regex;
int multi_replace;
size_t offset[MAX_MATCHES];
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
int seen;
} store;
static int matches(const char* key, const char* value)
{
return !strcmp(key, store.key) &&
(store.value_regex == NULL ||
(store.do_not_match ^
!regexec(store.value_regex, value, 0, NULL, 0)));
}
static int store_aux(const char* key, const char* value, void *cb __used)
{
int section_len;
const char *ep;
switch (store.state) {
case KEY_SEEN:
if (matches(key, value)) {
if (store.seen == 1 && store.multi_replace == 0) {
warning("%s has multiple values", key);
} else if (store.seen >= MAX_MATCHES) {
error("too many matches for %s", key);
return 1;
}
store.offset[store.seen] = ftell(config_file);
store.seen++;
}
break;
case SECTION_SEEN:
/*
* What we are looking for is in store.key (both
* section and var), and its section part is baselen
* long. We found key (again, both section and var).
* We would want to know if this key is in the same
* section as what we are looking for. We already
* know we are in the same section as what should
* hold store.key.
*/
ep = strrchr(key, '.');
section_len = ep - key;
if ((section_len != store.baselen) ||
memcmp(key, store.key, section_len+1)) {
store.state = SECTION_END_SEEN;
break;
}
/*
* Do not increment matches: this is no match, but we
* just made sure we are in the desired section.
*/
store.offset[store.seen] = ftell(config_file);
/* fallthru */
case SECTION_END_SEEN:
case START:
if (matches(key, value)) {
store.offset[store.seen] = ftell(config_file);
store.state = KEY_SEEN;
store.seen++;
} else {
if (strrchr(key, '.') - key == store.baselen &&
!strncmp(key, store.key, store.baselen)) {
store.state = SECTION_SEEN;
store.offset[store.seen] = ftell(config_file);
}
}
default:
break;
}
return 0;
}
static int store_write_section(int fd, const char* key)
{
const char *dot;
int i, success;
struct strbuf sb = STRBUF_INIT;
dot = memchr(key, '.', store.baselen);
if (dot) {
strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
for (i = dot - key + 1; i < store.baselen; i++) {
if (key[i] == '"' || key[i] == '\\')
strbuf_addch(&sb, '\\');
strbuf_addch(&sb, key[i]);
}
strbuf_addstr(&sb, "\"]\n");
} else {
strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
}
success = (write_in_full(fd, sb.buf, sb.len) == (ssize_t)sb.len);
strbuf_release(&sb);
return success;
}
static int store_write_pair(int fd, const char* key, const char* value)
{
int i, success;
int length = strlen(key + store.baselen + 1);
const char *quote = "";
struct strbuf sb = STRBUF_INIT;
/*
* Check to see if the value needs to be surrounded with a dq pair.
* Note that problematic characters are always backslash-quoted; this
* check is about not losing leading or trailing SP and strings that
* follow beginning-of-comment characters (i.e. ';' and '#') by the
* configuration parser.
*/
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
strbuf_addf(&sb, "\t%.*s = %s",
length, key + store.baselen + 1, quote);
for (i = 0; value[i]; i++)
switch (value[i]) {
case '\n':
strbuf_addstr(&sb, "\\n");
break;
case '\t':
strbuf_addstr(&sb, "\\t");
break;
case '"':
case '\\':
strbuf_addch(&sb, '\\');
default:
strbuf_addch(&sb, value[i]);
break;
}
strbuf_addf(&sb, "%s\n", quote);
success = (write_in_full(fd, sb.buf, sb.len) == (ssize_t)sb.len);
strbuf_release(&sb);
return success;
}
static ssize_t find_beginning_of_line(const char* contents, size_t size,
size_t offset_, int* found_bracket)
{
size_t equal_offset = size, bracket_offset = size;
ssize_t offset;
contline:
for (offset = offset_-2; offset > 0
&& contents[offset] != '\n'; offset--)
switch (contents[offset]) {
case '=': equal_offset = offset; break;
case ']': bracket_offset = offset; break;
default: break;
}
if (offset > 0 && contents[offset-1] == '\\') {
offset_ = offset;
goto contline;
}
if (bracket_offset < equal_offset) {
*found_bracket = 1;
offset = bracket_offset+1;
} else
offset++;
return offset;
}
int perf_config_set(const char* key, const char* value)
{
return perf_config_set_multivar(key, value, NULL, 0);
}
/*
* If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match.
* if multi_replace==0, nothing, or only one matching key/value is replaced,
* else all matching key/values (regardless how many) are removed,
* before the new pair is written.
*
* Returns 0 on success.
*
* This function does this:
*
* - it locks the config file by creating ".perf/config.lock"
*
* - it then parses the config using store_aux() as validator to find
* the position on the key/value pair to replace. If it is to be unset,
* it must be found exactly once.
*
* - the config file is mmap()ed and the part before the match (if any) is
* written to the lock file, then the changed part and the rest.
*
* - the config file is removed and the lock file rename()d to it.
*
*/
int perf_config_set_multivar(const char* key, const char* value,
const char* value_regex, int multi_replace)
{
int i, dot;
int fd = -1, in_fd;
int ret = 0;
char* config_filename;
const char* last_dot = strrchr(key, '.');
if (config_exclusive_filename)
config_filename = strdup(config_exclusive_filename);
else
config_filename = perf_pathdup("config");
/*
* Since "key" actually contains the section name and the real
* key name separated by a dot, we have to know where the dot is.
*/
if (last_dot == NULL) {
error("key does not contain a section: %s", key);
ret = 2;
goto out_free;
}
store.baselen = last_dot - key;
store.multi_replace = multi_replace;
/*
* Validate the key and while at it, lower case it for matching.
*/
store.key = malloc(strlen(key) + 1);
dot = 0;
for (i = 0; key[i]; i++) {
unsigned char c = key[i];
if (c == '.')
dot = 1;
/* Leave the extended basename untouched.. */
if (!dot || i > store.baselen) {
if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
error("invalid key: %s", key);
free(store.key);
ret = 1;
goto out_free;
}
c = tolower(c);
} else if (c == '\n') {
error("invalid key (newline): %s", key);
free(store.key);
ret = 1;
goto out_free;
}
store.key[i] = c;
}
store.key[i] = 0;
/*
* If .perf/config does not exist yet, write a minimal version.
*/
in_fd = open(config_filename, O_RDONLY);
if ( in_fd < 0 ) {
free(store.key);
if ( ENOENT != errno ) {
error("opening %s: %s", config_filename,
strerror(errno));
ret = 3; /* same as "invalid config file" */
goto out_free;
}
/* if nothing to unset, error out */
if (value == NULL) {
ret = 5;
goto out_free;
}
store.key = (char*)key;
if (!store_write_section(fd, key) ||
!store_write_pair(fd, key, value))
goto write_err_out;
} else {
struct stat st;
char *contents;
ssize_t contents_sz, copy_begin, copy_end;
int new_line = 0;
if (value_regex == NULL)
store.value_regex = NULL;
else {
if (value_regex[0] == '!') {
store.do_not_match = 1;
value_regex++;
} else
store.do_not_match = 0;
store.value_regex = (regex_t*)malloc(sizeof(regex_t));
if (regcomp(store.value_regex, value_regex,
REG_EXTENDED)) {
error("invalid pattern: %s", value_regex);
free(store.value_regex);
ret = 6;
goto out_free;
}
}
store.offset[0] = 0;
store.state = START;
store.seen = 0;
/*
* After this, store.offset will contain the *end* offset
* of the last match, or remain at 0 if no match was found.
* As a side effect, we make sure to transform only a valid
* existing config file.
*/
if (perf_config_from_file(store_aux, config_filename, NULL)) {
error("invalid config file %s", config_filename);
free(store.key);
if (store.value_regex != NULL) {
regfree(store.value_regex);
free(store.value_regex);
}
ret = 3;
goto out_free;
}
free(store.key);
if (store.value_regex != NULL) {
regfree(store.value_regex);
free(store.value_regex);
}
/* if nothing to unset, or too many matches, error out */
if ((store.seen == 0 && value == NULL) ||
(store.seen > 1 && multi_replace == 0)) {
ret = 5;
goto out_free;
}
fstat(in_fd, &st);
contents_sz = xsize_t(st.st_size);
contents = mmap(NULL, contents_sz, PROT_READ,
MAP_PRIVATE, in_fd, 0);
close(in_fd);
if (store.seen == 0)
store.seen = 1;
for (i = 0, copy_begin = 0; i < store.seen; i++) {
if (store.offset[i] == 0) {
store.offset[i] = copy_end = contents_sz;
} else if (store.state != KEY_SEEN) {
copy_end = store.offset[i];
} else
copy_end = find_beginning_of_line(
contents, contents_sz,
store.offset[i]-2, &new_line);
if (copy_end > 0 && contents[copy_end-1] != '\n')
new_line = 1;
/* write the first part of the config */
if (copy_end > copy_begin) {
if (write_in_full(fd, contents + copy_begin,
copy_end - copy_begin) <
copy_end - copy_begin)
goto write_err_out;
if (new_line &&
write_in_full(fd, "\n", 1) != 1)
goto write_err_out;
}
copy_begin = store.offset[i];
}
/* write the pair (value == NULL means unset) */
if (value != NULL) {
if (store.state == START) {
if (!store_write_section(fd, key))
goto write_err_out;
}
if (!store_write_pair(fd, key, value))
goto write_err_out;
}
/* write the rest of the config */
if (copy_begin < contents_sz)
if (write_in_full(fd, contents + copy_begin,
contents_sz - copy_begin) <
contents_sz - copy_begin)
goto write_err_out;
munmap(contents, contents_sz);
}
ret = 0;
out_free:
free(config_filename);
return ret;
write_err_out:
goto out_free;
}
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").
*/
int config_error_nonbool(const char *var)
{
return error("Missing value for '%s'", var);
}

View File

@@ -0,0 +1,31 @@
/*
* Sane locale-independent, ASCII ctype.
*
* No surprises, and works with signed and unsigned chars.
*/
#include "cache.h"
enum {
S = GIT_SPACE,
A = GIT_ALPHA,
D = GIT_DIGIT,
G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | * */
P = GIT_PRINT_EXTRA, /* printable - alpha - digit - glob - regex */
PS = GIT_SPACE | GIT_PRINT_EXTRA,
};
unsigned char sane_ctype[256] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */
PS,P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
A, A, A, A, A, A, A, A, A, A, A, G, G, P, R, P, /* 80.. 95 */
P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
A, A, A, A, A, A, A, A, A, A, A, R, R, P, P, 0, /* 112..127 */
/* Nothing in the 128.. range */
};

View File

@@ -0,0 +1,95 @@
/* For general debugging purposes */
#include "../perf.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "color.h"
#include "event.h"
#include "debug.h"
int verbose = 0;
int dump_trace = 0;
int eprintf(const char *fmt, ...)
{
va_list args;
int ret = 0;
if (verbose) {
va_start(args, fmt);
ret = vfprintf(stderr, fmt, args);
va_end(args);
}
return ret;
}
int dump_printf(const char *fmt, ...)
{
va_list args;
int ret = 0;
if (dump_trace) {
va_start(args, fmt);
ret = vprintf(fmt, args);
va_end(args);
}
return ret;
}
static int dump_printf_color(const char *fmt, const char *color, ...)
{
va_list args;
int ret = 0;
if (dump_trace) {
va_start(args, color);
ret = color_vfprintf(stdout, color, fmt, args);
va_end(args);
}
return ret;
}
void trace_event(event_t *event)
{
unsigned char *raw_event = (void *)event;
const char *color = PERF_COLOR_BLUE;
int i, j;
if (!dump_trace)
return;
dump_printf(".");
dump_printf_color("\n. ... raw event: size %d bytes\n", color,
event->header.size);
for (i = 0; i < event->header.size; i++) {
if ((i & 15) == 0) {
dump_printf(".");
dump_printf_color(" %04x: ", color, i);
}
dump_printf_color(" %02x", color, raw_event[i]);
if (((i & 15) == 15) || i == event->header.size-1) {
dump_printf_color(" ", color);
for (j = 0; j < 15-(i & 15); j++)
dump_printf_color(" ", color);
for (j = 0; j < (i & 15); j++) {
if (isprint(raw_event[i-15+j]))
dump_printf_color("%c", color,
raw_event[i-15+j]);
else
dump_printf_color(".", color);
}
dump_printf_color("\n", color);
}
}
dump_printf(".\n");
}

View File

@@ -0,0 +1,8 @@
/* For debugging general purposes */
extern int verbose;
extern int dump_trace;
int eprintf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void trace_event(event_t *event);

View File

@@ -0,0 +1,9 @@
/*
* We put all the perf config variables in this same object
* file, so that programs can link against the config parser
* without having to link against all the rest of perf.
*/
#include "cache.h"
const char *pager_program;
int pager_use_color = 1;

View File

@@ -0,0 +1,104 @@
#ifndef __PERF_RECORD_H
#define __PERF_RECORD_H
#include "../perf.h"
#include "util.h"
#include <linux/list.h>
enum {
SHOW_KERNEL = 1,
SHOW_USER = 2,
SHOW_HV = 4,
};
/*
* PERF_SAMPLE_IP | PERF_SAMPLE_TID | *
*/
struct ip_event {
struct perf_event_header header;
u64 ip;
u32 pid, tid;
unsigned char __more_data[];
};
struct mmap_event {
struct perf_event_header header;
u32 pid, tid;
u64 start;
u64 len;
u64 pgoff;
char filename[PATH_MAX];
};
struct comm_event {
struct perf_event_header header;
u32 pid, tid;
char comm[16];
};
struct fork_event {
struct perf_event_header header;
u32 pid, ppid;
u32 tid, ptid;
u64 time;
};
struct lost_event {
struct perf_event_header header;
u64 id;
u64 lost;
};
/*
* PERF_FORMAT_ENABLED | PERF_FORMAT_RUNNING | PERF_FORMAT_ID
*/
struct read_event {
struct perf_event_header header;
u32 pid, tid;
u64 value;
u64 time_enabled;
u64 time_running;
u64 id;
};
struct sample_event{
struct perf_event_header header;
u64 array[];
};
typedef union event_union {
struct perf_event_header header;
struct ip_event ip;
struct mmap_event mmap;
struct comm_event comm;
struct fork_event fork;
struct lost_event lost;
struct read_event read;
struct sample_event sample;
} event_t;
struct map {
struct list_head node;
u64 start;
u64 end;
u64 pgoff;
u64 (*map_ip)(struct map *, u64);
struct dso *dso;
};
static inline u64 map__map_ip(struct map *map, u64 ip)
{
return ip - map->start + map->pgoff;
}
static inline u64 vdso__map_ip(struct map *map __used, u64 ip)
{
return ip;
}
struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen);
struct map *map__clone(struct map *self);
int map__overlap(struct map *l, struct map *r);
size_t map__fprintf(struct map *self, FILE *fp);
#endif

View File

@@ -0,0 +1,167 @@
#include "cache.h"
#include "exec_cmd.h"
#include "quote.h"
#include <string.h>
#define MAX_ARGS 32
static const char *argv_exec_path;
static const char *argv0_path;
const char *system_path(const char *path)
{
#ifdef RUNTIME_PREFIX
static const char *prefix;
#else
static const char *prefix = PREFIX;
#endif
struct strbuf d = STRBUF_INIT;
if (is_absolute_path(path))
return path;
#ifdef RUNTIME_PREFIX
assert(argv0_path);
assert(is_absolute_path(argv0_path));
if (!prefix &&
!(prefix = strip_path_suffix(argv0_path, PERF_EXEC_PATH)) &&
!(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
!(prefix = strip_path_suffix(argv0_path, "perf"))) {
prefix = PREFIX;
fprintf(stderr, "RUNTIME_PREFIX requested, "
"but prefix computation failed. "
"Using static fallback '%s'.\n", prefix);
}
#endif
strbuf_addf(&d, "%s/%s", prefix, path);
path = strbuf_detach(&d, NULL);
return path;
}
const char *perf_extract_argv0_path(const char *argv0)
{
const char *slash;
if (!argv0 || !*argv0)
return NULL;
slash = argv0 + strlen(argv0);
while (argv0 <= slash && !is_dir_sep(*slash))
slash--;
if (slash >= argv0) {
argv0_path = xstrndup(argv0, slash - argv0);
return slash + 1;
}
return argv0;
}
void perf_set_argv_exec_path(const char *exec_path)
{
argv_exec_path = exec_path;
/*
* Propagate this setting to external programs.
*/
setenv(EXEC_PATH_ENVIRONMENT, exec_path, 1);
}
/* Returns the highest-priority, location to look for perf programs. */
const char *perf_exec_path(void)
{
const char *env;
if (argv_exec_path)
return argv_exec_path;
env = getenv(EXEC_PATH_ENVIRONMENT);
if (env && *env) {
return env;
}
return system_path(PERF_EXEC_PATH);
}
static void add_path(struct strbuf *out, const char *path)
{
if (path && *path) {
if (is_absolute_path(path))
strbuf_addstr(out, path);
else
strbuf_addstr(out, make_nonrelative_path(path));
strbuf_addch(out, PATH_SEP);
}
}
void setup_path(void)
{
const char *old_path = getenv("PATH");
struct strbuf new_path = STRBUF_INIT;
add_path(&new_path, perf_exec_path());
add_path(&new_path, argv0_path);
if (old_path)
strbuf_addstr(&new_path, old_path);
else
strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin");
setenv("PATH", new_path.buf, 1);
strbuf_release(&new_path);
}
const char **prepare_perf_cmd(const char **argv)
{
int argc;
const char **nargv;
for (argc = 0; argv[argc]; argc++)
; /* just counting */
nargv = malloc(sizeof(*nargv) * (argc + 2));
nargv[0] = "perf";
for (argc = 0; argv[argc]; argc++)
nargv[argc + 1] = argv[argc];
nargv[argc + 1] = NULL;
return nargv;
}
int execv_perf_cmd(const char **argv) {
const char **nargv = prepare_perf_cmd(argv);
/* execvp() can only ever return if it fails */
execvp("perf", (char **)nargv);
free(nargv);
return -1;
}
int execl_perf_cmd(const char *cmd,...)
{
int argc;
const char *argv[MAX_ARGS + 1];
const char *arg;
va_list param;
va_start(param, cmd);
argv[0] = cmd;
argc = 1;
while (argc < MAX_ARGS) {
arg = argv[argc++] = va_arg(param, char *);
if (!arg)
break;
}
va_end(param);
if (MAX_ARGS <= argc)
return error("too many args to run %s", cmd);
argv[argc] = NULL;
return execv_perf_cmd(argv);
}

View File

@@ -0,0 +1,13 @@
#ifndef PERF_EXEC_CMD_H
#define PERF_EXEC_CMD_H
extern void perf_set_argv_exec_path(const char *exec_path);
extern const char *perf_extract_argv0_path(const char *path);
extern const char *perf_exec_path(void);
extern void setup_path(void);
extern const char **prepare_perf_cmd(const char **argv);
extern int execv_perf_cmd(const char **argv); /* NULL terminated */
extern int execl_perf_cmd(const char *cmd, ...);
extern const char *system_path(const char *path);
#endif /* PERF_EXEC_CMD_H */

View File

@@ -0,0 +1,24 @@
#!/bin/sh
echo "/* Automatically generated by $0 */
struct cmdname_help
{
char name[16];
char help[80];
};
static struct cmdname_help common_cmds[] = {"
sed -n -e 's/^perf-\([^ ]*\)[ ].* common.*/\1/p' command-list.txt |
sort |
while read cmd
do
sed -n '
/^NAME/,/perf-'"$cmd"'/H
${
x
s/.*perf-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/
p
}' "Documentation/perf-$cmd.txt"
done
echo "};"

View File

@@ -0,0 +1,339 @@
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "util.h"
#include "header.h"
/*
* Create new perf.data header attribute:
*/
struct perf_header_attr *perf_header_attr__new(struct perf_event_attr *attr)
{
struct perf_header_attr *self = malloc(sizeof(*self));
if (!self)
die("nomem");
self->attr = *attr;
self->ids = 0;
self->size = 1;
self->id = malloc(sizeof(u64));
if (!self->id)
die("nomem");
return self;
}
void perf_header_attr__add_id(struct perf_header_attr *self, u64 id)
{
int pos = self->ids;
self->ids++;
if (self->ids > self->size) {
self->size *= 2;
self->id = realloc(self->id, self->size * sizeof(u64));
if (!self->id)
die("nomem");
}
self->id[pos] = id;
}
/*
* Create new perf.data header:
*/
struct perf_header *perf_header__new(void)
{
struct perf_header *self = malloc(sizeof(*self));
if (!self)
die("nomem");
self->frozen = 0;
self->attrs = 0;
self->size = 1;
self->attr = malloc(sizeof(void *));
if (!self->attr)
die("nomem");
self->data_offset = 0;
self->data_size = 0;
return self;
}
void perf_header__add_attr(struct perf_header *self,
struct perf_header_attr *attr)
{
int pos = self->attrs;
if (self->frozen)
die("frozen");
self->attrs++;
if (self->attrs > self->size) {
self->size *= 2;
self->attr = realloc(self->attr, self->size * sizeof(void *));
if (!self->attr)
die("nomem");
}
self->attr[pos] = attr;
}
#define MAX_EVENT_NAME 64
struct perf_trace_event_type {
u64 event_id;
char name[MAX_EVENT_NAME];
};
static int event_count;
static struct perf_trace_event_type *events;
void perf_header__push_event(u64 id, const char *name)
{
if (strlen(name) > MAX_EVENT_NAME)
printf("Event %s will be truncated\n", name);
if (!events) {
events = malloc(sizeof(struct perf_trace_event_type));
if (!events)
die("nomem");
} else {
events = realloc(events, (event_count + 1) * sizeof(struct perf_trace_event_type));
if (!events)
die("nomem");
}
memset(&events[event_count], 0, sizeof(struct perf_trace_event_type));
events[event_count].event_id = id;
strncpy(events[event_count].name, name, MAX_EVENT_NAME - 1);
event_count++;
}
char *perf_header__find_event(u64 id)
{
int i;
for (i = 0 ; i < event_count; i++) {
if (events[i].event_id == id)
return events[i].name;
}
return NULL;
}
static const char *__perf_magic = "PERFFILE";
#define PERF_MAGIC (*(u64 *)__perf_magic)
struct perf_file_section {
u64 offset;
u64 size;
};
struct perf_file_attr {
struct perf_event_attr attr;
struct perf_file_section ids;
};
struct perf_file_header {
u64 magic;
u64 size;
u64 attr_size;
struct perf_file_section attrs;
struct perf_file_section data;
struct perf_file_section event_types;
};
static void do_write(int fd, void *buf, size_t size)
{
while (size) {
int ret = write(fd, buf, size);
if (ret < 0)
die("failed to write");
size -= ret;
buf += ret;
}
}
void perf_header__write(struct perf_header *self, int fd)
{
struct perf_file_header f_header;
struct perf_file_attr f_attr;
struct perf_header_attr *attr;
int i;
lseek(fd, sizeof(f_header), SEEK_SET);
for (i = 0; i < self->attrs; i++) {
attr = self->attr[i];
attr->id_offset = lseek(fd, 0, SEEK_CUR);
do_write(fd, attr->id, attr->ids * sizeof(u64));
}
self->attr_offset = lseek(fd, 0, SEEK_CUR);
for (i = 0; i < self->attrs; i++) {
attr = self->attr[i];
f_attr = (struct perf_file_attr){
.attr = attr->attr,
.ids = {
.offset = attr->id_offset,
.size = attr->ids * sizeof(u64),
}
};
do_write(fd, &f_attr, sizeof(f_attr));
}
self->event_offset = lseek(fd, 0, SEEK_CUR);
self->event_size = event_count * sizeof(struct perf_trace_event_type);
if (events)
do_write(fd, events, self->event_size);
self->data_offset = lseek(fd, 0, SEEK_CUR);
f_header = (struct perf_file_header){
.magic = PERF_MAGIC,
.size = sizeof(f_header),
.attr_size = sizeof(f_attr),
.attrs = {
.offset = self->attr_offset,
.size = self->attrs * sizeof(f_attr),
},
.data = {
.offset = self->data_offset,
.size = self->data_size,
},
.event_types = {
.offset = self->event_offset,
.size = self->event_size,
},
};
lseek(fd, 0, SEEK_SET);
do_write(fd, &f_header, sizeof(f_header));
lseek(fd, self->data_offset + self->data_size, SEEK_SET);
self->frozen = 1;
}
static void do_read(int fd, void *buf, size_t size)
{
while (size) {
int ret = read(fd, buf, size);
if (ret < 0)
die("failed to read");
if (ret == 0)
die("failed to read: missing data");
size -= ret;
buf += ret;
}
}
struct perf_header *perf_header__read(int fd)
{
struct perf_header *self = perf_header__new();
struct perf_file_header f_header;
struct perf_file_attr f_attr;
u64 f_id;
int nr_attrs, nr_ids, i, j;
lseek(fd, 0, SEEK_SET);
do_read(fd, &f_header, sizeof(f_header));
if (f_header.magic != PERF_MAGIC ||
f_header.size != sizeof(f_header) ||
f_header.attr_size != sizeof(f_attr))
die("incompatible file format");
nr_attrs = f_header.attrs.size / sizeof(f_attr);
lseek(fd, f_header.attrs.offset, SEEK_SET);
for (i = 0; i < nr_attrs; i++) {
struct perf_header_attr *attr;
off_t tmp;
do_read(fd, &f_attr, sizeof(f_attr));
tmp = lseek(fd, 0, SEEK_CUR);
attr = perf_header_attr__new(&f_attr.attr);
nr_ids = f_attr.ids.size / sizeof(u64);
lseek(fd, f_attr.ids.offset, SEEK_SET);
for (j = 0; j < nr_ids; j++) {
do_read(fd, &f_id, sizeof(f_id));
perf_header_attr__add_id(attr, f_id);
}
perf_header__add_attr(self, attr);
lseek(fd, tmp, SEEK_SET);
}
if (f_header.event_types.size) {
lseek(fd, f_header.event_types.offset, SEEK_SET);
events = malloc(f_header.event_types.size);
if (!events)
die("nomem");
do_read(fd, events, f_header.event_types.size);
event_count = f_header.event_types.size / sizeof(struct perf_trace_event_type);
}
self->event_offset = f_header.event_types.offset;
self->event_size = f_header.event_types.size;
self->data_offset = f_header.data.offset;
self->data_size = f_header.data.size;
lseek(fd, self->data_offset, SEEK_SET);
self->frozen = 1;
return self;
}
u64 perf_header__sample_type(struct perf_header *header)
{
u64 type = 0;
int i;
for (i = 0; i < header->attrs; i++) {
struct perf_header_attr *attr = header->attr[i];
if (!type)
type = attr->attr.sample_type;
else if (type != attr->attr.sample_type)
die("non matching sample_type");
}
return type;
}
struct perf_event_attr *
perf_header__find_attr(u64 id, struct perf_header *header)
{
int i;
for (i = 0; i < header->attrs; i++) {
struct perf_header_attr *attr = header->attr[i];
int j;
for (j = 0; j < attr->ids; j++) {
if (attr->id[j] == id)
return &attr->attr;
}
}
return NULL;
}

View File

@@ -0,0 +1,47 @@
#ifndef _PERF_HEADER_H
#define _PERF_HEADER_H
#include "../../../include/linux/perf_event.h"
#include <sys/types.h>
#include "types.h"
struct perf_header_attr {
struct perf_event_attr attr;
int ids, size;
u64 *id;
off_t id_offset;
};
struct perf_header {
int frozen;
int attrs, size;
struct perf_header_attr **attr;
s64 attr_offset;
u64 data_offset;
u64 data_size;
u64 event_offset;
u64 event_size;
};
struct perf_header *perf_header__read(int fd);
void perf_header__write(struct perf_header *self, int fd);
void perf_header__add_attr(struct perf_header *self,
struct perf_header_attr *attr);
void perf_header__push_event(u64 id, const char *name);
char *perf_header__find_event(u64 id);
struct perf_header_attr *
perf_header_attr__new(struct perf_event_attr *attr);
void perf_header_attr__add_id(struct perf_header_attr *self, u64 id);
u64 perf_header__sample_type(struct perf_header *header);
struct perf_event_attr *
perf_header__find_attr(u64 id, struct perf_header *header);
struct perf_header *perf_header__new(void);
#endif /* _PERF_HEADER_H */

View File

@@ -0,0 +1,356 @@
#include "cache.h"
#include "../builtin.h"
#include "exec_cmd.h"
#include "levenshtein.h"
#include "help.h"
/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
{
char *col_string = getenv("COLUMNS");
int n_cols;
if (col_string && (n_cols = atoi(col_string)) > 0)
return n_cols;
#ifdef TIOCGWINSZ
{
struct winsize ws;
if (!ioctl(1, TIOCGWINSZ, &ws)) {
if (ws.ws_col)
return ws.ws_col;
}
}
#endif
return 80;
}
void add_cmdname(struct cmdnames *cmds, const char *name, size_t len)
{
struct cmdname *ent = malloc(sizeof(*ent) + len + 1);
ent->len = len;
memcpy(ent->name, name, len);
ent->name[len] = 0;
ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
cmds->names[cmds->cnt++] = ent;
}
static void clean_cmdnames(struct cmdnames *cmds)
{
unsigned int i;
for (i = 0; i < cmds->cnt; ++i)
free(cmds->names[i]);
free(cmds->names);
cmds->cnt = 0;
cmds->alloc = 0;
}
static int cmdname_compare(const void *a_, const void *b_)
{
struct cmdname *a = *(struct cmdname **)a_;
struct cmdname *b = *(struct cmdname **)b_;
return strcmp(a->name, b->name);
}
static void uniq(struct cmdnames *cmds)
{
unsigned int i, j;
if (!cmds->cnt)
return;
for (i = j = 1; i < cmds->cnt; i++)
if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
cmds->names[j++] = cmds->names[i];
cmds->cnt = j;
}
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
{
size_t ci, cj, ei;
int cmp;
ci = cj = ei = 0;
while (ci < cmds->cnt && ei < excludes->cnt) {
cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
if (cmp < 0)
cmds->names[cj++] = cmds->names[ci++];
else if (cmp == 0)
ci++, ei++;
else if (cmp > 0)
ei++;
}
while (ci < cmds->cnt)
cmds->names[cj++] = cmds->names[ci++];
cmds->cnt = cj;
}
static void pretty_print_string_list(struct cmdnames *cmds, int longest)
{
int cols = 1, rows;
int space = longest + 1; /* min 1 SP between words */
int max_cols = term_columns() - 1; /* don't print *on* the edge */
int i, j;
if (space < max_cols)
cols = max_cols / space;
rows = (cmds->cnt + cols - 1) / cols;
for (i = 0; i < rows; i++) {
printf(" ");
for (j = 0; j < cols; j++) {
unsigned int n = j * rows + i;
unsigned int size = space;
if (n >= cmds->cnt)
break;
if (j == cols-1 || n + rows >= cmds->cnt)
size = 1;
printf("%-*s", size, cmds->names[n]->name);
}
putchar('\n');
}
}
static int is_executable(const char *name)
{
struct stat st;
if (stat(name, &st) || /* stat, not lstat */
!S_ISREG(st.st_mode))
return 0;
return st.st_mode & S_IXUSR;
}
static void list_commands_in_dir(struct cmdnames *cmds,
const char *path,
const char *prefix)
{
int prefix_len;
DIR *dir = opendir(path);
struct dirent *de;
struct strbuf buf = STRBUF_INIT;
int len;
if (!dir)
return;
if (!prefix)
prefix = "perf-";
prefix_len = strlen(prefix);
strbuf_addf(&buf, "%s/", path);
len = buf.len;
while ((de = readdir(dir)) != NULL) {
int entlen;
if (prefixcmp(de->d_name, prefix))
continue;
strbuf_setlen(&buf, len);
strbuf_addstr(&buf, de->d_name);
if (!is_executable(buf.buf))
continue;
entlen = strlen(de->d_name) - prefix_len;
if (has_extension(de->d_name, ".exe"))
entlen -= 4;
add_cmdname(cmds, de->d_name + prefix_len, entlen);
}
closedir(dir);
strbuf_release(&buf);
}
void load_command_list(const char *prefix,
struct cmdnames *main_cmds,
struct cmdnames *other_cmds)
{
const char *env_path = getenv("PATH");
const char *exec_path = perf_exec_path();
if (exec_path) {
list_commands_in_dir(main_cmds, exec_path, prefix);
qsort(main_cmds->names, main_cmds->cnt,
sizeof(*main_cmds->names), cmdname_compare);
uniq(main_cmds);
}
if (env_path) {
char *paths, *path, *colon;
path = paths = strdup(env_path);
while (1) {
if ((colon = strchr(path, PATH_SEP)))
*colon = 0;
if (!exec_path || strcmp(path, exec_path))
list_commands_in_dir(other_cmds, path, prefix);
if (!colon)
break;
path = colon + 1;
}
free(paths);
qsort(other_cmds->names, other_cmds->cnt,
sizeof(*other_cmds->names), cmdname_compare);
uniq(other_cmds);
}
exclude_cmds(other_cmds, main_cmds);
}
void list_commands(const char *title, struct cmdnames *main_cmds,
struct cmdnames *other_cmds)
{
unsigned int i, longest = 0;
for (i = 0; i < main_cmds->cnt; i++)
if (longest < main_cmds->names[i]->len)
longest = main_cmds->names[i]->len;
for (i = 0; i < other_cmds->cnt; i++)
if (longest < other_cmds->names[i]->len)
longest = other_cmds->names[i]->len;
if (main_cmds->cnt) {
const char *exec_path = perf_exec_path();
printf("available %s in '%s'\n", title, exec_path);
printf("----------------");
mput_char('-', strlen(title) + strlen(exec_path));
putchar('\n');
pretty_print_string_list(main_cmds, longest);
putchar('\n');
}
if (other_cmds->cnt) {
printf("%s available from elsewhere on your $PATH\n", title);
printf("---------------------------------------");
mput_char('-', strlen(title));
putchar('\n');
pretty_print_string_list(other_cmds, longest);
putchar('\n');
}
}
int is_in_cmdlist(struct cmdnames *c, const char *s)
{
unsigned int i;
for (i = 0; i < c->cnt; i++)
if (!strcmp(s, c->names[i]->name))
return 1;
return 0;
}
static int autocorrect;
static struct cmdnames aliases;
static int perf_unknown_cmd_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "help.autocorrect"))
autocorrect = perf_config_int(var,value);
/* Also use aliases for command lookup */
if (!prefixcmp(var, "alias."))
add_cmdname(&aliases, var + 6, strlen(var + 6));
return perf_default_config(var, value, cb);
}
static int levenshtein_compare(const void *p1, const void *p2)
{
const struct cmdname *const *c1 = p1, *const *c2 = p2;
const char *s1 = (*c1)->name, *s2 = (*c2)->name;
int l1 = (*c1)->len;
int l2 = (*c2)->len;
return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
}
static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
{
unsigned int i;
ALLOC_GROW(cmds->names, cmds->cnt + old->cnt, cmds->alloc);
for (i = 0; i < old->cnt; i++)
cmds->names[cmds->cnt++] = old->names[i];
free(old->names);
old->cnt = 0;
old->names = NULL;
}
const char *help_unknown_cmd(const char *cmd)
{
unsigned int i, n = 0, best_similarity = 0;
struct cmdnames main_cmds, other_cmds;
memset(&main_cmds, 0, sizeof(main_cmds));
memset(&other_cmds, 0, sizeof(main_cmds));
memset(&aliases, 0, sizeof(aliases));
perf_config(perf_unknown_cmd_config, NULL);
load_command_list("perf-", &main_cmds, &other_cmds);
add_cmd_list(&main_cmds, &aliases);
add_cmd_list(&main_cmds, &other_cmds);
qsort(main_cmds.names, main_cmds.cnt,
sizeof(main_cmds.names), cmdname_compare);
uniq(&main_cmds);
if (main_cmds.cnt) {
/* This reuses cmdname->len for similarity index */
for (i = 0; i < main_cmds.cnt; ++i)
main_cmds.names[i]->len =
levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
qsort(main_cmds.names, main_cmds.cnt,
sizeof(*main_cmds.names), levenshtein_compare);
best_similarity = main_cmds.names[0]->len;
n = 1;
while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
++n;
}
if (autocorrect && n == 1) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
clean_cmdnames(&main_cmds);
fprintf(stderr, "WARNING: You called a Git program named '%s', "
"which does not exist.\n"
"Continuing under the assumption that you meant '%s'\n",
cmd, assumed);
if (autocorrect > 0) {
fprintf(stderr, "in %0.1f seconds automatically...\n",
(float)autocorrect/10.0);
poll(NULL, 0, autocorrect * 100);
}
return assumed;
}
fprintf(stderr, "perf: '%s' is not a perf-command. See 'perf --help'.\n", cmd);
if (main_cmds.cnt && best_similarity < 6) {
fprintf(stderr, "\nDid you mean %s?\n",
n < 2 ? "this": "one of these");
for (i = 0; i < n; i++)
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
}
exit(1);
}
int cmd_version(int argc __used, const char **argv __used, const char *prefix __used)
{
printf("perf version %s\n", perf_version_string);
return 0;
}

View File

@@ -0,0 +1,29 @@
#ifndef HELP_H
#define HELP_H
struct cmdnames {
size_t alloc;
size_t cnt;
struct cmdname {
size_t len; /* also used for similarity index in help.c */
char name[FLEX_ARRAY];
} **names;
};
static inline void mput_char(char c, unsigned int num)
{
while(num--)
putchar(c);
}
void load_command_list(const char *prefix,
struct cmdnames *main_cmds,
struct cmdnames *other_cmds);
void add_cmdname(struct cmdnames *cmds, const char *name, size_t len);
/* Here we require that excludes is a sorted list. */
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
int is_in_cmdlist(struct cmdnames *c, const char *s);
void list_commands(const char *title, struct cmdnames *main_cmds,
struct cmdnames *other_cmds);
#endif /* HELP_H */

View File

@@ -0,0 +1 @@
/* Empty */

View File

@@ -0,0 +1,29 @@
#ifndef PERF_LINUX_KERNEL_H_
#define PERF_LINUX_KERNEL_H_
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
#endif
#ifndef max
#define max(x, y) ({ \
typeof(x) _max1 = (x); \
typeof(y) _max2 = (y); \
(void) (&_max1 == &_max2); \
_max1 > _max2 ? _max1 : _max2; })
#endif
#endif

View File

@@ -0,0 +1,18 @@
#include "../../../../include/linux/list.h"
#ifndef PERF_LIST_H
#define PERF_LIST_H
/**
* list_del_range - deletes range of entries from list.
* @begin: first element in the range to delete from the list.
* @end: last element in the range to delete from the list.
* Note: list_empty on the range of entries does not return true after this,
* the entries is in an undefined state.
*/
static inline void list_del_range(struct list_head *begin,
struct list_head *end)
{
begin->prev->next = end->next;
end->next->prev = begin->prev;
}
#endif

View File

@@ -0,0 +1,6 @@
#ifndef PERF_LINUX_MODULE_H
#define PERF_LINUX_MODULE_H
#define EXPORT_SYMBOL(name)
#endif

View File

@@ -0,0 +1 @@
#include "../../../../include/linux/poison.h"

View File

@@ -0,0 +1,6 @@
#ifndef PERF_LINUX_PREFETCH_H
#define PERF_LINUX_PREFETCH_H
static inline void prefetch(void *a __attribute__((unused))) { }
#endif

View File

@@ -0,0 +1 @@
#include "../../../../include/linux/rbtree.h"

View File

@@ -0,0 +1,84 @@
#include "cache.h"
#include "levenshtein.h"
/*
* This function implements the Damerau-Levenshtein algorithm to
* calculate a distance between strings.
*
* Basically, it says how many letters need to be swapped, substituted,
* deleted from, or added to string1, at least, to get string2.
*
* The idea is to build a distance matrix for the substrings of both
* strings. To avoid a large space complexity, only the last three rows
* are kept in memory (if swaps had the same or higher cost as one deletion
* plus one insertion, only two rows would be needed).
*
* At any stage, "i + 1" denotes the length of the current substring of
* string1 that the distance is calculated for.
*
* row2 holds the current row, row1 the previous row (i.e. for the substring
* of string1 of length "i"), and row0 the row before that.
*
* In other words, at the start of the big loop, row2[j + 1] contains the
* Damerau-Levenshtein distance between the substring of string1 of length
* "i" and the substring of string2 of length "j + 1".
*
* All the big loop does is determine the partial minimum-cost paths.
*
* It does so by calculating the costs of the path ending in characters
* i (in string1) and j (in string2), respectively, given that the last
* operation is a substition, a swap, a deletion, or an insertion.
*
* This implementation allows the costs to be weighted:
*
* - w (as in "sWap")
* - s (as in "Substitution")
* - a (for insertion, AKA "Add")
* - d (as in "Deletion")
*
* Note that this algorithm calculates a distance _iff_ d == a.
*/
int levenshtein(const char *string1, const char *string2,
int w, int s, int a, int d)
{
int len1 = strlen(string1), len2 = strlen(string2);
int *row0 = malloc(sizeof(int) * (len2 + 1));
int *row1 = malloc(sizeof(int) * (len2 + 1));
int *row2 = malloc(sizeof(int) * (len2 + 1));
int i, j;
for (j = 0; j <= len2; j++)
row1[j] = j * a;
for (i = 0; i < len1; i++) {
int *dummy;
row2[0] = (i + 1) * d;
for (j = 0; j < len2; j++) {
/* substitution */
row2[j + 1] = row1[j] + s * (string1[i] != string2[j]);
/* swap */
if (i > 0 && j > 0 && string1[i - 1] == string2[j] &&
string1[i] == string2[j - 1] &&
row2[j + 1] > row0[j - 1] + w)
row2[j + 1] = row0[j - 1] + w;
/* deletion */
if (row2[j + 1] > row1[j + 1] + d)
row2[j + 1] = row1[j + 1] + d;
/* insertion */
if (row2[j + 1] > row2[j] + a)
row2[j + 1] = row2[j] + a;
}
dummy = row0;
row0 = row1;
row1 = row2;
row2 = dummy;
}
i = row1[len2];
free(row0);
free(row1);
free(row2);
return i;
}

View File

@@ -0,0 +1,8 @@
#ifndef LEVENSHTEIN_H
#define LEVENSHTEIN_H
int levenshtein(const char *string1, const char *string2,
int swap_penalty, int substition_penalty,
int insertion_penalty, int deletion_penalty);
#endif

View File

@@ -0,0 +1,97 @@
#include "event.h"
#include "symbol.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static inline int is_anon_memory(const char *filename)
{
return strcmp(filename, "//anon") == 0;
}
static int strcommon(const char *pathname, char *cwd, int cwdlen)
{
int n = 0;
while (n < cwdlen && pathname[n] == cwd[n])
++n;
return n;
}
struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen)
{
struct map *self = malloc(sizeof(*self));
if (self != NULL) {
const char *filename = event->filename;
char newfilename[PATH_MAX];
int anon;
if (cwd) {
int n = strcommon(filename, cwd, cwdlen);
if (n == cwdlen) {
snprintf(newfilename, sizeof(newfilename),
".%s", filename + n);
filename = newfilename;
}
}
anon = is_anon_memory(filename);
if (anon) {
snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", event->pid);
filename = newfilename;
}
self->start = event->start;
self->end = event->start + event->len;
self->pgoff = event->pgoff;
self->dso = dsos__findnew(filename);
if (self->dso == NULL)
goto out_delete;
if (self->dso == vdso || anon)
self->map_ip = vdso__map_ip;
else
self->map_ip = map__map_ip;
}
return self;
out_delete:
free(self);
return NULL;
}
struct map *map__clone(struct map *self)
{
struct map *map = malloc(sizeof(*self));
if (!map)
return NULL;
memcpy(map, self, sizeof(*self));
return map;
}
int map__overlap(struct map *l, struct map *r)
{
if (l->start > r->start) {
struct map *t = l;
l = r;
r = t;
}
if (l->end > r->start)
return 1;
return 0;
}
size_t map__fprintf(struct map *self, FILE *fp)
{
return fprintf(fp, " %Lx-%Lx %Lx %s\n",
self->start, self->end, self->pgoff, self->dso->name);
}

View File

@@ -0,0 +1,545 @@
#include "util.h"
#include "../perf.h"
#include "string.h"
#include "module.h"
#include <libelf.h>
#include <libgen.h>
#include <gelf.h>
#include <elf.h>
#include <dirent.h>
#include <sys/utsname.h>
static unsigned int crc32(const char *p, unsigned int len)
{
int i;
unsigned int crc = 0;
while (len--) {
crc ^= *p++;
for (i = 0; i < 8; i++)
crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
}
return crc;
}
/* module section methods */
struct sec_dso *sec_dso__new_dso(const char *name)
{
struct sec_dso *self = malloc(sizeof(*self) + strlen(name) + 1);
if (self != NULL) {
strcpy(self->name, name);
self->secs = RB_ROOT;
self->find_section = sec_dso__find_section;
}
return self;
}
static void sec_dso__delete_section(struct section *self)
{
free(((void *)self));
}
void sec_dso__delete_sections(struct sec_dso *self)
{
struct section *pos;
struct rb_node *next = rb_first(&self->secs);
while (next) {
pos = rb_entry(next, struct section, rb_node);
next = rb_next(&pos->rb_node);
rb_erase(&pos->rb_node, &self->secs);
sec_dso__delete_section(pos);
}
}
void sec_dso__delete_self(struct sec_dso *self)
{
sec_dso__delete_sections(self);
free(self);
}
static void sec_dso__insert_section(struct sec_dso *self, struct section *sec)
{
struct rb_node **p = &self->secs.rb_node;
struct rb_node *parent = NULL;
const u64 hash = sec->hash;
struct section *s;
while (*p != NULL) {
parent = *p;
s = rb_entry(parent, struct section, rb_node);
if (hash < s->hash)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&sec->rb_node, parent, p);
rb_insert_color(&sec->rb_node, &self->secs);
}
struct section *sec_dso__find_section(struct sec_dso *self, const char *name)
{
struct rb_node *n;
u64 hash;
int len;
if (self == NULL)
return NULL;
len = strlen(name);
hash = crc32(name, len);
n = self->secs.rb_node;
while (n) {
struct section *s = rb_entry(n, struct section, rb_node);
if (hash < s->hash)
n = n->rb_left;
else if (hash > s->hash)
n = n->rb_right;
else {
if (!strcmp(name, s->name))
return s;
else
n = rb_next(&s->rb_node);
}
}
return NULL;
}
static size_t sec_dso__fprintf_section(struct section *self, FILE *fp)
{
return fprintf(fp, "name:%s vma:%llx path:%s\n",
self->name, self->vma, self->path);
}
size_t sec_dso__fprintf(struct sec_dso *self, FILE *fp)
{
size_t ret = fprintf(fp, "dso: %s\n", self->name);
struct rb_node *nd;
for (nd = rb_first(&self->secs); nd; nd = rb_next(nd)) {
struct section *pos = rb_entry(nd, struct section, rb_node);
ret += sec_dso__fprintf_section(pos, fp);
}
return ret;
}
static struct section *section__new(const char *name, const char *path)
{
struct section *self = calloc(1, sizeof(*self));
if (!self)
goto out_failure;
self->name = calloc(1, strlen(name) + 1);
if (!self->name)
goto out_failure;
self->path = calloc(1, strlen(path) + 1);
if (!self->path)
goto out_failure;
strcpy(self->name, name);
strcpy(self->path, path);
self->hash = crc32(self->name, strlen(name));
return self;
out_failure:
if (self) {
if (self->name)
free(self->name);
if (self->path)
free(self->path);
free(self);
}
return NULL;
}
/* module methods */
struct mod_dso *mod_dso__new_dso(const char *name)
{
struct mod_dso *self = malloc(sizeof(*self) + strlen(name) + 1);
if (self != NULL) {
strcpy(self->name, name);
self->mods = RB_ROOT;
self->find_module = mod_dso__find_module;
}
return self;
}
static void mod_dso__delete_module(struct module *self)
{
free(((void *)self));
}
void mod_dso__delete_modules(struct mod_dso *self)
{
struct module *pos;
struct rb_node *next = rb_first(&self->mods);
while (next) {
pos = rb_entry(next, struct module, rb_node);
next = rb_next(&pos->rb_node);
rb_erase(&pos->rb_node, &self->mods);
mod_dso__delete_module(pos);
}
}
void mod_dso__delete_self(struct mod_dso *self)
{
mod_dso__delete_modules(self);
free(self);
}
static void mod_dso__insert_module(struct mod_dso *self, struct module *mod)
{
struct rb_node **p = &self->mods.rb_node;
struct rb_node *parent = NULL;
const u64 hash = mod->hash;
struct module *m;
while (*p != NULL) {
parent = *p;
m = rb_entry(parent, struct module, rb_node);
if (hash < m->hash)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&mod->rb_node, parent, p);
rb_insert_color(&mod->rb_node, &self->mods);
}
struct module *mod_dso__find_module(struct mod_dso *self, const char *name)
{
struct rb_node *n;
u64 hash;
int len;
if (self == NULL)
return NULL;
len = strlen(name);
hash = crc32(name, len);
n = self->mods.rb_node;
while (n) {
struct module *m = rb_entry(n, struct module, rb_node);
if (hash < m->hash)
n = n->rb_left;
else if (hash > m->hash)
n = n->rb_right;
else {
if (!strcmp(name, m->name))
return m;
else
n = rb_next(&m->rb_node);
}
}
return NULL;
}
static size_t mod_dso__fprintf_module(struct module *self, FILE *fp)
{
return fprintf(fp, "name:%s path:%s\n", self->name, self->path);
}
size_t mod_dso__fprintf(struct mod_dso *self, FILE *fp)
{
struct rb_node *nd;
size_t ret;
ret = fprintf(fp, "dso: %s\n", self->name);
for (nd = rb_first(&self->mods); nd; nd = rb_next(nd)) {
struct module *pos = rb_entry(nd, struct module, rb_node);
ret += mod_dso__fprintf_module(pos, fp);
}
return ret;
}
static struct module *module__new(const char *name, const char *path)
{
struct module *self = calloc(1, sizeof(*self));
if (!self)
goto out_failure;
self->name = calloc(1, strlen(name) + 1);
if (!self->name)
goto out_failure;
self->path = calloc(1, strlen(path) + 1);
if (!self->path)
goto out_failure;
strcpy(self->name, name);
strcpy(self->path, path);
self->hash = crc32(self->name, strlen(name));
return self;
out_failure:
if (self) {
if (self->name)
free(self->name);
if (self->path)
free(self->path);
free(self);
}
return NULL;
}
static int mod_dso__load_sections(struct module *mod)
{
int count = 0, path_len;
struct dirent *entry;
char *line = NULL;
char *dir_path;
DIR *dir;
size_t n;
path_len = strlen("/sys/module/");
path_len += strlen(mod->name);
path_len += strlen("/sections/");
dir_path = calloc(1, path_len + 1);
if (dir_path == NULL)
goto out_failure;
strcat(dir_path, "/sys/module/");
strcat(dir_path, mod->name);
strcat(dir_path, "/sections/");
dir = opendir(dir_path);
if (dir == NULL)
goto out_free;
while ((entry = readdir(dir))) {
struct section *section;
char *path, *vma;
int line_len;
FILE *file;
if (!strcmp(".", entry->d_name) || !strcmp("..", entry->d_name))
continue;
path = calloc(1, path_len + strlen(entry->d_name) + 1);
if (path == NULL)
break;
strcat(path, dir_path);
strcat(path, entry->d_name);
file = fopen(path, "r");
if (file == NULL) {
free(path);
break;
}
line_len = getline(&line, &n, file);
if (line_len < 0) {
free(path);
fclose(file);
break;
}
if (!line) {
free(path);
fclose(file);
break;
}
line[--line_len] = '\0'; /* \n */
vma = strstr(line, "0x");
if (!vma) {
free(path);
fclose(file);
break;
}
vma += 2;
section = section__new(entry->d_name, path);
if (!section) {
fprintf(stderr, "load_sections: allocation error\n");
free(path);
fclose(file);
break;
}
hex2u64(vma, &section->vma);
sec_dso__insert_section(mod->sections, section);
free(path);
fclose(file);
count++;
}
closedir(dir);
free(line);
free(dir_path);
return count;
out_free:
free(dir_path);
out_failure:
return count;
}
static int mod_dso__load_module_paths(struct mod_dso *self)
{
struct utsname uts;
int count = 0, len, err = -1;
char *line = NULL;
FILE *file;
char *dpath, *dir;
size_t n;
if (uname(&uts) < 0)
return err;
len = strlen("/lib/modules/");
len += strlen(uts.release);
len += strlen("/modules.dep");
dpath = calloc(1, len + 1);
if (dpath == NULL)
return err;
strcat(dpath, "/lib/modules/");
strcat(dpath, uts.release);
strcat(dpath, "/modules.dep");
file = fopen(dpath, "r");
if (file == NULL)
goto out_failure;
dir = dirname(dpath);
if (!dir)
goto out_failure;
strcat(dir, "/");
while (!feof(file)) {
struct module *module;
char *name, *path, *tmp;
FILE *modfile;
int line_len;
line_len = getline(&line, &n, file);
if (line_len < 0)
break;
if (!line)
break;
line[--line_len] = '\0'; /* \n */
path = strchr(line, ':');
if (!path)
break;
*path = '\0';
path = strdup(line);
if (!path)
break;
if (!strstr(path, dir)) {
if (strncmp(path, "kernel/", 7))
break;
free(path);
path = calloc(1, strlen(dir) + strlen(line) + 1);
if (!path)
break;
strcat(path, dir);
strcat(path, line);
}
modfile = fopen(path, "r");
if (modfile == NULL)
break;
fclose(modfile);
name = strdup(path);
if (!name)
break;
name = strtok(name, "/");
tmp = name;
while (tmp) {
tmp = strtok(NULL, "/");
if (tmp)
name = tmp;
}
name = strsep(&name, ".");
if (!name)
break;
/* Quirk: replace '-' with '_' in all modules */
for (len = strlen(name); len; len--) {
if (*(name+len) == '-')
*(name+len) = '_';
}
module = module__new(name, path);
if (!module)
break;
mod_dso__insert_module(self, module);
module->sections = sec_dso__new_dso("sections");
if (!module->sections)
break;
module->active = mod_dso__load_sections(module);
if (module->active > 0)
count++;
}
if (feof(file))
err = count;
else
fprintf(stderr, "load_module_paths: modules.dep parsing failure!\n");
out_failure:
if (dpath)
free(dpath);
if (file)
fclose(file);
if (line)
free(line);
return err;
}
int mod_dso__load_modules(struct mod_dso *dso)
{
int err;
err = mod_dso__load_module_paths(dso);
return err;
}

View File

@@ -0,0 +1,53 @@
#ifndef _PERF_MODULE_
#define _PERF_MODULE_ 1
#include <linux/types.h>
#include "../types.h"
#include <linux/list.h>
#include <linux/rbtree.h>
struct section {
struct rb_node rb_node;
u64 hash;
u64 vma;
char *name;
char *path;
};
struct sec_dso {
struct list_head node;
struct rb_root secs;
struct section *(*find_section)(struct sec_dso *, const char *name);
char name[0];
};
struct module {
struct rb_node rb_node;
u64 hash;
char *name;
char *path;
struct sec_dso *sections;
int active;
};
struct mod_dso {
struct list_head node;
struct rb_root mods;
struct module *(*find_module)(struct mod_dso *, const char *name);
char name[0];
};
struct sec_dso *sec_dso__new_dso(const char *name);
void sec_dso__delete_sections(struct sec_dso *self);
void sec_dso__delete_self(struct sec_dso *self);
size_t sec_dso__fprintf(struct sec_dso *self, FILE *fp);
struct section *sec_dso__find_section(struct sec_dso *self, const char *name);
struct mod_dso *mod_dso__new_dso(const char *name);
void mod_dso__delete_modules(struct mod_dso *self);
void mod_dso__delete_self(struct mod_dso *self);
size_t mod_dso__fprintf(struct mod_dso *self, FILE *fp);
struct module *mod_dso__find_module(struct mod_dso *self, const char *name);
int mod_dso__load_modules(struct mod_dso *dso);
#endif /* _PERF_MODULE_ */

View File

@@ -0,0 +1,96 @@
#include "cache.h"
#include "run-command.h"
#include "sigchain.h"
/*
* This is split up from the rest of git so that we can do
* something different on Windows.
*/
static int spawned_pager;
static void pager_preexec(void)
{
/*
* Work around bug in "less" by not starting it until we
* have real input
*/
fd_set in;
FD_ZERO(&in);
FD_SET(0, &in);
select(1, &in, NULL, &in, NULL);
setenv("LESS", "FRSX", 0);
}
static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
static struct child_process pager_process;
static void wait_for_pager(void)
{
fflush(stdout);
fflush(stderr);
/* signal EOF to pager */
close(1);
close(2);
finish_command(&pager_process);
}
static void wait_for_pager_signal(int signo)
{
wait_for_pager();
sigchain_pop(signo);
raise(signo);
}
void setup_pager(void)
{
const char *pager = getenv("PERF_PAGER");
if (!isatty(1))
return;
if (!pager) {
if (!pager_program)
perf_config(perf_default_config, NULL);
pager = pager_program;
}
if (!pager)
pager = getenv("PAGER");
if (!pager)
pager = "less";
else if (!*pager || !strcmp(pager, "cat"))
return;
spawned_pager = 1; /* means we are emitting to terminal */
/* spawn the pager */
pager_argv[2] = pager;
pager_process.argv = pager_argv;
pager_process.in = -1;
pager_process.preexec_cb = pager_preexec;
if (start_command(&pager_process))
return;
/* original process continues, but writes to the pipe */
dup2(pager_process.in, 1);
if (isatty(2))
dup2(pager_process.in, 2);
close(pager_process.in);
/* this makes sure that the parent terminates after the pager */
sigchain_push_common(wait_for_pager_signal);
atexit(wait_for_pager);
}
int pager_in_use(void)
{
const char *env;
if (spawned_pager)
return 1;
env = getenv("PERF_PAGER_IN_USE");
return env ? perf_config_bool("PERF_PAGER_IN_USE", env) : 0;
}

View File

@@ -0,0 +1,846 @@
#include "util.h"
#include "../perf.h"
#include "parse-options.h"
#include "parse-events.h"
#include "exec_cmd.h"
#include "string.h"
#include "cache.h"
#include "header.h"
int nr_counters;
struct perf_event_attr attrs[MAX_COUNTERS];
struct event_symbol {
u8 type;
u64 config;
const char *symbol;
const char *alias;
};
enum event_result {
EVT_FAILED,
EVT_HANDLED,
EVT_HANDLED_ALL
};
char debugfs_path[MAXPATHLEN];
#define CHW(x) .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_##x
#define CSW(x) .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_##x
static struct event_symbol event_symbols[] = {
{ CHW(CPU_CYCLES), "cpu-cycles", "cycles" },
{ CHW(INSTRUCTIONS), "instructions", "" },
{ CHW(CACHE_REFERENCES), "cache-references", "" },
{ CHW(CACHE_MISSES), "cache-misses", "" },
{ CHW(BRANCH_INSTRUCTIONS), "branch-instructions", "branches" },
{ CHW(BRANCH_MISSES), "branch-misses", "" },
{ CHW(BUS_CYCLES), "bus-cycles", "" },
{ CSW(CPU_CLOCK), "cpu-clock", "" },
{ CSW(TASK_CLOCK), "task-clock", "" },
{ CSW(PAGE_FAULTS), "page-faults", "faults" },
{ CSW(PAGE_FAULTS_MIN), "minor-faults", "" },
{ CSW(PAGE_FAULTS_MAJ), "major-faults", "" },
{ CSW(CONTEXT_SWITCHES), "context-switches", "cs" },
{ CSW(CPU_MIGRATIONS), "cpu-migrations", "migrations" },
};
#define __PERF_EVENT_FIELD(config, name) \
((config & PERF_EVENT_##name##_MASK) >> PERF_EVENT_##name##_SHIFT)
#define PERF_EVENT_RAW(config) __PERF_EVENT_FIELD(config, RAW)
#define PERF_EVENT_CONFIG(config) __PERF_EVENT_FIELD(config, CONFIG)
#define PERF_EVENT_TYPE(config) __PERF_EVENT_FIELD(config, TYPE)
#define PERF_EVENT_ID(config) __PERF_EVENT_FIELD(config, EVENT)
static const char *hw_event_names[] = {
"cycles",
"instructions",
"cache-references",
"cache-misses",
"branches",
"branch-misses",
"bus-cycles",
};
static const char *sw_event_names[] = {
"cpu-clock-msecs",
"task-clock-msecs",
"page-faults",
"context-switches",
"CPU-migrations",
"minor-faults",
"major-faults",
};
#define MAX_ALIASES 8
static const char *hw_cache[][MAX_ALIASES] = {
{ "L1-dcache", "l1-d", "l1d", "L1-data", },
{ "L1-icache", "l1-i", "l1i", "L1-instruction", },
{ "LLC", "L2" },
{ "dTLB", "d-tlb", "Data-TLB", },
{ "iTLB", "i-tlb", "Instruction-TLB", },
{ "branch", "branches", "bpu", "btb", "bpc", },
};
static const char *hw_cache_op[][MAX_ALIASES] = {
{ "load", "loads", "read", },
{ "store", "stores", "write", },
{ "prefetch", "prefetches", "speculative-read", "speculative-load", },
};
static const char *hw_cache_result[][MAX_ALIASES] = {
{ "refs", "Reference", "ops", "access", },
{ "misses", "miss", },
};
#define C(x) PERF_COUNT_HW_CACHE_##x
#define CACHE_READ (1 << C(OP_READ))
#define CACHE_WRITE (1 << C(OP_WRITE))
#define CACHE_PREFETCH (1 << C(OP_PREFETCH))
#define COP(x) (1 << x)
/*
* cache operartion stat
* L1I : Read and prefetch only
* ITLB and BPU : Read-only
*/
static unsigned long hw_cache_stat[C(MAX)] = {
[C(L1D)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
[C(L1I)] = (CACHE_READ | CACHE_PREFETCH),
[C(LL)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
[C(DTLB)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
[C(ITLB)] = (CACHE_READ),
[C(BPU)] = (CACHE_READ),
};
#define for_each_subsystem(sys_dir, sys_dirent, sys_next) \
while (!readdir_r(sys_dir, &sys_dirent, &sys_next) && sys_next) \
if (sys_dirent.d_type == DT_DIR && \
(strcmp(sys_dirent.d_name, ".")) && \
(strcmp(sys_dirent.d_name, "..")))
static int tp_event_has_id(struct dirent *sys_dir, struct dirent *evt_dir)
{
char evt_path[MAXPATHLEN];
int fd;
snprintf(evt_path, MAXPATHLEN, "%s/%s/%s/id", debugfs_path,
sys_dir->d_name, evt_dir->d_name);
fd = open(evt_path, O_RDONLY);
if (fd < 0)
return -EINVAL;
close(fd);
return 0;
}
#define for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) \
while (!readdir_r(evt_dir, &evt_dirent, &evt_next) && evt_next) \
if (evt_dirent.d_type == DT_DIR && \
(strcmp(evt_dirent.d_name, ".")) && \
(strcmp(evt_dirent.d_name, "..")) && \
(!tp_event_has_id(&sys_dirent, &evt_dirent)))
#define MAX_EVENT_LENGTH 512
int valid_debugfs_mount(const char *debugfs)
{
struct statfs st_fs;
if (statfs(debugfs, &st_fs) < 0)
return -ENOENT;
else if (st_fs.f_type != (long) DEBUGFS_MAGIC)
return -ENOENT;
return 0;
}
struct tracepoint_path *tracepoint_id_to_path(u64 config)
{
struct tracepoint_path *path = NULL;
DIR *sys_dir, *evt_dir;
struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent;
char id_buf[4];
int fd;
u64 id;
char evt_path[MAXPATHLEN];
char dir_path[MAXPATHLEN];
if (valid_debugfs_mount(debugfs_path))
return NULL;
sys_dir = opendir(debugfs_path);
if (!sys_dir)
return NULL;
for_each_subsystem(sys_dir, sys_dirent, sys_next) {
snprintf(dir_path, MAXPATHLEN, "%s/%s", debugfs_path,
sys_dirent.d_name);
evt_dir = opendir(dir_path);
if (!evt_dir)
continue;
for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) {
snprintf(evt_path, MAXPATHLEN, "%s/%s/id", dir_path,
evt_dirent.d_name);
fd = open(evt_path, O_RDONLY);
if (fd < 0)
continue;
if (read(fd, id_buf, sizeof(id_buf)) < 0) {
close(fd);
continue;
}
close(fd);
id = atoll(id_buf);
if (id == config) {
closedir(evt_dir);
closedir(sys_dir);
path = calloc(1, sizeof(path));
path->system = malloc(MAX_EVENT_LENGTH);
if (!path->system) {
free(path);
return NULL;
}
path->name = malloc(MAX_EVENT_LENGTH);
if (!path->name) {
free(path->system);
free(path);
return NULL;
}
strncpy(path->system, sys_dirent.d_name,
MAX_EVENT_LENGTH);
strncpy(path->name, evt_dirent.d_name,
MAX_EVENT_LENGTH);
return path;
}
}
closedir(evt_dir);
}
closedir(sys_dir);
return NULL;
}
#define TP_PATH_LEN (MAX_EVENT_LENGTH * 2 + 1)
static const char *tracepoint_id_to_name(u64 config)
{
static char buf[TP_PATH_LEN];
struct tracepoint_path *path;
path = tracepoint_id_to_path(config);
if (path) {
snprintf(buf, TP_PATH_LEN, "%s:%s", path->system, path->name);
free(path->name);
free(path->system);
free(path);
} else
snprintf(buf, TP_PATH_LEN, "%s:%s", "unknown", "unknown");
return buf;
}
static int is_cache_op_valid(u8 cache_type, u8 cache_op)
{
if (hw_cache_stat[cache_type] & COP(cache_op))
return 1; /* valid */
else
return 0; /* invalid */
}
static char *event_cache_name(u8 cache_type, u8 cache_op, u8 cache_result)
{
static char name[50];
if (cache_result) {
sprintf(name, "%s-%s-%s", hw_cache[cache_type][0],
hw_cache_op[cache_op][0],
hw_cache_result[cache_result][0]);
} else {
sprintf(name, "%s-%s", hw_cache[cache_type][0],
hw_cache_op[cache_op][1]);
}
return name;
}
const char *event_name(int counter)
{
u64 config = attrs[counter].config;
int type = attrs[counter].type;
return __event_name(type, config);
}
const char *__event_name(int type, u64 config)
{
static char buf[32];
if (type == PERF_TYPE_RAW) {
sprintf(buf, "raw 0x%llx", config);
return buf;
}
switch (type) {
case PERF_TYPE_HARDWARE:
if (config < PERF_COUNT_HW_MAX)
return hw_event_names[config];
return "unknown-hardware";
case PERF_TYPE_HW_CACHE: {
u8 cache_type, cache_op, cache_result;
cache_type = (config >> 0) & 0xff;
if (cache_type > PERF_COUNT_HW_CACHE_MAX)
return "unknown-ext-hardware-cache-type";
cache_op = (config >> 8) & 0xff;
if (cache_op > PERF_COUNT_HW_CACHE_OP_MAX)
return "unknown-ext-hardware-cache-op";
cache_result = (config >> 16) & 0xff;
if (cache_result > PERF_COUNT_HW_CACHE_RESULT_MAX)
return "unknown-ext-hardware-cache-result";
if (!is_cache_op_valid(cache_type, cache_op))
return "invalid-cache";
return event_cache_name(cache_type, cache_op, cache_result);
}
case PERF_TYPE_SOFTWARE:
if (config < PERF_COUNT_SW_MAX)
return sw_event_names[config];
return "unknown-software";
case PERF_TYPE_TRACEPOINT:
return tracepoint_id_to_name(config);
default:
break;
}
return "unknown";
}
static int parse_aliases(const char **str, const char *names[][MAX_ALIASES], int size)
{
int i, j;
int n, longest = -1;
for (i = 0; i < size; i++) {
for (j = 0; j < MAX_ALIASES && names[i][j]; j++) {
n = strlen(names[i][j]);
if (n > longest && !strncasecmp(*str, names[i][j], n))
longest = n;
}
if (longest > 0) {
*str += longest;
return i;
}
}
return -1;
}
static enum event_result
parse_generic_hw_event(const char **str, struct perf_event_attr *attr)
{
const char *s = *str;
int cache_type = -1, cache_op = -1, cache_result = -1;
cache_type = parse_aliases(&s, hw_cache, PERF_COUNT_HW_CACHE_MAX);
/*
* No fallback - if we cannot get a clear cache type
* then bail out:
*/
if (cache_type == -1)
return EVT_FAILED;
while ((cache_op == -1 || cache_result == -1) && *s == '-') {
++s;
if (cache_op == -1) {
cache_op = parse_aliases(&s, hw_cache_op,
PERF_COUNT_HW_CACHE_OP_MAX);
if (cache_op >= 0) {
if (!is_cache_op_valid(cache_type, cache_op))
return 0;
continue;
}
}
if (cache_result == -1) {
cache_result = parse_aliases(&s, hw_cache_result,
PERF_COUNT_HW_CACHE_RESULT_MAX);
if (cache_result >= 0)
continue;
}
/*
* Can't parse this as a cache op or result, so back up
* to the '-'.
*/
--s;
break;
}
/*
* Fall back to reads:
*/
if (cache_op == -1)
cache_op = PERF_COUNT_HW_CACHE_OP_READ;
/*
* Fall back to accesses:
*/
if (cache_result == -1)
cache_result = PERF_COUNT_HW_CACHE_RESULT_ACCESS;
attr->config = cache_type | (cache_op << 8) | (cache_result << 16);
attr->type = PERF_TYPE_HW_CACHE;
*str = s;
return EVT_HANDLED;
}
static enum event_result
parse_single_tracepoint_event(char *sys_name,
const char *evt_name,
unsigned int evt_length,
char *flags,
struct perf_event_attr *attr,
const char **strp)
{
char evt_path[MAXPATHLEN];
char id_buf[4];
u64 id;
int fd;
if (flags) {
if (!strncmp(flags, "record", strlen(flags))) {
attr->sample_type |= PERF_SAMPLE_RAW;
attr->sample_type |= PERF_SAMPLE_TIME;
attr->sample_type |= PERF_SAMPLE_CPU;
}
}
snprintf(evt_path, MAXPATHLEN, "%s/%s/%s/id", debugfs_path,
sys_name, evt_name);
fd = open(evt_path, O_RDONLY);
if (fd < 0)
return EVT_FAILED;
if (read(fd, id_buf, sizeof(id_buf)) < 0) {
close(fd);
return EVT_FAILED;
}
close(fd);
id = atoll(id_buf);
attr->config = id;
attr->type = PERF_TYPE_TRACEPOINT;
*strp = evt_name + evt_length;
return EVT_HANDLED;
}
/* sys + ':' + event + ':' + flags*/
#define MAX_EVOPT_LEN (MAX_EVENT_LENGTH * 2 + 2 + 128)
static enum event_result
parse_subsystem_tracepoint_event(char *sys_name, char *flags)
{
char evt_path[MAXPATHLEN];
struct dirent *evt_ent;
DIR *evt_dir;
snprintf(evt_path, MAXPATHLEN, "%s/%s", debugfs_path, sys_name);
evt_dir = opendir(evt_path);
if (!evt_dir) {
perror("Can't open event dir");
return EVT_FAILED;
}
while ((evt_ent = readdir(evt_dir))) {
char event_opt[MAX_EVOPT_LEN + 1];
int len;
unsigned int rem = MAX_EVOPT_LEN;
if (!strcmp(evt_ent->d_name, ".")
|| !strcmp(evt_ent->d_name, "..")
|| !strcmp(evt_ent->d_name, "enable")
|| !strcmp(evt_ent->d_name, "filter"))
continue;
len = snprintf(event_opt, MAX_EVOPT_LEN, "%s:%s", sys_name,
evt_ent->d_name);
if (len < 0)
return EVT_FAILED;
rem -= len;
if (flags) {
if (rem < strlen(flags) + 1)
return EVT_FAILED;
strcat(event_opt, ":");
strcat(event_opt, flags);
}
if (parse_events(NULL, event_opt, 0))
return EVT_FAILED;
}
return EVT_HANDLED_ALL;
}
static enum event_result parse_tracepoint_event(const char **strp,
struct perf_event_attr *attr)
{
const char *evt_name;
char *flags;
char sys_name[MAX_EVENT_LENGTH];
unsigned int sys_length, evt_length;
if (valid_debugfs_mount(debugfs_path))
return 0;
evt_name = strchr(*strp, ':');
if (!evt_name)
return EVT_FAILED;
sys_length = evt_name - *strp;
if (sys_length >= MAX_EVENT_LENGTH)
return 0;
strncpy(sys_name, *strp, sys_length);
sys_name[sys_length] = '\0';
evt_name = evt_name + 1;
flags = strchr(evt_name, ':');
if (flags) {
/* split it out: */
evt_name = strndup(evt_name, flags - evt_name);
flags++;
}
evt_length = strlen(evt_name);
if (evt_length >= MAX_EVENT_LENGTH)
return EVT_FAILED;
if (!strcmp(evt_name, "*")) {
*strp = evt_name + evt_length;
return parse_subsystem_tracepoint_event(sys_name, flags);
} else
return parse_single_tracepoint_event(sys_name, evt_name,
evt_length, flags,
attr, strp);
}
static int check_events(const char *str, unsigned int i)
{
int n;
n = strlen(event_symbols[i].symbol);
if (!strncmp(str, event_symbols[i].symbol, n))
return n;
n = strlen(event_symbols[i].alias);
if (n)
if (!strncmp(str, event_symbols[i].alias, n))
return n;
return 0;
}
static enum event_result
parse_symbolic_event(const char **strp, struct perf_event_attr *attr)
{
const char *str = *strp;
unsigned int i;
int n;
for (i = 0; i < ARRAY_SIZE(event_symbols); i++) {
n = check_events(str, i);
if (n > 0) {
attr->type = event_symbols[i].type;
attr->config = event_symbols[i].config;
*strp = str + n;
return EVT_HANDLED;
}
}
return EVT_FAILED;
}
static enum event_result
parse_raw_event(const char **strp, struct perf_event_attr *attr)
{
const char *str = *strp;
u64 config;
int n;
if (*str != 'r')
return EVT_FAILED;
n = hex2u64(str + 1, &config);
if (n > 0) {
*strp = str + n + 1;
attr->type = PERF_TYPE_RAW;
attr->config = config;
return EVT_HANDLED;
}
return EVT_FAILED;
}
static enum event_result
parse_numeric_event(const char **strp, struct perf_event_attr *attr)
{
const char *str = *strp;
char *endp;
unsigned long type;
u64 config;
type = strtoul(str, &endp, 0);
if (endp > str && type < PERF_TYPE_MAX && *endp == ':') {
str = endp + 1;
config = strtoul(str, &endp, 0);
if (endp > str) {
attr->type = type;
attr->config = config;
*strp = endp;
return EVT_HANDLED;
}
}
return EVT_FAILED;
}
static enum event_result
parse_event_modifier(const char **strp, struct perf_event_attr *attr)
{
const char *str = *strp;
int eu = 1, ek = 1, eh = 1;
if (*str++ != ':')
return 0;
while (*str) {
if (*str == 'u')
eu = 0;
else if (*str == 'k')
ek = 0;
else if (*str == 'h')
eh = 0;
else
break;
++str;
}
if (str >= *strp + 2) {
*strp = str;
attr->exclude_user = eu;
attr->exclude_kernel = ek;
attr->exclude_hv = eh;
return 1;
}
return 0;
}
/*
* Each event can have multiple symbolic names.
* Symbolic names are (almost) exactly matched.
*/
static enum event_result
parse_event_symbols(const char **str, struct perf_event_attr *attr)
{
enum event_result ret;
ret = parse_tracepoint_event(str, attr);
if (ret != EVT_FAILED)
goto modifier;
ret = parse_raw_event(str, attr);
if (ret != EVT_FAILED)
goto modifier;
ret = parse_numeric_event(str, attr);
if (ret != EVT_FAILED)
goto modifier;
ret = parse_symbolic_event(str, attr);
if (ret != EVT_FAILED)
goto modifier;
ret = parse_generic_hw_event(str, attr);
if (ret != EVT_FAILED)
goto modifier;
return EVT_FAILED;
modifier:
parse_event_modifier(str, attr);
return ret;
}
static void store_event_type(const char *orgname)
{
char filename[PATH_MAX], *c;
FILE *file;
int id;
sprintf(filename, "%s/", debugfs_path);
strncat(filename, orgname, strlen(orgname));
strcat(filename, "/id");
c = strchr(filename, ':');
if (c)
*c = '/';
file = fopen(filename, "r");
if (!file)
return;
if (fscanf(file, "%i", &id) < 1)
die("cannot store event ID");
fclose(file);
perf_header__push_event(id, orgname);
}
int parse_events(const struct option *opt __used, const char *str, int unset __used)
{
struct perf_event_attr attr;
enum event_result ret;
if (strchr(str, ':'))
store_event_type(str);
for (;;) {
if (nr_counters == MAX_COUNTERS)
return -1;
memset(&attr, 0, sizeof(attr));
ret = parse_event_symbols(&str, &attr);
if (ret == EVT_FAILED)
return -1;
if (!(*str == 0 || *str == ',' || isspace(*str)))
return -1;
if (ret != EVT_HANDLED_ALL) {
attrs[nr_counters] = attr;
nr_counters++;
}
if (*str == 0)
break;
if (*str == ',')
++str;
while (isspace(*str))
++str;
}
return 0;
}
static const char * const event_type_descriptors[] = {
"",
"Hardware event",
"Software event",
"Tracepoint event",
"Hardware cache event",
};
/*
* Print the events from <debugfs_mount_point>/tracing/events
*/
static void print_tracepoint_events(void)
{
DIR *sys_dir, *evt_dir;
struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent;
char evt_path[MAXPATHLEN];
char dir_path[MAXPATHLEN];
if (valid_debugfs_mount(debugfs_path))
return;
sys_dir = opendir(debugfs_path);
if (!sys_dir)
return;
for_each_subsystem(sys_dir, sys_dirent, sys_next) {
snprintf(dir_path, MAXPATHLEN, "%s/%s", debugfs_path,
sys_dirent.d_name);
evt_dir = opendir(dir_path);
if (!evt_dir)
continue;
for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) {
snprintf(evt_path, MAXPATHLEN, "%s:%s",
sys_dirent.d_name, evt_dirent.d_name);
fprintf(stderr, " %-42s [%s]\n", evt_path,
event_type_descriptors[PERF_TYPE_TRACEPOINT+1]);
}
closedir(evt_dir);
}
closedir(sys_dir);
}
/*
* Print the help text for the event symbols:
*/
void print_events(void)
{
struct event_symbol *syms = event_symbols;
unsigned int i, type, op, prev_type = -1;
char name[40];
fprintf(stderr, "\n");
fprintf(stderr, "List of pre-defined events (to be used in -e):\n");
for (i = 0; i < ARRAY_SIZE(event_symbols); i++, syms++) {
type = syms->type + 1;
if (type >= ARRAY_SIZE(event_type_descriptors))
type = 0;
if (type != prev_type)
fprintf(stderr, "\n");
if (strlen(syms->alias))
sprintf(name, "%s OR %s", syms->symbol, syms->alias);
else
strcpy(name, syms->symbol);
fprintf(stderr, " %-42s [%s]\n", name,
event_type_descriptors[type]);
prev_type = type;
}
fprintf(stderr, "\n");
for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) {
for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) {
/* skip invalid cache type */
if (!is_cache_op_valid(type, op))
continue;
for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) {
fprintf(stderr, " %-42s [%s]\n",
event_cache_name(type, op, i),
event_type_descriptors[4]);
}
}
}
fprintf(stderr, "\n");
fprintf(stderr, " %-42s [raw hardware event descriptor]\n",
"rNNN");
fprintf(stderr, "\n");
print_tracepoint_events();
exit(129);
}

View File

@@ -0,0 +1,34 @@
#ifndef _PARSE_EVENTS_H
#define _PARSE_EVENTS_H
/*
* Parse symbolic events/counts passed in as options:
*/
struct option;
struct tracepoint_path {
char *system;
char *name;
struct tracepoint_path *next;
};
extern struct tracepoint_path *tracepoint_id_to_path(u64 config);
extern int nr_counters;
extern struct perf_event_attr attrs[MAX_COUNTERS];
extern const char *event_name(int ctr);
extern const char *__event_name(int type, u64 config);
extern int parse_events(const struct option *opt, const char *str, int unset);
#define EVENTS_HELP_MAX (128*1024)
extern void print_events(void);
extern char debugfs_path[];
extern int valid_debugfs_mount(const char *debugfs);
#endif /* _PARSE_EVENTS_H */

View File

@@ -0,0 +1,531 @@
#include "util.h"
#include "parse-options.h"
#include "cache.h"
#define OPT_SHORT 1
#define OPT_UNSET 2
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
return error("switch `%c' %s", opt->short_name, reason);
if (flags & OPT_UNSET)
return error("option `no-%s' %s", opt->long_name, reason);
return error("option `%s' %s", opt->long_name, reason);
}
static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
int flags, const char **arg)
{
if (p->opt) {
*arg = p->opt;
p->opt = NULL;
} else if ((opt->flags & PARSE_OPT_LASTARG_DEFAULT) && (p->argc == 1 ||
**(p->argv + 1) == '-')) {
*arg = (const char *)opt->defval;
} else if (p->argc > 1) {
p->argc--;
*arg = *++p->argv;
} else
return opterror(opt, "requires a value", flags);
return 0;
}
static int get_value(struct parse_opt_ctx_t *p,
const struct option *opt, int flags)
{
const char *s, *arg = NULL;
const int unset = flags & OPT_UNSET;
if (unset && p->opt)
return opterror(opt, "takes no value", flags);
if (unset && (opt->flags & PARSE_OPT_NONEG))
return opterror(opt, "isn't available", flags);
if (!(flags & OPT_SHORT) && p->opt) {
switch (opt->type) {
case OPTION_CALLBACK:
if (!(opt->flags & PARSE_OPT_NOARG))
break;
/* FALLTHROUGH */
case OPTION_BOOLEAN:
case OPTION_BIT:
case OPTION_SET_INT:
case OPTION_SET_PTR:
return opterror(opt, "takes no value", flags);
case OPTION_END:
case OPTION_ARGUMENT:
case OPTION_GROUP:
case OPTION_STRING:
case OPTION_INTEGER:
case OPTION_LONG:
default:
break;
}
}
switch (opt->type) {
case OPTION_BIT:
if (unset)
*(int *)opt->value &= ~opt->defval;
else
*(int *)opt->value |= opt->defval;
return 0;
case OPTION_BOOLEAN:
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
return 0;
case OPTION_SET_INT:
*(int *)opt->value = unset ? 0 : opt->defval;
return 0;
case OPTION_SET_PTR:
*(void **)opt->value = unset ? NULL : (void *)opt->defval;
return 0;
case OPTION_STRING:
if (unset)
*(const char **)opt->value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
else
return get_arg(p, opt, flags, (const char **)opt->value);
return 0;
case OPTION_CALLBACK:
if (unset)
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
if (opt->flags & PARSE_OPT_NOARG)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (get_arg(p, opt, flags, &arg))
return -1;
return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
case OPTION_INTEGER:
if (unset) {
*(int *)opt->value = 0;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
*(int *)opt->value = opt->defval;
return 0;
}
if (get_arg(p, opt, flags, &arg))
return -1;
*(int *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
return opterror(opt, "expects a numerical value", flags);
return 0;
case OPTION_LONG:
if (unset) {
*(long *)opt->value = 0;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
*(long *)opt->value = opt->defval;
return 0;
}
if (get_arg(p, opt, flags, &arg))
return -1;
*(long *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
return opterror(opt, "expects a numerical value", flags);
return 0;
case OPTION_END:
case OPTION_ARGUMENT:
case OPTION_GROUP:
default:
die("should not happen, someone must be hit on the forehead");
}
}
static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
{
for (; options->type != OPTION_END; options++) {
if (options->short_name == *p->opt) {
p->opt = p->opt[1] ? p->opt + 1 : NULL;
return get_value(p, options, OPT_SHORT);
}
}
return -2;
}
static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
const struct option *options)
{
const char *arg_end = strchr(arg, '=');
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
int abbrev_flags = 0, ambiguous_flags = 0;
if (!arg_end)
arg_end = arg + strlen(arg);
for (; options->type != OPTION_END; options++) {
const char *rest;
int flags = 0;
if (!options->long_name)
continue;
rest = skip_prefix(arg, options->long_name);
if (options->type == OPTION_ARGUMENT) {
if (!rest)
continue;
if (*rest == '=')
return opterror(options, "takes no value", flags);
if (*rest)
continue;
p->out[p->cpidx++] = arg - 2;
return 0;
}
if (!rest) {
/* abbreviated? */
if (!strncmp(options->long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option) {
/*
* If this is abbreviated, it is
* ambiguous. So when there is no
* exact match later, we need to
* error out.
*/
ambiguous_option = abbrev_option;
ambiguous_flags = abbrev_flags;
}
if (!(flags & OPT_UNSET) && *arg_end)
p->opt = arg_end + 1;
abbrev_option = options;
abbrev_flags = flags;
continue;
}
/* negated and abbreviated very much? */
if (!prefixcmp("no-", arg)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
if (strncmp(arg, "no-", 3))
continue;
flags |= OPT_UNSET;
rest = skip_prefix(arg + 3, options->long_name);
/* abbreviated and negated? */
if (!rest && !prefixcmp(options->long_name, arg + 3))
goto is_abbreviated;
if (!rest)
continue;
}
if (*rest) {
if (*rest != '=')
continue;
p->opt = rest + 1;
}
return get_value(p, options, flags);
}
if (ambiguous_option)
return error("Ambiguous option: %s "
"(could be --%s%s or --%s%s)",
arg,
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
ambiguous_option->long_name,
(abbrev_flags & OPT_UNSET) ? "no-" : "",
abbrev_option->long_name);
if (abbrev_option)
return get_value(p, abbrev_option, abbrev_flags);
return -2;
}
static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
return;
if (!prefixcmp(arg, "no-")) {
error ("did you mean `--%s` (with two dashes ?)", arg);
exit(129);
}
for (; options->type != OPTION_END; options++) {
if (!options->long_name)
continue;
if (!prefixcmp(options->long_name, arg)) {
error ("did you mean `--%s` (with two dashes ?)", arg);
exit(129);
}
}
}
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, int flags)
{
memset(ctx, 0, sizeof(*ctx));
ctx->argc = argc - 1;
ctx->argv = argv + 1;
ctx->out = argv;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION))
die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
}
static int usage_with_options_internal(const char * const *,
const struct option *, int);
int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[])
{
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0];
if (*arg != '-' || !arg[1]) {
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
break;
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
if (arg[1] != '-') {
ctx->opt = arg + 1;
if (internal_help && *ctx->opt == 'h')
return parse_options_usage(usagestr, options);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
goto unknown;
default:
break;
}
if (ctx->opt)
check_typos(arg + 1, options);
while (ctx->opt) {
if (internal_help && *ctx->opt == 'h')
return parse_options_usage(usagestr, options);
switch (parse_short_opt(ctx, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
/* fake a short option thing to hide the fact that we may have
* started to parse aggregated stuff
*
* This is leaky, too bad.
*/
ctx->argv[0] = strdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-';
goto unknown;
default:
break;
}
}
continue;
}
if (!arg[2]) { /* "--" */
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
ctx->argc--;
ctx->argv++;
}
break;
}
if (internal_help && !strcmp(arg + 2, "help-all"))
return usage_with_options_internal(usagestr, options, 1);
if (internal_help && !strcmp(arg + 2, "help"))
return parse_options_usage(usagestr, options);
switch (parse_long_opt(ctx, arg + 2, options)) {
case -1:
return parse_options_usage(usagestr, options);
case -2:
goto unknown;
default:
break;
}
continue;
unknown:
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
ctx->opt = NULL;
}
return PARSE_OPT_DONE;
}
int parse_options_end(struct parse_opt_ctx_t *ctx)
{
memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
ctx->out[ctx->cpidx + ctx->argc] = NULL;
return ctx->cpidx + ctx->argc;
}
int parse_options(int argc, const char **argv, const struct option *options,
const char * const usagestr[], int flags)
{
struct parse_opt_ctx_t ctx;
parse_options_start(&ctx, argc, argv, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
exit(129);
case PARSE_OPT_DONE:
break;
default: /* PARSE_OPT_UNKNOWN */
if (ctx.argv[0][1] == '-') {
error("unknown option `%s'", ctx.argv[0] + 2);
} else {
error("unknown switch `%c'", *ctx.opt);
}
usage_with_options(usagestr, options);
}
return parse_options_end(&ctx);
}
#define USAGE_OPTS_WIDTH 24
#define USAGE_GAP 2
int usage_with_options_internal(const char * const *usagestr,
const struct option *opts, int full)
{
if (!usagestr)
return PARSE_OPT_HELP;
fprintf(stderr, "\n usage: %s\n", *usagestr++);
while (*usagestr && **usagestr)
fprintf(stderr, " or: %s\n", *usagestr++);
while (*usagestr) {
fprintf(stderr, "%s%s\n",
**usagestr ? " " : "",
*usagestr);
usagestr++;
}
if (opts->type != OPTION_GROUP)
fputc('\n', stderr);
for (; opts->type != OPTION_END; opts++) {
size_t pos;
int pad;
if (opts->type == OPTION_GROUP) {
fputc('\n', stderr);
if (*opts->help)
fprintf(stderr, "%s\n", opts->help);
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
pos = fprintf(stderr, " ");
if (opts->short_name)
pos += fprintf(stderr, "-%c", opts->short_name);
if (opts->long_name && opts->short_name)
pos += fprintf(stderr, ", ");
if (opts->long_name)
pos += fprintf(stderr, "--%s", opts->long_name);
switch (opts->type) {
case OPTION_ARGUMENT:
break;
case OPTION_INTEGER:
if (opts->flags & PARSE_OPT_OPTARG)
if (opts->long_name)
pos += fprintf(stderr, "[=<n>]");
else
pos += fprintf(stderr, "[<n>]");
else
pos += fprintf(stderr, " <n>");
break;
case OPTION_CALLBACK:
if (opts->flags & PARSE_OPT_NOARG)
break;
/* FALLTHROUGH */
case OPTION_STRING:
if (opts->argh) {
if (opts->flags & PARSE_OPT_OPTARG)
if (opts->long_name)
pos += fprintf(stderr, "[=<%s>]", opts->argh);
else
pos += fprintf(stderr, "[<%s>]", opts->argh);
else
pos += fprintf(stderr, " <%s>", opts->argh);
} else {
if (opts->flags & PARSE_OPT_OPTARG)
if (opts->long_name)
pos += fprintf(stderr, "[=...]");
else
pos += fprintf(stderr, "[...]");
else
pos += fprintf(stderr, " ...");
}
break;
default: /* OPTION_{BIT,BOOLEAN,SET_INT,SET_PTR} */
case OPTION_END:
case OPTION_GROUP:
case OPTION_BIT:
case OPTION_BOOLEAN:
case OPTION_SET_INT:
case OPTION_SET_PTR:
case OPTION_LONG:
break;
}
if (pos <= USAGE_OPTS_WIDTH)
pad = USAGE_OPTS_WIDTH - pos;
else {
fputc('\n', stderr);
pad = USAGE_OPTS_WIDTH;
}
fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
}
fputc('\n', stderr);
return PARSE_OPT_HELP;
}
void usage_with_options(const char * const *usagestr,
const struct option *opts)
{
usage_with_options_internal(usagestr, opts, 0);
exit(129);
}
int parse_options_usage(const char * const *usagestr,
const struct option *opts)
{
return usage_with_options_internal(usagestr, opts, 0);
}
int parse_opt_verbosity_cb(const struct option *opt, const char *arg __used,
int unset)
{
int *target = opt->value;
if (unset)
/* --no-quiet, --no-verbose */
*target = 0;
else if (opt->short_name == 'v') {
if (*target >= 0)
(*target)++;
else
*target = 1;
} else {
if (*target <= 0)
(*target)--;
else
*target = -1;
}
return 0;
}

View File

@@ -0,0 +1,177 @@
#ifndef PARSE_OPTIONS_H
#define PARSE_OPTIONS_H
enum parse_opt_type {
/* special types */
OPTION_END,
OPTION_ARGUMENT,
OPTION_GROUP,
/* options with no arguments */
OPTION_BIT,
OPTION_BOOLEAN, /* _INCR would have been a better name */
OPTION_SET_INT,
OPTION_SET_PTR,
/* options with arguments (usually) */
OPTION_STRING,
OPTION_INTEGER,
OPTION_LONG,
OPTION_CALLBACK,
};
enum parse_opt_flags {
PARSE_OPT_KEEP_DASHDASH = 1,
PARSE_OPT_STOP_AT_NON_OPTION = 2,
PARSE_OPT_KEEP_ARGV0 = 4,
PARSE_OPT_KEEP_UNKNOWN = 8,
PARSE_OPT_NO_INTERNAL_HELP = 16,
};
enum parse_opt_option_flags {
PARSE_OPT_OPTARG = 1,
PARSE_OPT_NOARG = 2,
PARSE_OPT_NONEG = 4,
PARSE_OPT_HIDDEN = 8,
PARSE_OPT_LASTARG_DEFAULT = 16,
};
struct option;
typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
/*
* `type`::
* holds the type of the option, you must have an OPTION_END last in your
* array.
*
* `short_name`::
* the character to use as a short option name, '\0' if none.
*
* `long_name`::
* the long option name, without the leading dashes, NULL if none.
*
* `value`::
* stores pointers to the values to be filled.
*
* `argh`::
* token to explain the kind of argument this option wants. Keep it
* homogenous across the repository.
*
* `help`::
* the short help associated to what the option does.
* Must never be NULL (except for OPTION_END).
* OPTION_GROUP uses this pointer to store the group header.
*
* `flags`::
* mask of parse_opt_option_flags.
* PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs)
* PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
* PARSE_OPT_NONEG: says that this option cannot be negated
* PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
* the long one.
*
* `callback`::
* pointer to the callback to use for OPTION_CALLBACK.
*
* `defval`::
* default value to fill (*->value) with for PARSE_OPT_OPTARG.
* OPTION_{BIT,SET_INT,SET_PTR} store the {mask,integer,pointer} to put in
* the value when met.
* CALLBACKS can use it like they want.
*/
struct option {
enum parse_opt_type type;
int short_name;
const char *long_name;
void *value;
const char *argh;
const char *help;
int flags;
parse_opt_cb *callback;
intptr_t defval;
};
#define OPT_END() { .type = OPTION_END }
#define OPT_ARGUMENT(l, h) { .type = OPTION_ARGUMENT, .long_name = (l), .help = (h) }
#define OPT_GROUP(h) { .type = OPTION_GROUP, .help = (h) }
#define OPT_BIT(s, l, v, h, b) { .type = OPTION_BIT, .short_name = (s), .long_name = (l), .value = (v), .help = (h), .defval = (b) }
#define OPT_BOOLEAN(s, l, v, h) { .type = OPTION_BOOLEAN, .short_name = (s), .long_name = (l), .value = (v), .help = (h) }
#define OPT_SET_INT(s, l, v, h, i) { .type = OPTION_SET_INT, .short_name = (s), .long_name = (l), .value = (v), .help = (h), .defval = (i) }
#define OPT_SET_PTR(s, l, v, h, p) { .type = OPTION_SET_PTR, .short_name = (s), .long_name = (l), .value = (v), .help = (h), .defval = (p) }
#define OPT_INTEGER(s, l, v, h) { .type = OPTION_INTEGER, .short_name = (s), .long_name = (l), .value = (v), .help = (h) }
#define OPT_LONG(s, l, v, h) { .type = OPTION_LONG, .short_name = (s), .long_name = (l), .value = (v), .help = (h) }
#define OPT_STRING(s, l, v, a, h) { .type = OPTION_STRING, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h) }
#define OPT_DATE(s, l, v, h) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f) }
#define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .flags = PARSE_OPT_NOARG }
#define OPT_CALLBACK_DEFAULT(s, l, v, a, h, f, d) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d, .flags = PARSE_OPT_LASTARG_DEFAULT }
/* parse_options() will filter out the processed options and leave the
* non-option argments in argv[].
* Returns the number of arguments left in argv[].
*/
extern int parse_options(int argc, const char **argv,
const struct option *options,
const char * const usagestr[], int flags);
extern NORETURN void usage_with_options(const char * const *usagestr,
const struct option *options);
/*----- incremantal advanced APIs -----*/
enum {
PARSE_OPT_HELP = -1,
PARSE_OPT_DONE,
PARSE_OPT_UNKNOWN,
};
/*
* It's okay for the caller to consume argv/argc in the usual way.
* Other fields of that structure are private to parse-options and should not
* be modified in any way.
*/
struct parse_opt_ctx_t {
const char **argv;
const char **out;
int argc, cpidx;
const char *opt;
int flags;
};
extern int parse_options_usage(const char * const *usagestr,
const struct option *opts);
extern void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, int flags);
extern int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[]);
extern int parse_options_end(struct parse_opt_ctx_t *ctx);
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
#define OPT__VERBOSITY(var) \
{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
#define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run")
#define OPT__ABBREV(var) \
{ OPTION_CALLBACK, 0, "abbrev", (var), "n", \
"use <n> digits to display SHA-1s", \
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
extern const char *parse_options_fix_filename(const char *prefix, const char *file);
#endif

View File

@@ -0,0 +1,358 @@
/*
* I'm tired of doing "vsnprintf()" etc just to open a
* file, so here's a "return static buffer with printf"
* interface for paths.
*
* It's obviously not thread-safe. Sue me. But it's quite
* useful for doing things like
*
* f = open(mkpath("%s/%s.perf", base, name), O_RDONLY);
*
* which is what it's designed for.
*/
#include "cache.h"
static char bad_path[] = "/bad-path/";
/*
* Two hacks:
*/
static const char *get_perf_dir(void)
{
return ".";
}
size_t strlcpy(char *dest, const char *src, size_t size)
{
size_t ret = strlen(src);
if (size) {
size_t len = (ret >= size) ? size - 1 : ret;
memcpy(dest, src, len);
dest[len] = '\0';
}
return ret;
}
static char *get_pathname(void)
{
static char pathname_array[4][PATH_MAX];
static int idx;
return pathname_array[3 & ++idx];
}
static char *cleanup_path(char *path)
{
/* Clean it up */
if (!memcmp(path, "./", 2)) {
path += 2;
while (*path == '/')
path++;
}
return path;
}
char *mksnpath(char *buf, size_t n, const char *fmt, ...)
{
va_list args;
unsigned len;
va_start(args, fmt);
len = vsnprintf(buf, n, fmt, args);
va_end(args);
if (len >= n) {
strlcpy(buf, bad_path, n);
return buf;
}
return cleanup_path(buf);
}
static char *perf_vsnpath(char *buf, size_t n, const char *fmt, va_list args)
{
const char *perf_dir = get_perf_dir();
size_t len;
len = strlen(perf_dir);
if (n < len + 1)
goto bad;
memcpy(buf, perf_dir, len);
if (len && !is_dir_sep(perf_dir[len-1]))
buf[len++] = '/';
len += vsnprintf(buf + len, n - len, fmt, args);
if (len >= n)
goto bad;
return cleanup_path(buf);
bad:
strlcpy(buf, bad_path, n);
return buf;
}
char *perf_snpath(char *buf, size_t n, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
(void)perf_vsnpath(buf, n, fmt, args);
va_end(args);
return buf;
}
char *perf_pathdup(const char *fmt, ...)
{
char path[PATH_MAX];
va_list args;
va_start(args, fmt);
(void)perf_vsnpath(path, sizeof(path), fmt, args);
va_end(args);
return xstrdup(path);
}
char *mkpath(const char *fmt, ...)
{
va_list args;
unsigned len;
char *pathname = get_pathname();
va_start(args, fmt);
len = vsnprintf(pathname, PATH_MAX, fmt, args);
va_end(args);
if (len >= PATH_MAX)
return bad_path;
return cleanup_path(pathname);
}
char *perf_path(const char *fmt, ...)
{
const char *perf_dir = get_perf_dir();
char *pathname = get_pathname();
va_list args;
unsigned len;
len = strlen(perf_dir);
if (len > PATH_MAX-100)
return bad_path;
memcpy(pathname, perf_dir, len);
if (len && perf_dir[len-1] != '/')
pathname[len++] = '/';
va_start(args, fmt);
len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
va_end(args);
if (len >= PATH_MAX)
return bad_path;
return cleanup_path(pathname);
}
/* perf_mkstemp() - create tmp file honoring TMPDIR variable */
int perf_mkstemp(char *path, size_t len, const char *template)
{
const char *tmp;
size_t n;
tmp = getenv("TMPDIR");
if (!tmp)
tmp = "/tmp";
n = snprintf(path, len, "%s/%s", tmp, template);
if (len <= n) {
errno = ENAMETOOLONG;
return -1;
}
return mkstemp(path);
}
const char *make_relative_path(const char *abs_path, const char *base)
{
static char buf[PATH_MAX + 1];
int baselen;
if (!base)
return abs_path;
baselen = strlen(base);
if (prefixcmp(abs_path, base))
return abs_path;
if (abs_path[baselen] == '/')
baselen++;
else if (base[baselen - 1] != '/')
return abs_path;
strcpy(buf, abs_path + baselen);
return buf;
}
/*
* It is okay if dst == src, but they should not overlap otherwise.
*
* Performs the following normalizations on src, storing the result in dst:
* - Ensures that components are separated by '/' (Windows only)
* - Squashes sequences of '/'.
* - Removes "." components.
* - Removes ".." components, and the components the precede them.
* Returns failure (non-zero) if a ".." component appears as first path
* component anytime during the normalization. Otherwise, returns success (0).
*
* Note that this function is purely textual. It does not follow symlinks,
* verify the existence of the path, or make any system calls.
*/
int normalize_path_copy(char *dst, const char *src)
{
char *dst0;
if (has_dos_drive_prefix(src)) {
*dst++ = *src++;
*dst++ = *src++;
}
dst0 = dst;
if (is_dir_sep(*src)) {
*dst++ = '/';
while (is_dir_sep(*src))
src++;
}
for (;;) {
char c = *src;
/*
* A path component that begins with . could be
* special:
* (1) "." and ends -- ignore and terminate.
* (2) "./" -- ignore them, eat slash and continue.
* (3) ".." and ends -- strip one and terminate.
* (4) "../" -- strip one, eat slash and continue.
*/
if (c == '.') {
if (!src[1]) {
/* (1) */
src++;
} else if (is_dir_sep(src[1])) {
/* (2) */
src += 2;
while (is_dir_sep(*src))
src++;
continue;
} else if (src[1] == '.') {
if (!src[2]) {
/* (3) */
src += 2;
goto up_one;
} else if (is_dir_sep(src[2])) {
/* (4) */
src += 3;
while (is_dir_sep(*src))
src++;
goto up_one;
}
}
}
/* copy up to the next '/', and eat all '/' */
while ((c = *src++) != '\0' && !is_dir_sep(c))
*dst++ = c;
if (is_dir_sep(c)) {
*dst++ = '/';
while (is_dir_sep(c))
c = *src++;
src--;
} else if (!c)
break;
continue;
up_one:
/*
* dst0..dst is prefix portion, and dst[-1] is '/';
* go up one level.
*/
dst--; /* go to trailing '/' */
if (dst <= dst0)
return -1;
/* Windows: dst[-1] cannot be backslash anymore */
while (dst0 < dst && dst[-1] != '/')
dst--;
}
*dst = '\0';
return 0;
}
/*
* path = Canonical absolute path
* prefix_list = Colon-separated list of absolute paths
*
* Determines, for each path in prefix_list, whether the "prefix" really
* is an ancestor directory of path. Returns the length of the longest
* ancestor directory, excluding any trailing slashes, or -1 if no prefix
* is an ancestor. (Note that this means 0 is returned if prefix_list is
* "/".) "/foo" is not considered an ancestor of "/foobar". Directories
* are not considered to be their own ancestors. path must be in a
* canonical form: empty components, or "." or ".." components are not
* allowed. prefix_list may be null, which is like "".
*/
int longest_ancestor_length(const char *path, const char *prefix_list)
{
char buf[PATH_MAX+1];
const char *ceil, *colon;
int len, max_len = -1;
if (prefix_list == NULL || !strcmp(path, "/"))
return -1;
for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
len = colon - ceil;
if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
continue;
strlcpy(buf, ceil, len+1);
if (normalize_path_copy(buf, buf) < 0)
continue;
len = strlen(buf);
if (len > 0 && buf[len-1] == '/')
buf[--len] = '\0';
if (!strncmp(path, buf, len) &&
path[len] == '/' &&
len > max_len) {
max_len = len;
}
}
return max_len;
}
/* strip arbitrary amount of directory separators at end of path */
static inline int chomp_trailing_dir_sep(const char *path, int len)
{
while (len && is_dir_sep(path[len - 1]))
len--;
return len;
}
/*
* If path ends with suffix (complete path components), returns the
* part before suffix (sans trailing directory separators).
* Otherwise returns NULL.
*/
char *strip_path_suffix(const char *path, const char *suffix)
{
int path_len = strlen(path), suffix_len = strlen(suffix);
while (suffix_len) {
if (!path_len)
return NULL;
if (is_dir_sep(path[path_len - 1])) {
if (!is_dir_sep(suffix[suffix_len - 1]))
return NULL;
path_len = chomp_trailing_dir_sep(path, path_len);
suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
}
else if (path[--path_len] != suffix[--suffix_len])
return NULL;
}
if (path_len && !is_dir_sep(path[path_len - 1]))
return NULL;
return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
}

View File

@@ -0,0 +1,485 @@
#include "cache.h"
#include "quote.h"
int quote_path_fully = 1;
/* Help to copy the thing properly quoted for the shell safety.
* any single quote is replaced with '\'', any exclamation point
* is replaced with '\!', and the whole thing is enclosed in a
*
* E.g.
* original sq_quote result
* name ==> name ==> 'name'
* a b ==> a b ==> 'a b'
* a'b ==> a'\''b ==> 'a'\''b'
* a!b ==> a'\!'b ==> 'a'\!'b'
*/
static inline int need_bs_quote(char c)
{
return (c == '\'' || c == '!');
}
void sq_quote_buf(struct strbuf *dst, const char *src)
{
char *to_free = NULL;
if (dst->buf == src)
to_free = strbuf_detach(dst, NULL);
strbuf_addch(dst, '\'');
while (*src) {
size_t len = strcspn(src, "'!");
strbuf_add(dst, src, len);
src += len;
while (need_bs_quote(*src)) {
strbuf_addstr(dst, "'\\");
strbuf_addch(dst, *src++);
strbuf_addch(dst, '\'');
}
}
strbuf_addch(dst, '\'');
free(to_free);
}
void sq_quote_print(FILE *stream, const char *src)
{
char c;
fputc('\'', stream);
while ((c = *src++)) {
if (need_bs_quote(c)) {
fputs("'\\", stream);
fputc(c, stream);
fputc('\'', stream);
} else {
fputc(c, stream);
}
}
fputc('\'', stream);
}
void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
{
int i;
/* Copy into destination buffer. */
strbuf_grow(dst, 255);
for (i = 0; argv[i]; ++i) {
strbuf_addch(dst, ' ');
sq_quote_buf(dst, argv[i]);
if (maxlen && dst->len > maxlen)
die("Too many or long arguments");
}
}
char *sq_dequote_step(char *arg, char **next)
{
char *dst = arg;
char *src = arg;
char c;
if (*src != '\'')
return NULL;
for (;;) {
c = *++src;
if (!c)
return NULL;
if (c != '\'') {
*dst++ = c;
continue;
}
/* We stepped out of sq */
switch (*++src) {
case '\0':
*dst = 0;
if (next)
*next = NULL;
return arg;
case '\\':
c = *++src;
if (need_bs_quote(c) && *++src == '\'') {
*dst++ = c;
continue;
}
/* Fallthrough */
default:
if (!next || !isspace(*src))
return NULL;
do {
c = *++src;
} while (isspace(c));
*dst = 0;
*next = src;
return arg;
}
}
}
char *sq_dequote(char *arg)
{
return sq_dequote_step(arg, NULL);
}
int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
{
char *next = arg;
if (!*arg)
return 0;
do {
char *dequoted = sq_dequote_step(next, &next);
if (!dequoted)
return -1;
ALLOC_GROW(*argv, *nr + 1, *alloc);
(*argv)[(*nr)++] = dequoted;
} while (next);
return 0;
}
/* 1 means: quote as octal
* 0 means: quote as octal if (quote_path_fully)
* -1 means: never quote
* c: quote as "\\c"
*/
#define X8(x) x, x, x, x, x, x, x, x
#define X16(x) X8(x), X8(x)
static signed char const sq_lookup[256] = {
/* 0 1 2 3 4 5 6 7 */
/* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a',
/* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1,
/* 0x10 */ X16(1),
/* 0x20 */ -1, -1, '"', -1, -1, -1, -1, -1,
/* 0x28 */ X16(-1), X16(-1), X16(-1),
/* 0x58 */ -1, -1, -1, -1,'\\', -1, -1, -1,
/* 0x60 */ X16(-1), X8(-1),
/* 0x78 */ -1, -1, -1, -1, -1, -1, -1, 1,
/* 0x80 */ /* set to 0 */
};
static inline int sq_must_quote(char c)
{
return sq_lookup[(unsigned char)c] + quote_path_fully > 0;
}
/*
* Returns the longest prefix not needing a quote up to maxlen if
* positive.
* This stops at the first \0 because it's marked as a character
* needing an escape.
*/
static ssize_t next_quote_pos(const char *s, ssize_t maxlen)
{
ssize_t len;
if (maxlen < 0) {
for (len = 0; !sq_must_quote(s[len]); len++);
} else {
for (len = 0; len < maxlen && !sq_must_quote(s[len]); len++);
}
return len;
}
/*
* C-style name quoting.
*
* (1) if sb and fp are both NULL, inspect the input name and counts the
* number of bytes that are needed to hold c_style quoted version of name,
* counting the double quotes around it but not terminating NUL, and
* returns it.
* However, if name does not need c_style quoting, it returns 0.
*
* (2) if sb or fp are not NULL, it emits the c_style quoted version
* of name, enclosed with double quotes if asked and needed only.
* Return value is the same as in (1).
*/
static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
struct strbuf *sb, FILE *fp, int no_dq)
{
#define EMIT(c) \
do { \
if (sb) strbuf_addch(sb, (c)); \
if (fp) fputc((c), fp); \
count++; \
} while (0)
#define EMITBUF(s, l) \
do { \
int __ret; \
if (sb) strbuf_add(sb, (s), (l)); \
if (fp) __ret = fwrite((s), (l), 1, fp); \
count += (l); \
} while (0)
ssize_t len, count = 0;
const char *p = name;
for (;;) {
int ch;
len = next_quote_pos(p, maxlen);
if (len == maxlen || !p[len])
break;
if (!no_dq && p == name)
EMIT('"');
EMITBUF(p, len);
EMIT('\\');
p += len;
ch = (unsigned char)*p++;
if (sq_lookup[ch] >= ' ') {
EMIT(sq_lookup[ch]);
} else {
EMIT(((ch >> 6) & 03) + '0');
EMIT(((ch >> 3) & 07) + '0');
EMIT(((ch >> 0) & 07) + '0');
}
}
EMITBUF(p, len);
if (p == name) /* no ending quote needed */
return 0;
if (!no_dq)
EMIT('"');
return count;
}
size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq)
{
return quote_c_style_counted(name, -1, sb, fp, nodq);
}
void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, int nodq)
{
if (quote_c_style(prefix, NULL, NULL, 0) ||
quote_c_style(path, NULL, NULL, 0)) {
if (!nodq)
strbuf_addch(sb, '"');
quote_c_style(prefix, sb, NULL, 1);
quote_c_style(path, sb, NULL, 1);
if (!nodq)
strbuf_addch(sb, '"');
} else {
strbuf_addstr(sb, prefix);
strbuf_addstr(sb, path);
}
}
void write_name_quoted(const char *name, FILE *fp, int terminator)
{
if (terminator) {
quote_c_style(name, NULL, fp, 0);
} else {
fputs(name, fp);
}
fputc(terminator, fp);
}
void write_name_quotedpfx(const char *pfx, ssize_t pfxlen,
const char *name, FILE *fp, int terminator)
{
int needquote = 0;
if (terminator) {
needquote = next_quote_pos(pfx, pfxlen) < pfxlen
|| name[next_quote_pos(name, -1)];
}
if (needquote) {
fputc('"', fp);
quote_c_style_counted(pfx, pfxlen, NULL, fp, 1);
quote_c_style(name, NULL, fp, 1);
fputc('"', fp);
} else {
int ret;
ret = fwrite(pfx, pfxlen, 1, fp);
fputs(name, fp);
}
fputc(terminator, fp);
}
/* quote path as relative to the given prefix */
char *quote_path_relative(const char *in, int len,
struct strbuf *out, const char *prefix)
{
int needquote;
if (len < 0)
len = strlen(in);
/* "../" prefix itself does not need quoting, but "in" might. */
needquote = (next_quote_pos(in, len) < len);
strbuf_setlen(out, 0);
strbuf_grow(out, len);
if (needquote)
strbuf_addch(out, '"');
if (prefix) {
int off = 0;
while (off < len && prefix[off] && prefix[off] == in[off])
if (prefix[off] == '/') {
prefix += off + 1;
in += off + 1;
len -= off + 1;
off = 0;
} else
off++;
for (; *prefix; prefix++)
if (*prefix == '/')
strbuf_addstr(out, "../");
}
quote_c_style_counted (in, len, out, NULL, 1);
if (needquote)
strbuf_addch(out, '"');
if (!out->len)
strbuf_addstr(out, "./");
return out->buf;
}
/*
* C-style name unquoting.
*
* Quoted should point at the opening double quote.
* + Returns 0 if it was able to unquote the string properly, and appends the
* result in the strbuf `sb'.
* + Returns -1 in case of error, and doesn't touch the strbuf. Though note
* that this function will allocate memory in the strbuf, so calling
* strbuf_release is mandatory whichever result unquote_c_style returns.
*
* Updates endp pointer to point at one past the ending double quote if given.
*/
int unquote_c_style(struct strbuf *sb, const char *quoted, const char **endp)
{
size_t oldlen = sb->len, len;
int ch, ac;
if (*quoted++ != '"')
return -1;
for (;;) {
len = strcspn(quoted, "\"\\");
strbuf_add(sb, quoted, len);
quoted += len;
switch (*quoted++) {
case '"':
if (endp)
*endp = quoted;
return 0;
case '\\':
break;
default:
goto error;
}
switch ((ch = *quoted++)) {
case 'a': ch = '\a'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'v': ch = '\v'; break;
case '\\': case '"':
break; /* verbatim */
/* octal values with first digit over 4 overflow */
case '0': case '1': case '2': case '3':
ac = ((ch - '0') << 6);
if ((ch = *quoted++) < '0' || '7' < ch)
goto error;
ac |= ((ch - '0') << 3);
if ((ch = *quoted++) < '0' || '7' < ch)
goto error;
ac |= (ch - '0');
ch = ac;
break;
default:
goto error;
}
strbuf_addch(sb, ch);
}
error:
strbuf_setlen(sb, oldlen);
return -1;
}
/* quoting as a string literal for other languages */
void perl_quote_print(FILE *stream, const char *src)
{
const char sq = '\'';
const char bq = '\\';
char c;
fputc(sq, stream);
while ((c = *src++)) {
if (c == sq || c == bq)
fputc(bq, stream);
fputc(c, stream);
}
fputc(sq, stream);
}
void python_quote_print(FILE *stream, const char *src)
{
const char sq = '\'';
const char bq = '\\';
const char nl = '\n';
char c;
fputc(sq, stream);
while ((c = *src++)) {
if (c == nl) {
fputc(bq, stream);
fputc('n', stream);
continue;
}
if (c == sq || c == bq)
fputc(bq, stream);
fputc(c, stream);
}
fputc(sq, stream);
}
void tcl_quote_print(FILE *stream, const char *src)
{
char c;
fputc('"', stream);
while ((c = *src++)) {
switch (c) {
case '[': case ']':
case '{': case '}':
case '$': case '\\': case '"':
fputc('\\', stream);
default:
fputc(c, stream);
break;
case '\f':
fputs("\\f", stream);
break;
case '\r':
fputs("\\r", stream);
break;
case '\n':
fputs("\\n", stream);
break;
case '\t':
fputs("\\t", stream);
break;
case '\v':
fputs("\\v", stream);
break;
}
}
fputc('"', stream);
}

View File

@@ -0,0 +1,68 @@
#ifndef QUOTE_H
#define QUOTE_H
#include <stddef.h>
#include <stdio.h>
/* Help to copy the thing properly quoted for the shell safety.
* any single quote is replaced with '\'', any exclamation point
* is replaced with '\!', and the whole thing is enclosed in a
* single quote pair.
*
* For example, if you are passing the result to system() as an
* argument:
*
* sprintf(cmd, "foobar %s %s", sq_quote(arg0), sq_quote(arg1))
*
* would be appropriate. If the system() is going to call ssh to
* run the command on the other side:
*
* sprintf(cmd, "git-diff-tree %s %s", sq_quote(arg0), sq_quote(arg1));
* sprintf(rcmd, "ssh %s %s", sq_util/quote.host), sq_quote(cmd));
*
* Note that the above examples leak memory! Remember to free result from
* sq_quote() in a real application.
*
* sq_quote_buf() writes to an existing buffer of specified size; it
* will return the number of characters that would have been written
* excluding the final null regardless of the buffer size.
*/
extern void sq_quote_print(FILE *stream, const char *src);
extern void sq_quote_buf(struct strbuf *, const char *src);
extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen);
/* This unwraps what sq_quote() produces in place, but returns
* NULL if the input does not look like what sq_quote would have
* produced.
*/
extern char *sq_dequote(char *);
/*
* Same as the above, but can be used to unwrap many arguments in the
* same string separated by space. "next" is changed to point to the
* next argument that should be passed as first parameter. When there
* is no more argument to be dequoted, "next" is updated to point to NULL.
*/
extern char *sq_dequote_step(char *arg, char **next);
extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
extern void write_name_quoted(const char *name, FILE *, int terminator);
extern void write_name_quotedpfx(const char *pfx, ssize_t pfxlen,
const char *name, FILE *, int terminator);
/* quote path as relative to the given prefix */
char *quote_path_relative(const char *in, int len,
struct strbuf *out, const char *prefix);
/* quoting as a string literal for other languages */
extern void perl_quote_print(FILE *stream, const char *src);
extern void python_quote_print(FILE *stream, const char *src);
extern void tcl_quote_print(FILE *stream, const char *src);
#endif

View File

@@ -0,0 +1,304 @@
#include "cache.h"
#include "run-command.h"
#include "exec_cmd.h"
static inline void close_pair(int fd[2])
{
close(fd[0]);
close(fd[1]);
}
static inline void dup_devnull(int to)
{
int fd = open("/dev/null", O_RDWR);
dup2(fd, to);
close(fd);
}
int start_command(struct child_process *cmd)
{
int need_in, need_out, need_err;
int fdin[2], fdout[2], fderr[2];
/*
* In case of errors we must keep the promise to close FDs
* that have been passed in via ->in and ->out.
*/
need_in = !cmd->no_stdin && cmd->in < 0;
if (need_in) {
if (pipe(fdin) < 0) {
if (cmd->out > 0)
close(cmd->out);
return -ERR_RUN_COMMAND_PIPE;
}
cmd->in = fdin[1];
}
need_out = !cmd->no_stdout
&& !cmd->stdout_to_stderr
&& cmd->out < 0;
if (need_out) {
if (pipe(fdout) < 0) {
if (need_in)
close_pair(fdin);
else if (cmd->in)
close(cmd->in);
return -ERR_RUN_COMMAND_PIPE;
}
cmd->out = fdout[0];
}
need_err = !cmd->no_stderr && cmd->err < 0;
if (need_err) {
if (pipe(fderr) < 0) {
if (need_in)
close_pair(fdin);
else if (cmd->in)
close(cmd->in);
if (need_out)
close_pair(fdout);
else if (cmd->out)
close(cmd->out);
return -ERR_RUN_COMMAND_PIPE;
}
cmd->err = fderr[0];
}
fflush(NULL);
cmd->pid = fork();
if (!cmd->pid) {
if (cmd->no_stdin)
dup_devnull(0);
else if (need_in) {
dup2(fdin[0], 0);
close_pair(fdin);
} else if (cmd->in) {
dup2(cmd->in, 0);
close(cmd->in);
}
if (cmd->no_stderr)
dup_devnull(2);
else if (need_err) {
dup2(fderr[1], 2);
close_pair(fderr);
}
if (cmd->no_stdout)
dup_devnull(1);
else if (cmd->stdout_to_stderr)
dup2(2, 1);
else if (need_out) {
dup2(fdout[1], 1);
close_pair(fdout);
} else if (cmd->out > 1) {
dup2(cmd->out, 1);
close(cmd->out);
}
if (cmd->dir && chdir(cmd->dir))
die("exec %s: cd to %s failed (%s)", cmd->argv[0],
cmd->dir, strerror(errno));
if (cmd->env) {
for (; *cmd->env; cmd->env++) {
if (strchr(*cmd->env, '='))
putenv((char*)*cmd->env);
else
unsetenv(*cmd->env);
}
}
if (cmd->preexec_cb)
cmd->preexec_cb();
if (cmd->perf_cmd) {
execv_perf_cmd(cmd->argv);
} else {
execvp(cmd->argv[0], (char *const*) cmd->argv);
}
exit(127);
}
if (cmd->pid < 0) {
int err = errno;
if (need_in)
close_pair(fdin);
else if (cmd->in)
close(cmd->in);
if (need_out)
close_pair(fdout);
else if (cmd->out)
close(cmd->out);
if (need_err)
close_pair(fderr);
return err == ENOENT ?
-ERR_RUN_COMMAND_EXEC :
-ERR_RUN_COMMAND_FORK;
}
if (need_in)
close(fdin[0]);
else if (cmd->in)
close(cmd->in);
if (need_out)
close(fdout[1]);
else if (cmd->out)
close(cmd->out);
if (need_err)
close(fderr[1]);
return 0;
}
static int wait_or_whine(pid_t pid)
{
for (;;) {
int status, code;
pid_t waiting = waitpid(pid, &status, 0);
if (waiting < 0) {
if (errno == EINTR)
continue;
error("waitpid failed (%s)", strerror(errno));
return -ERR_RUN_COMMAND_WAITPID;
}
if (waiting != pid)
return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
if (WIFSIGNALED(status))
return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
if (!WIFEXITED(status))
return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
code = WEXITSTATUS(status);
switch (code) {
case 127:
return -ERR_RUN_COMMAND_EXEC;
case 0:
return 0;
default:
return -code;
}
}
}
int finish_command(struct child_process *cmd)
{
return wait_or_whine(cmd->pid);
}
int run_command(struct child_process *cmd)
{
int code = start_command(cmd);
if (code)
return code;
return finish_command(cmd);
}
static void prepare_run_command_v_opt(struct child_process *cmd,
const char **argv,
int opt)
{
memset(cmd, 0, sizeof(*cmd));
cmd->argv = argv;
cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
cmd->perf_cmd = opt & RUN_PERF_CMD ? 1 : 0;
cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
}
int run_command_v_opt(const char **argv, int opt)
{
struct child_process cmd;
prepare_run_command_v_opt(&cmd, argv, opt);
return run_command(&cmd);
}
int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
{
struct child_process cmd;
prepare_run_command_v_opt(&cmd, argv, opt);
cmd.dir = dir;
cmd.env = env;
return run_command(&cmd);
}
int start_async(struct async *async)
{
int pipe_out[2];
if (pipe(pipe_out) < 0)
return error("cannot create pipe: %s", strerror(errno));
async->out = pipe_out[0];
/* Flush stdio before fork() to avoid cloning buffers */
fflush(NULL);
async->pid = fork();
if (async->pid < 0) {
error("fork (async) failed: %s", strerror(errno));
close_pair(pipe_out);
return -1;
}
if (!async->pid) {
close(pipe_out[0]);
exit(!!async->proc(pipe_out[1], async->data));
}
close(pipe_out[1]);
return 0;
}
int finish_async(struct async *async)
{
int ret = 0;
if (wait_or_whine(async->pid))
ret = error("waitpid (async) failed");
return ret;
}
int run_hook(const char *index_file, const char *name, ...)
{
struct child_process hook;
const char **argv = NULL, *env[2];
char idx[PATH_MAX];
va_list args;
int ret;
size_t i = 0, alloc = 0;
if (access(perf_path("hooks/%s", name), X_OK) < 0)
return 0;
va_start(args, name);
ALLOC_GROW(argv, i + 1, alloc);
argv[i++] = perf_path("hooks/%s", name);
while (argv[i-1]) {
ALLOC_GROW(argv, i + 1, alloc);
argv[i++] = va_arg(args, const char *);
}
va_end(args);
memset(&hook, 0, sizeof(hook));
hook.argv = argv;
hook.no_stdin = 1;
hook.stdout_to_stderr = 1;
if (index_file) {
snprintf(idx, sizeof(idx), "PERF_INDEX_FILE=%s", index_file);
env[0] = idx;
env[1] = NULL;
hook.env = env;
}
ret = start_command(&hook);
free(argv);
if (ret) {
warning("Could not spawn %s", argv[0]);
return ret;
}
ret = finish_command(&hook);
if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
warning("%s exited due to uncaught signal", argv[0]);
return ret;
}

View File

@@ -0,0 +1,88 @@
#ifndef RUN_COMMAND_H
#define RUN_COMMAND_H
enum {
ERR_RUN_COMMAND_FORK = 10000,
ERR_RUN_COMMAND_EXEC,
ERR_RUN_COMMAND_PIPE,
ERR_RUN_COMMAND_WAITPID,
ERR_RUN_COMMAND_WAITPID_WRONG_PID,
ERR_RUN_COMMAND_WAITPID_SIGNAL,
ERR_RUN_COMMAND_WAITPID_NOEXIT,
};
#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK)
struct child_process {
const char **argv;
pid_t pid;
/*
* Using .in, .out, .err:
* - Specify 0 for no redirections (child inherits stdin, stdout,
* stderr from parent).
* - Specify -1 to have a pipe allocated as follows:
* .in: returns the writable pipe end; parent writes to it,
* the readable pipe end becomes child's stdin
* .out, .err: returns the readable pipe end; parent reads from
* it, the writable pipe end becomes child's stdout/stderr
* The caller of start_command() must close the returned FDs
* after it has completed reading from/writing to it!
* - Specify > 0 to set a channel to a particular FD as follows:
* .in: a readable FD, becomes child's stdin
* .out: a writable FD, becomes child's stdout/stderr
* .err > 0 not supported
* The specified FD is closed by start_command(), even in case
* of errors!
*/
int in;
int out;
int err;
const char *dir;
const char *const *env;
unsigned no_stdin:1;
unsigned no_stdout:1;
unsigned no_stderr:1;
unsigned perf_cmd:1; /* if this is to be perf sub-command */
unsigned stdout_to_stderr:1;
void (*preexec_cb)(void);
};
int start_command(struct child_process *);
int finish_command(struct child_process *);
int run_command(struct child_process *);
extern int run_hook(const char *index_file, const char *name, ...);
#define RUN_COMMAND_NO_STDIN 1
#define RUN_PERF_CMD 2 /*If this is to be perf sub-command */
#define RUN_COMMAND_STDOUT_TO_STDERR 4
int run_command_v_opt(const char **argv, int opt);
/*
* env (the environment) is to be formatted like environ: "VAR=VALUE".
* To unset an environment variable use just "VAR".
*/
int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
/*
* The purpose of the following functions is to feed a pipe by running
* a function asynchronously and providing output that the caller reads.
*
* It is expected that no synchronization and mutual exclusion between
* the caller and the feed function is necessary so that the function
* can run in a thread without interfering with the caller.
*/
struct async {
/*
* proc writes to fd and closes it;
* returns 0 on success, non-zero on failure
*/
int (*proc)(int fd, void *data);
void *data;
int out; /* caller reads from here and closes it */
pid_t pid;
};
int start_async(struct async *async);
int finish_async(struct async *async);
#endif

View File

@@ -0,0 +1,52 @@
#include "sigchain.h"
#include "cache.h"
#define SIGCHAIN_MAX_SIGNALS 32
struct sigchain_signal {
sigchain_fun *old;
int n;
int alloc;
};
static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
static void check_signum(int sig)
{
if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
die("BUG: signal out of range: %d", sig);
}
int sigchain_push(int sig, sigchain_fun f)
{
struct sigchain_signal *s = signals + sig;
check_signum(sig);
ALLOC_GROW(s->old, s->n + 1, s->alloc);
s->old[s->n] = signal(sig, f);
if (s->old[s->n] == SIG_ERR)
return -1;
s->n++;
return 0;
}
int sigchain_pop(int sig)
{
struct sigchain_signal *s = signals + sig;
check_signum(sig);
if (s->n < 1)
return 0;
if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
return -1;
s->n--;
return 0;
}
void sigchain_push_common(sigchain_fun f)
{
sigchain_push(SIGINT, f);
sigchain_push(SIGHUP, f);
sigchain_push(SIGTERM, f);
sigchain_push(SIGQUIT, f);
sigchain_push(SIGPIPE, f);
}

View File

@@ -0,0 +1,11 @@
#ifndef SIGCHAIN_H
#define SIGCHAIN_H
typedef void (*sigchain_fun)(int);
int sigchain_push(int sig, sigchain_fun f);
int sigchain_pop(int sig);
void sigchain_push_common(sigchain_fun f);
#endif /* SIGCHAIN_H */

View File

@@ -0,0 +1,360 @@
#include "cache.h"
int prefixcmp(const char *str, const char *prefix)
{
for (; ; str++, prefix++)
if (!*prefix)
return 0;
else if (*str != *prefix)
return (unsigned char)*prefix - (unsigned char)*str;
}
/*
* Used as the default ->buf value, so that people can always assume
* buf is non NULL and ->buf is NUL terminated even for a freshly
* initialized strbuf.
*/
char strbuf_slopbuf[1];
void strbuf_init(struct strbuf *sb, ssize_t hint)
{
sb->alloc = sb->len = 0;
sb->buf = strbuf_slopbuf;
if (hint)
strbuf_grow(sb, hint);
}
void strbuf_release(struct strbuf *sb)
{
if (sb->alloc) {
free(sb->buf);
strbuf_init(sb, 0);
}
}
char *strbuf_detach(struct strbuf *sb, size_t *sz)
{
char *res = sb->alloc ? sb->buf : NULL;
if (sz)
*sz = sb->len;
strbuf_init(sb, 0);
return res;
}
void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
{
strbuf_release(sb);
sb->buf = buf;
sb->len = len;
sb->alloc = alloc;
strbuf_grow(sb, 0);
sb->buf[sb->len] = '\0';
}
void strbuf_grow(struct strbuf *sb, size_t extra)
{
if (sb->len + extra + 1 <= sb->len)
die("you want to use way too much memory");
if (!sb->alloc)
sb->buf = NULL;
ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
}
void strbuf_trim(struct strbuf *sb)
{
char *b = sb->buf;
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
sb->len--;
while (sb->len > 0 && isspace(*b)) {
b++;
sb->len--;
}
memmove(sb->buf, b, sb->len);
sb->buf[sb->len] = '\0';
}
void strbuf_rtrim(struct strbuf *sb)
{
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
sb->len--;
sb->buf[sb->len] = '\0';
}
void strbuf_ltrim(struct strbuf *sb)
{
char *b = sb->buf;
while (sb->len > 0 && isspace(*b)) {
b++;
sb->len--;
}
memmove(sb->buf, b, sb->len);
sb->buf[sb->len] = '\0';
}
void strbuf_tolower(struct strbuf *sb)
{
unsigned int i;
for (i = 0; i < sb->len; i++)
sb->buf[i] = tolower(sb->buf[i]);
}
struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
{
int alloc = 2, pos = 0;
char *n, *p;
struct strbuf **ret;
struct strbuf *t;
ret = calloc(alloc, sizeof(struct strbuf *));
p = n = sb->buf;
while (n < sb->buf + sb->len) {
int len;
n = memchr(n, delim, sb->len - (n - sb->buf));
if (pos + 1 >= alloc) {
alloc = alloc * 2;
ret = realloc(ret, sizeof(struct strbuf *) * alloc);
}
if (!n)
n = sb->buf + sb->len - 1;
len = n - p + 1;
t = malloc(sizeof(struct strbuf));
strbuf_init(t, len);
strbuf_add(t, p, len);
ret[pos] = t;
ret[++pos] = NULL;
p = ++n;
}
return ret;
}
void strbuf_list_free(struct strbuf **sbs)
{
struct strbuf **s = sbs;
while (*s) {
strbuf_release(*s);
free(*s++);
}
free(sbs);
}
int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
{
int len = a->len < b->len ? a->len: b->len;
int cmp = memcmp(a->buf, b->buf, len);
if (cmp)
return cmp;
return a->len < b->len ? -1: a->len != b->len;
}
void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
const void *data, size_t dlen)
{
if (pos + len < pos)
die("you want to use way too much memory");
if (pos > sb->len)
die("`pos' is too far after the end of the buffer");
if (pos + len > sb->len)
die("`pos + len' is too far after the end of the buffer");
if (dlen >= len)
strbuf_grow(sb, dlen - len);
memmove(sb->buf + pos + dlen,
sb->buf + pos + len,
sb->len - pos - len);
memcpy(sb->buf + pos, data, dlen);
strbuf_setlen(sb, sb->len + dlen - len);
}
void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
{
strbuf_splice(sb, pos, 0, data, len);
}
void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
{
strbuf_splice(sb, pos, len, NULL, 0);
}
void strbuf_add(struct strbuf *sb, const void *data, size_t len)
{
strbuf_grow(sb, len);
memcpy(sb->buf + sb->len, data, len);
strbuf_setlen(sb, sb->len + len);
}
void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
{
strbuf_grow(sb, len);
memcpy(sb->buf + sb->len, sb->buf + pos, len);
strbuf_setlen(sb, sb->len + len);
}
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
{
int len;
va_list ap;
if (!strbuf_avail(sb))
strbuf_grow(sb, 64);
va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_end(ap);
if (len < 0)
die("your vsnprintf is broken");
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_end(ap);
if (len > strbuf_avail(sb)) {
die("this should not happen, your snprintf is broken");
}
}
strbuf_setlen(sb, sb->len + len);
}
void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
void *context)
{
for (;;) {
const char *percent;
size_t consumed;
percent = strchrnul(format, '%');
strbuf_add(sb, format, percent - format);
if (!*percent)
break;
format = percent + 1;
consumed = fn(sb, format, context);
if (consumed)
format += consumed;
else
strbuf_addch(sb, '%');
}
}
size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
void *context)
{
struct strbuf_expand_dict_entry *e = context;
size_t len;
for (; e->placeholder && (len = strlen(e->placeholder)); e++) {
if (!strncmp(placeholder, e->placeholder, len)) {
if (e->value)
strbuf_addstr(sb, e->value);
return len;
}
}
return 0;
}
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
size_t oldalloc = sb->alloc;
strbuf_grow(sb, size);
res = fread(sb->buf + sb->len, 1, size, f);
if (res > 0)
strbuf_setlen(sb, sb->len + res);
else if (oldalloc == 0)
strbuf_release(sb);
return res;
}
ssize_t strbuf_read(struct strbuf *sb, int fd, ssize_t hint)
{
size_t oldlen = sb->len;
size_t oldalloc = sb->alloc;
strbuf_grow(sb, hint ? hint : 8192);
for (;;) {
ssize_t cnt;
cnt = read(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
if (cnt < 0) {
if (oldalloc == 0)
strbuf_release(sb);
else
strbuf_setlen(sb, oldlen);
return -1;
}
if (!cnt)
break;
sb->len += cnt;
strbuf_grow(sb, 8192);
}
sb->buf[sb->len] = '\0';
return sb->len - oldlen;
}
#define STRBUF_MAXLINK (2*PATH_MAX)
int strbuf_readlink(struct strbuf *sb, const char *path, ssize_t hint)
{
size_t oldalloc = sb->alloc;
if (hint < 32)
hint = 32;
while (hint < STRBUF_MAXLINK) {
ssize_t len;
strbuf_grow(sb, hint);
len = readlink(path, sb->buf, hint);
if (len < 0) {
if (errno != ERANGE)
break;
} else if (len < hint) {
strbuf_setlen(sb, len);
return 0;
}
/* .. the buffer was too small - try again */
hint *= 2;
}
if (oldalloc == 0)
strbuf_release(sb);
return -1;
}
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
{
int ch;
strbuf_grow(sb, 0);
if (feof(fp))
return EOF;
strbuf_reset(sb);
while ((ch = fgetc(fp)) != EOF) {
if (ch == term)
break;
strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
}
if (ch == EOF && sb->len == 0)
return EOF;
sb->buf[sb->len] = '\0';
return 0;
}
int strbuf_read_file(struct strbuf *sb, const char *path, ssize_t hint)
{
int fd, len;
fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
len = strbuf_read(sb, fd, hint);
close(fd);
if (len < 0)
return -1;
return len;
}

View File

@@ -0,0 +1,137 @@
#ifndef STRBUF_H
#define STRBUF_H
/*
* Strbuf's can be use in many ways: as a byte array, or to store arbitrary
* long, overflow safe strings.
*
* Strbufs has some invariants that are very important to keep in mind:
*
* 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
* build complex strings/buffers whose final size isn't easily known.
*
* It is NOT legal to copy the ->buf pointer away.
* `strbuf_detach' is the operation that detachs a buffer from its shell
* while keeping the shell valid wrt its invariants.
*
* 2. the ->buf member is a byte array that has at least ->len + 1 bytes
* allocated. The extra byte is used to store a '\0', allowing the ->buf
* member to be a valid C-string. Every strbuf function ensure this
* invariant is preserved.
*
* Note that it is OK to "play" with the buffer directly if you work it
* that way:
*
* strbuf_grow(sb, SOME_SIZE);
* ... Here, the memory array starting at sb->buf, and of length
* ... strbuf_avail(sb) is all yours, and you are sure that
* ... strbuf_avail(sb) is at least SOME_SIZE.
* strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
*
* Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
*
* Doing so is safe, though if it has to be done in many places, adding the
* missing API to the strbuf module is the way to go.
*
* XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
* even if it's true in the current implementation. Alloc is somehow a
* "private" member that should not be messed with.
*/
#include <assert.h>
extern char strbuf_slopbuf[];
struct strbuf {
size_t alloc;
size_t len;
char *buf;
};
#define STRBUF_INIT { 0, 0, strbuf_slopbuf }
/*----- strbuf life cycle -----*/
extern void strbuf_init(struct strbuf *buf, ssize_t hint);
extern void strbuf_release(struct strbuf *);
extern char *strbuf_detach(struct strbuf *, size_t *);
extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);
static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {
struct strbuf tmp = *a;
*a = *b;
*b = tmp;
}
/*----- strbuf size related -----*/
static inline ssize_t strbuf_avail(const struct strbuf *sb) {
return sb->alloc ? sb->alloc - sb->len - 1 : 0;
}
extern void strbuf_grow(struct strbuf *, size_t);
static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
if (!sb->alloc)
strbuf_grow(sb, 0);
assert(len < sb->alloc);
sb->len = len;
sb->buf[len] = '\0';
}
#define strbuf_reset(sb) strbuf_setlen(sb, 0)
/*----- content related -----*/
extern void strbuf_trim(struct strbuf *);
extern void strbuf_rtrim(struct strbuf *);
extern void strbuf_ltrim(struct strbuf *);
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
extern void strbuf_tolower(struct strbuf *);
extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
extern void strbuf_list_free(struct strbuf **);
/*----- add data in your buffer -----*/
static inline void strbuf_addch(struct strbuf *sb, int c) {
strbuf_grow(sb, 1);
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t);
extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
/* splice pos..pos+len with given data */
extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
const void *, size_t);
extern void strbuf_add(struct strbuf *, const void *, size_t);
static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
strbuf_add(sb, s, strlen(s));
}
static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
strbuf_add(sb, sb2->buf, sb2->len);
}
extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
struct strbuf_expand_dict_entry {
const char *placeholder;
const char *value;
};
extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
__attribute__((format(printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
/* XXX: if read fails, any partial read is undone */
extern ssize_t strbuf_read(struct strbuf *, int fd, ssize_t hint);
extern int strbuf_read_file(struct strbuf *sb, const char *path, ssize_t hint);
extern int strbuf_readlink(struct strbuf *sb, const char *path, ssize_t hint);
extern int strbuf_getline(struct strbuf *, FILE *, int);
extern void stripspace(struct strbuf *buf, int skip_comments);
extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
extern int strbuf_branchname(struct strbuf *sb, const char *name);
extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
#endif /* STRBUF_H */

View File

@@ -0,0 +1,34 @@
#include "string.h"
static int hex(char ch)
{
if ((ch >= '0') && (ch <= '9'))
return ch - '0';
if ((ch >= 'a') && (ch <= 'f'))
return ch - 'a' + 10;
if ((ch >= 'A') && (ch <= 'F'))
return ch - 'A' + 10;
return -1;
}
/*
* While we find nice hex chars, build a long_val.
* Return number of chars processed.
*/
int hex2u64(const char *ptr, u64 *long_val)
{
const char *p = ptr;
*long_val = 0;
while (*p) {
const int hex_val = hex(*p);
if (hex_val < 0)
break;
*long_val = (*long_val << 4) | hex_val;
p++;
}
return p - ptr;
}

View File

@@ -0,0 +1,11 @@
#ifndef _PERF_STRING_H_
#define _PERF_STRING_H_
#include "types.h"
int hex2u64(const char *ptr, u64 *val);
#define _STR(x) #x
#define STR(x) _STR(x)
#endif

View File

@@ -0,0 +1,200 @@
/*
* (c) 2009 Arnaldo Carvalho de Melo <acme@redhat.com>
*
* Licensed under the GPLv2.
*/
#include "strlist.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static struct str_node *str_node__new(const char *s, bool dupstr)
{
struct str_node *self = malloc(sizeof(*self));
if (self != NULL) {
if (dupstr) {
s = strdup(s);
if (s == NULL)
goto out_delete;
}
self->s = s;
}
return self;
out_delete:
free(self);
return NULL;
}
static void str_node__delete(struct str_node *self, bool dupstr)
{
if (dupstr)
free((void *)self->s);
free(self);
}
int strlist__add(struct strlist *self, const char *new_entry)
{
struct rb_node **p = &self->entries.rb_node;
struct rb_node *parent = NULL;
struct str_node *sn;
while (*p != NULL) {
int rc;
parent = *p;
sn = rb_entry(parent, struct str_node, rb_node);
rc = strcmp(sn->s, new_entry);
if (rc > 0)
p = &(*p)->rb_left;
else if (rc < 0)
p = &(*p)->rb_right;
else
return -EEXIST;
}
sn = str_node__new(new_entry, self->dupstr);
if (sn == NULL)
return -ENOMEM;
rb_link_node(&sn->rb_node, parent, p);
rb_insert_color(&sn->rb_node, &self->entries);
++self->nr_entries;
return 0;
}
int strlist__load(struct strlist *self, const char *filename)
{
char entry[1024];
int err;
FILE *fp = fopen(filename, "r");
if (fp == NULL)
return errno;
while (fgets(entry, sizeof(entry), fp) != NULL) {
const size_t len = strlen(entry);
if (len == 0)
continue;
entry[len - 1] = '\0';
err = strlist__add(self, entry);
if (err != 0)
goto out;
}
err = 0;
out:
fclose(fp);
return err;
}
void strlist__remove(struct strlist *self, struct str_node *sn)
{
rb_erase(&sn->rb_node, &self->entries);
str_node__delete(sn, self->dupstr);
}
bool strlist__has_entry(struct strlist *self, const char *entry)
{
struct rb_node **p = &self->entries.rb_node;
struct rb_node *parent = NULL;
while (*p != NULL) {
struct str_node *sn;
int rc;
parent = *p;
sn = rb_entry(parent, struct str_node, rb_node);
rc = strcmp(sn->s, entry);
if (rc > 0)
p = &(*p)->rb_left;
else if (rc < 0)
p = &(*p)->rb_right;
else
return true;
}
return false;
}
static int strlist__parse_list_entry(struct strlist *self, const char *s)
{
if (strncmp(s, "file://", 7) == 0)
return strlist__load(self, s + 7);
return strlist__add(self, s);
}
int strlist__parse_list(struct strlist *self, const char *s)
{
char *sep;
int err;
while ((sep = strchr(s, ',')) != NULL) {
*sep = '\0';
err = strlist__parse_list_entry(self, s);
*sep = ',';
if (err != 0)
return err;
s = sep + 1;
}
return *s ? strlist__parse_list_entry(self, s) : 0;
}
struct strlist *strlist__new(bool dupstr, const char *slist)
{
struct strlist *self = malloc(sizeof(*self));
if (self != NULL) {
self->entries = RB_ROOT;
self->dupstr = dupstr;
self->nr_entries = 0;
if (slist && strlist__parse_list(self, slist) != 0)
goto out_error;
}
return self;
out_error:
free(self);
return NULL;
}
void strlist__delete(struct strlist *self)
{
if (self != NULL) {
struct str_node *pos;
struct rb_node *next = rb_first(&self->entries);
while (next) {
pos = rb_entry(next, struct str_node, rb_node);
next = rb_next(&pos->rb_node);
strlist__remove(self, pos);
}
self->entries = RB_ROOT;
free(self);
}
}
struct str_node *strlist__entry(const struct strlist *self, unsigned int idx)
{
struct rb_node *nd;
for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
struct str_node *pos = rb_entry(nd, struct str_node, rb_node);
if (!idx--)
return pos;
}
return NULL;
}

View File

@@ -0,0 +1,39 @@
#ifndef STRLIST_H_
#define STRLIST_H_
#include <linux/rbtree.h>
#include <stdbool.h>
struct str_node {
struct rb_node rb_node;
const char *s;
};
struct strlist {
struct rb_root entries;
unsigned int nr_entries;
bool dupstr;
};
struct strlist *strlist__new(bool dupstr, const char *slist);
void strlist__delete(struct strlist *self);
void strlist__remove(struct strlist *self, struct str_node *sn);
int strlist__load(struct strlist *self, const char *filename);
int strlist__add(struct strlist *self, const char *str);
struct str_node *strlist__entry(const struct strlist *self, unsigned int idx);
bool strlist__has_entry(struct strlist *self, const char *entry);
static inline bool strlist__empty(const struct strlist *self)
{
return self->nr_entries == 0;
}
static inline unsigned int strlist__nr_entries(const struct strlist *self)
{
return self->nr_entries;
}
int strlist__parse_list(struct strlist *self, const char *s);
#endif /* STRLIST_H_ */

View File

@@ -0,0 +1,500 @@
/*
* svghelper.c - helper functions for outputting svg
*
* (C) Copyright 2009 Intel Corporation
*
* Authors:
* Arjan van de Ven <arjan@linux.intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "svghelper.h"
static u64 first_time, last_time;
static u64 turbo_frequency, max_freq;
#define SLOT_MULT 30.0
#define SLOT_HEIGHT 25.0
int svg_page_width = 1000;
#define MIN_TEXT_SIZE 0.01
static u64 total_height;
static FILE *svgfile;
static double cpu2slot(int cpu)
{
return 2 * cpu + 1;
}
static double cpu2y(int cpu)
{
return cpu2slot(cpu) * SLOT_MULT;
}
static double time2pixels(u64 time)
{
double X;
X = 1.0 * svg_page_width * (time - first_time) / (last_time - first_time);
return X;
}
/*
* Round text sizes so that the svg viewer only needs a discrete
* number of renderings of the font
*/
static double round_text_size(double size)
{
int loop = 100;
double target = 10.0;
if (size >= 10.0)
return size;
while (loop--) {
if (size >= target)
return target;
target = target / 2.0;
}
return size;
}
void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end)
{
int new_width;
svgfile = fopen(filename, "w");
if (!svgfile) {
fprintf(stderr, "Cannot open %s for output\n", filename);
return;
}
first_time = start;
first_time = first_time / 100000000 * 100000000;
last_time = end;
/*
* if the recording is short, we default to a width of 1000, but
* for longer recordings we want at least 200 units of width per second
*/
new_width = (last_time - first_time) / 5000000;
if (new_width > svg_page_width)
svg_page_width = new_width;
total_height = (1 + rows + cpu2slot(cpus)) * SLOT_MULT;
fprintf(svgfile, "<?xml version=\"1.0\" standalone=\"no\"?> \n");
fprintf(svgfile, "<svg width=\"%i\" height=\"%llu\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n", svg_page_width, total_height);
fprintf(svgfile, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
fprintf(svgfile, " rect { stroke-width: 1; }\n");
fprintf(svgfile, " rect.process { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:1; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.process2 { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.sample { fill:rgb( 0, 0,255); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.blocked { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.waiting { fill:rgb(224,214, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.WAITING { fill:rgb(255,214, 48); fill-opacity:0.6; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
fprintf(svgfile, " rect.cpu { fill:rgb(192,192,192); fill-opacity:0.2; stroke-width:0.5; stroke:rgb(128,128,128); } \n");
fprintf(svgfile, " rect.pstate { fill:rgb(128,128,128); fill-opacity:0.8; stroke-width:0; } \n");
fprintf(svgfile, " rect.c1 { fill:rgb(255,214,214); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " rect.c2 { fill:rgb(255,172,172); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " rect.c3 { fill:rgb(255,130,130); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " rect.c4 { fill:rgb(255, 88, 88); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " rect.c5 { fill:rgb(255, 44, 44); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " rect.c6 { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; } \n");
fprintf(svgfile, " line.pstate { stroke:rgb(255,255, 0); stroke-opacity:0.8; stroke-width:2; } \n");
fprintf(svgfile, " ]]>\n </style>\n</defs>\n");
}
void svg_box(int Yslot, u64 start, u64 end, const char *type)
{
if (!svgfile)
return;
fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"%s\"/>\n",
time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT, type);
}
void svg_sample(int Yslot, int cpu, u64 start, u64 end)
{
double text_size;
if (!svgfile)
return;
fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"sample\"/>\n",
time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT);
text_size = (time2pixels(end)-time2pixels(start));
if (cpu > 9)
text_size = text_size/2;
if (text_size > 1.25)
text_size = 1.25;
text_size = round_text_size(text_size);
if (text_size > MIN_TEXT_SIZE)
fprintf(svgfile, "<text x=\"%1.8f\" y=\"%1.8f\" font-size=\"%1.8fpt\">%i</text>\n",
time2pixels(start), Yslot * SLOT_MULT + SLOT_HEIGHT - 1, text_size, cpu + 1);
}
static char *time_to_string(u64 duration)
{
static char text[80];
text[0] = 0;
if (duration < 1000) /* less than 1 usec */
return text;
if (duration < 1000 * 1000) { /* less than 1 msec */
sprintf(text, "%4.1f us", duration / 1000.0);
return text;
}
sprintf(text, "%4.1f ms", duration / 1000.0 / 1000);
return text;
}
void svg_waiting(int Yslot, u64 start, u64 end)
{
char *text;
const char *style;
double font_size;
if (!svgfile)
return;
style = "waiting";
if (end-start > 10 * 1000000) /* 10 msec */
style = "WAITING";
text = time_to_string(end-start);
font_size = 1.0 * (time2pixels(end)-time2pixels(start));
if (font_size > 3)
font_size = 3;
font_size = round_text_size(font_size);
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), Yslot * SLOT_MULT);
fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
time2pixels(end)-time2pixels(start), SLOT_HEIGHT, style);
if (font_size > MIN_TEXT_SIZE)
fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%1.8fpt\"> %s</text>\n",
font_size, text);
fprintf(svgfile, "</g>\n");
}
static char *cpu_model(void)
{
static char cpu_m[255];
char buf[256];
FILE *file;
cpu_m[0] = 0;
/* CPU type */
file = fopen("/proc/cpuinfo", "r");
if (file) {
while (fgets(buf, 255, file)) {
if (strstr(buf, "model name")) {
strncpy(cpu_m, &buf[13], 255);
break;
}
}
fclose(file);
}
/* CPU type */
file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies", "r");
if (file) {
while (fgets(buf, 255, file)) {
unsigned int freq;
freq = strtoull(buf, NULL, 10);
if (freq > max_freq)
max_freq = freq;
}
fclose(file);
}
return cpu_m;
}
void svg_cpu_box(int cpu, u64 __max_freq, u64 __turbo_freq)
{
char cpu_string[80];
if (!svgfile)
return;
max_freq = __max_freq;
turbo_frequency = __turbo_freq;
fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"cpu\"/>\n",
time2pixels(first_time),
time2pixels(last_time)-time2pixels(first_time),
cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT);
sprintf(cpu_string, "CPU %i", (int)cpu+1);
fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n",
10+time2pixels(first_time), cpu2y(cpu) + SLOT_HEIGHT/2, cpu_string);
fprintf(svgfile, "<text transform=\"translate(%4.8f,%4.8f)\" font-size=\"1.25pt\">%s</text>\n",
10+time2pixels(first_time), cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - 4, cpu_model());
}
void svg_process(int cpu, u64 start, u64 end, const char *type, const char *name)
{
double width;
if (!svgfile)
return;
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), cpu2y(cpu));
fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
time2pixels(end)-time2pixels(start), SLOT_MULT+SLOT_HEIGHT, type);
width = time2pixels(end)-time2pixels(start);
if (width > 6)
width = 6;
width = round_text_size(width);
if (width > MIN_TEXT_SIZE)
fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%3.8fpt\">%s</text>\n",
width, name);
fprintf(svgfile, "</g>\n");
}
void svg_cstate(int cpu, u64 start, u64 end, int type)
{
double width;
char style[128];
if (!svgfile)
return;
if (type > 6)
type = 6;
sprintf(style, "c%i", type);
fprintf(svgfile, "<rect class=\"%s\" x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\"/>\n",
style,
time2pixels(start), time2pixels(end)-time2pixels(start),
cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT);
width = (time2pixels(end)-time2pixels(start))/2.0;
if (width > 6)
width = 6;
width = round_text_size(width);
if (width > MIN_TEXT_SIZE)
fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"%3.8fpt\">C%i</text>\n",
time2pixels(start), cpu2y(cpu)+width, width, type);
}
static char *HzToHuman(unsigned long hz)
{
static char buffer[1024];
unsigned long long Hz;
memset(buffer, 0, 1024);
Hz = hz;
/* default: just put the Number in */
sprintf(buffer, "%9lli", Hz);
if (Hz > 1000)
sprintf(buffer, " %6lli Mhz", (Hz+500)/1000);
if (Hz > 1500000)
sprintf(buffer, " %6.2f Ghz", (Hz+5000.0)/1000000);
if (Hz == turbo_frequency)
sprintf(buffer, "Turbo");
return buffer;
}
void svg_pstate(int cpu, u64 start, u64 end, u64 freq)
{
double height = 0;
if (!svgfile)
return;
if (max_freq)
height = freq * 1.0 / max_freq * (SLOT_HEIGHT + SLOT_MULT);
height = 1 + cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - height;
fprintf(svgfile, "<line x1=\"%4.8f\" x2=\"%4.8f\" y1=\"%4.1f\" y2=\"%4.1f\" class=\"pstate\"/>\n",
time2pixels(start), time2pixels(end), height, height);
fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"0.25pt\">%s</text>\n",
time2pixels(start), height+0.9, HzToHuman(freq));
}
void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2)
{
double height;
if (!svgfile)
return;
if (row1 < row2) {
if (row1) {
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32);
if (desc2)
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &gt;</text></g>\n",
time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_HEIGHT/48, desc2);
}
if (row2) {
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row2 * SLOT_MULT);
if (desc1)
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &gt;</text></g>\n",
time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, desc1);
}
} else {
if (row2) {
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32);
if (desc1)
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &lt;</text></g>\n",
time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/48, desc1);
}
if (row1) {
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row1 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row1 * SLOT_MULT);
if (desc2)
fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &lt;</text></g>\n",
time2pixels(start), row1 * SLOT_MULT - SLOT_HEIGHT/32, desc2);
}
}
height = row1 * SLOT_MULT;
if (row2 > row1)
height += SLOT_HEIGHT;
if (row1)
fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n",
time2pixels(start), height);
}
void svg_wakeline(u64 start, int row1, int row2)
{
double height;
if (!svgfile)
return;
if (row1 < row2)
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT);
else
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT);
height = row1 * SLOT_MULT;
if (row2 > row1)
height += SLOT_HEIGHT;
fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n",
time2pixels(start), height);
}
void svg_interrupt(u64 start, int row)
{
if (!svgfile)
return;
fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n",
time2pixels(start), row * SLOT_MULT);
fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n",
time2pixels(start), row * SLOT_MULT + SLOT_HEIGHT);
}
void svg_text(int Yslot, u64 start, const char *text)
{
if (!svgfile)
return;
fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n",
time2pixels(start), Yslot * SLOT_MULT+SLOT_HEIGHT/2, text);
}
static void svg_legenda_box(int X, const char *text, const char *style)
{
double boxsize;
boxsize = SLOT_HEIGHT / 2;
fprintf(svgfile, "<rect x=\"%i\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
X, boxsize, boxsize, style);
fprintf(svgfile, "<text transform=\"translate(%4.8f, %4.8f)\" font-size=\"%4.8fpt\">%s</text>\n",
X + boxsize + 5, boxsize, 0.8 * boxsize, text);
}
void svg_legenda(void)
{
if (!svgfile)
return;
svg_legenda_box(0, "Running", "sample");
svg_legenda_box(100, "Idle","rect.c1");
svg_legenda_box(200, "Deeper Idle", "rect.c3");
svg_legenda_box(350, "Deepest Idle", "rect.c6");
svg_legenda_box(550, "Sleeping", "process2");
svg_legenda_box(650, "Waiting for cpu", "waiting");
svg_legenda_box(800, "Blocked on IO", "blocked");
}
void svg_time_grid(void)
{
u64 i;
if (!svgfile)
return;
i = first_time;
while (i < last_time) {
int color = 220;
double thickness = 0.075;
if ((i % 100000000) == 0) {
thickness = 0.5;
color = 192;
}
if ((i % 1000000000) == 0) {
thickness = 2.0;
color = 128;
}
fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%llu\" style=\"stroke:rgb(%i,%i,%i);stroke-width:%1.3f\"/>\n",
time2pixels(i), SLOT_MULT/2, time2pixels(i), total_height, color, color, color, thickness);
i += 10000000;
}
}
void svg_close(void)
{
if (svgfile) {
fprintf(svgfile, "</svg>\n");
fclose(svgfile);
svgfile = NULL;
}
}

View File

@@ -0,0 +1,28 @@
#ifndef _INCLUDE_GUARD_SVG_HELPER_
#define _INCLUDE_GUARD_SVG_HELPER_
#include "types.h"
extern void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end);
extern void svg_box(int Yslot, u64 start, u64 end, const char *type);
extern void svg_sample(int Yslot, int cpu, u64 start, u64 end);
extern void svg_waiting(int Yslot, u64 start, u64 end);
extern void svg_cpu_box(int cpu, u64 max_frequency, u64 turbo_frequency);
extern void svg_process(int cpu, u64 start, u64 end, const char *type, const char *name);
extern void svg_cstate(int cpu, u64 start, u64 end, int type);
extern void svg_pstate(int cpu, u64 start, u64 end, u64 freq);
extern void svg_time_grid(void);
extern void svg_legenda(void);
extern void svg_wakeline(u64 start, int row1, int row2);
extern void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2);
extern void svg_interrupt(u64 start, int row);
extern void svg_text(int Yslot, u64 start, const char *text);
extern void svg_close(void);
extern int svg_page_width;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
#ifndef _PERF_SYMBOL_
#define _PERF_SYMBOL_ 1
#include <linux/types.h>
#include "types.h"
#include <linux/list.h>
#include <linux/rbtree.h>
#include "module.h"
#include "event.h"
#ifdef HAVE_CPLUS_DEMANGLE
extern char *cplus_demangle(const char *, int);
static inline char *bfd_demangle(void __used *v, const char *c, int i)
{
return cplus_demangle(c, i);
}
#else
#ifdef NO_DEMANGLE
static inline char *bfd_demangle(void __used *v, const char __used *c,
int __used i)
{
return NULL;
}
#else
#include <bfd.h>
#endif
#endif
/*
* libelf 0.8.x and earlier do not support ELF_C_READ_MMAP;
* for newer versions we can use mmap to reduce memory usage:
*/
#ifdef LIBELF_NO_MMAP
# define PERF_ELF_C_READ_MMAP ELF_C_READ
#else
# define PERF_ELF_C_READ_MMAP ELF_C_READ_MMAP
#endif
#ifndef DMGL_PARAMS
#define DMGL_PARAMS (1 << 0) /* Include function args */
#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */
#endif
struct symbol {
struct rb_node rb_node;
u64 start;
u64 end;
u64 obj_start;
u64 hist_sum;
u64 *hist;
struct module *module;
void *priv;
char name[0];
};
struct dso {
struct list_head node;
struct rb_root syms;
struct symbol *(*find_symbol)(struct dso *, u64 ip);
unsigned int sym_priv_size;
unsigned char adjust_symbols;
unsigned char slen_calculated;
unsigned char origin;
char name[0];
};
extern const char *sym_hist_filter;
typedef int (*symbol_filter_t)(struct dso *self, struct symbol *sym);
struct dso *dso__new(const char *name, unsigned int sym_priv_size);
void dso__delete(struct dso *self);
static inline void *dso__sym_priv(struct dso *self, struct symbol *sym)
{
return ((void *)sym) - self->sym_priv_size;
}
struct symbol *dso__find_symbol(struct dso *self, u64 ip);
int dso__load_kernel(struct dso *self, const char *vmlinux,
symbol_filter_t filter, int verbose, int modules);
int dso__load_modules(struct dso *self, symbol_filter_t filter, int verbose);
int dso__load(struct dso *self, symbol_filter_t filter, int verbose);
struct dso *dsos__findnew(const char *name);
void dsos__fprintf(FILE *fp);
size_t dso__fprintf(struct dso *self, FILE *fp);
char dso__symtab_origin(const struct dso *self);
int load_kernel(void);
void symbol__init(void);
extern struct list_head dsos;
extern struct dso *kernel_dso;
extern struct dso *vdso;
extern struct dso *hypervisor_dso;
extern const char *vmlinux_name;
extern int modules;
#endif /* _PERF_SYMBOL_ */

View File

@@ -0,0 +1,175 @@
#include "../perf.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "thread.h"
#include "util.h"
#include "debug.h"
static struct thread *thread__new(pid_t pid)
{
struct thread *self = calloc(1, sizeof(*self));
if (self != NULL) {
self->pid = pid;
self->comm = malloc(32);
if (self->comm)
snprintf(self->comm, 32, ":%d", self->pid);
INIT_LIST_HEAD(&self->maps);
}
return self;
}
int thread__set_comm(struct thread *self, const char *comm)
{
if (self->comm)
free(self->comm);
self->comm = strdup(comm);
return self->comm ? 0 : -ENOMEM;
}
static size_t thread__fprintf(struct thread *self, FILE *fp)
{
struct map *pos;
size_t ret = fprintf(fp, "Thread %d %s\n", self->pid, self->comm);
list_for_each_entry(pos, &self->maps, node)
ret += map__fprintf(pos, fp);
return ret;
}
struct thread *
threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match)
{
struct rb_node **p = &threads->rb_node;
struct rb_node *parent = NULL;
struct thread *th;
/*
* Font-end cache - PID lookups come in blocks,
* so most of the time we dont have to look up
* the full rbtree:
*/
if (*last_match && (*last_match)->pid == pid)
return *last_match;
while (*p != NULL) {
parent = *p;
th = rb_entry(parent, struct thread, rb_node);
if (th->pid == pid) {
*last_match = th;
return th;
}
if (pid < th->pid)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
th = thread__new(pid);
if (th != NULL) {
rb_link_node(&th->rb_node, parent, p);
rb_insert_color(&th->rb_node, threads);
*last_match = th;
}
return th;
}
struct thread *
register_idle_thread(struct rb_root *threads, struct thread **last_match)
{
struct thread *thread = threads__findnew(0, threads, last_match);
if (!thread || thread__set_comm(thread, "swapper")) {
fprintf(stderr, "problem inserting idle task.\n");
exit(-1);
}
return thread;
}
void thread__insert_map(struct thread *self, struct map *map)
{
struct map *pos, *tmp;
list_for_each_entry_safe(pos, tmp, &self->maps, node) {
if (map__overlap(pos, map)) {
if (verbose >= 2) {
printf("overlapping maps:\n");
map__fprintf(map, stdout);
map__fprintf(pos, stdout);
}
if (map->start <= pos->start && map->end > pos->start)
pos->start = map->end;
if (map->end >= pos->end && map->start < pos->end)
pos->end = map->start;
if (verbose >= 2) {
printf("after collision:\n");
map__fprintf(pos, stdout);
}
if (pos->start >= pos->end) {
list_del_init(&pos->node);
free(pos);
}
}
}
list_add_tail(&map->node, &self->maps);
}
int thread__fork(struct thread *self, struct thread *parent)
{
struct map *map;
if (self->comm)
free(self->comm);
self->comm = strdup(parent->comm);
if (!self->comm)
return -ENOMEM;
list_for_each_entry(map, &parent->maps, node) {
struct map *new = map__clone(map);
if (!new)
return -ENOMEM;
thread__insert_map(self, new);
}
return 0;
}
struct map *thread__find_map(struct thread *self, u64 ip)
{
struct map *pos;
if (self == NULL)
return NULL;
list_for_each_entry(pos, &self->maps, node)
if (ip >= pos->start && ip <= pos->end)
return pos;
return NULL;
}
size_t threads__fprintf(FILE *fp, struct rb_root *threads)
{
size_t ret = 0;
struct rb_node *nd;
for (nd = rb_first(threads); nd; nd = rb_next(nd)) {
struct thread *pos = rb_entry(nd, struct thread, rb_node);
ret += thread__fprintf(pos, fp);
}
return ret;
}

View File

@@ -0,0 +1,22 @@
#include <linux/rbtree.h>
#include <linux/list.h>
#include <unistd.h>
#include "symbol.h"
struct thread {
struct rb_node rb_node;
struct list_head maps;
pid_t pid;
char shortname[3];
char *comm;
};
int thread__set_comm(struct thread *self, const char *comm);
struct thread *
threads__findnew(pid_t pid, struct rb_root *threads, struct thread **last_match);
struct thread *
register_idle_thread(struct rb_root *threads, struct thread **last_match);
void thread__insert_map(struct thread *self, struct map *map);
int thread__fork(struct thread *self, struct thread *parent);
struct map *thread__find_map(struct thread *self, u64 ip);
size_t threads__fprintf(FILE *fp, struct rb_root *threads);

View File

@@ -0,0 +1,540 @@
/*
* Copyright (C) 2008,2009, Steven Rostedt <srostedt@redhat.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License (not later!)
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include "../perf.h"
#include "trace-event.h"
#define VERSION "0.5"
#define _STR(x) #x
#define STR(x) _STR(x)
#define MAX_PATH 256
#define TRACE_CTRL "tracing_on"
#define TRACE "trace"
#define AVAILABLE "available_tracers"
#define CURRENT "current_tracer"
#define ITER_CTRL "trace_options"
#define MAX_LATENCY "tracing_max_latency"
unsigned int page_size;
static const char *output_file = "trace.info";
static int output_fd;
struct event_list {
struct event_list *next;
const char *event;
};
struct events {
struct events *sibling;
struct events *children;
struct events *next;
char *name;
};
static void die(const char *fmt, ...)
{
va_list ap;
int ret = errno;
if (errno)
perror("trace-cmd");
else
ret = -1;
va_start(ap, fmt);
fprintf(stderr, " ");
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(ret);
}
void *malloc_or_die(unsigned int size)
{
void *data;
data = malloc(size);
if (!data)
die("malloc");
return data;
}
static const char *find_debugfs(void)
{
static char debugfs[MAX_PATH+1];
static int debugfs_found;
char type[100];
FILE *fp;
if (debugfs_found)
return debugfs;
if ((fp = fopen("/proc/mounts","r")) == NULL)
die("Can't open /proc/mounts for read");
while (fscanf(fp, "%*s %"
STR(MAX_PATH)
"s %99s %*s %*d %*d\n",
debugfs, type) == 2) {
if (strcmp(type, "debugfs") == 0)
break;
}
fclose(fp);
if (strcmp(type, "debugfs") != 0)
die("debugfs not mounted, please mount");
debugfs_found = 1;
return debugfs;
}
/*
* Finds the path to the debugfs/tracing
* Allocates the string and stores it.
*/
static const char *find_tracing_dir(void)
{
static char *tracing;
static int tracing_found;
const char *debugfs;
if (tracing_found)
return tracing;
debugfs = find_debugfs();
tracing = malloc_or_die(strlen(debugfs) + 9);
sprintf(tracing, "%s/tracing", debugfs);
tracing_found = 1;
return tracing;
}
static char *get_tracing_file(const char *name)
{
const char *tracing;
char *file;
tracing = find_tracing_dir();
if (!tracing)
return NULL;
file = malloc_or_die(strlen(tracing) + strlen(name) + 2);
sprintf(file, "%s/%s", tracing, name);
return file;
}
static void put_tracing_file(char *file)
{
free(file);
}
static ssize_t write_or_die(const void *buf, size_t len)
{
int ret;
ret = write(output_fd, buf, len);
if (ret < 0)
die("writing to '%s'", output_file);
return ret;
}
int bigendian(void)
{
unsigned char str[] = { 0x1, 0x2, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0};
unsigned int *ptr;
ptr = (unsigned int *)(void *)str;
return *ptr == 0x01020304;
}
static unsigned long long copy_file_fd(int fd)
{
unsigned long long size = 0;
char buf[BUFSIZ];
int r;
do {
r = read(fd, buf, BUFSIZ);
if (r > 0) {
size += r;
write_or_die(buf, r);
}
} while (r > 0);
return size;
}
static unsigned long long copy_file(const char *file)
{
unsigned long long size = 0;
int fd;
fd = open(file, O_RDONLY);
if (fd < 0)
die("Can't read '%s'", file);
size = copy_file_fd(fd);
close(fd);
return size;
}
static unsigned long get_size_fd(int fd)
{
unsigned long long size = 0;
char buf[BUFSIZ];
int r;
do {
r = read(fd, buf, BUFSIZ);
if (r > 0)
size += r;
} while (r > 0);
lseek(fd, 0, SEEK_SET);
return size;
}
static unsigned long get_size(const char *file)
{
unsigned long long size = 0;
int fd;
fd = open(file, O_RDONLY);
if (fd < 0)
die("Can't read '%s'", file);
size = get_size_fd(fd);
close(fd);
return size;
}
static void read_header_files(void)
{
unsigned long long size, check_size;
char *path;
int fd;
path = get_tracing_file("events/header_page");
fd = open(path, O_RDONLY);
if (fd < 0)
die("can't read '%s'", path);
/* unfortunately, you can not stat debugfs files for size */
size = get_size_fd(fd);
write_or_die("header_page", 12);
write_or_die(&size, 8);
check_size = copy_file_fd(fd);
if (size != check_size)
die("wrong size for '%s' size=%lld read=%lld",
path, size, check_size);
put_tracing_file(path);
path = get_tracing_file("events/header_event");
fd = open(path, O_RDONLY);
if (fd < 0)
die("can't read '%s'", path);
size = get_size_fd(fd);
write_or_die("header_event", 13);
write_or_die(&size, 8);
check_size = copy_file_fd(fd);
if (size != check_size)
die("wrong size for '%s'", path);
put_tracing_file(path);
}
static bool name_in_tp_list(char *sys, struct tracepoint_path *tps)
{
while (tps) {
if (!strcmp(sys, tps->name))
return true;
tps = tps->next;
}
return false;
}
static void copy_event_system(const char *sys, struct tracepoint_path *tps)
{
unsigned long long size, check_size;
struct dirent *dent;
struct stat st;
char *format;
DIR *dir;
int count = 0;
int ret;
dir = opendir(sys);
if (!dir)
die("can't read directory '%s'", sys);
while ((dent = readdir(dir))) {
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 ||
!name_in_tp_list(dent->d_name, tps))
continue;
format = malloc_or_die(strlen(sys) + strlen(dent->d_name) + 10);
sprintf(format, "%s/%s/format", sys, dent->d_name);
ret = stat(format, &st);
free(format);
if (ret < 0)
continue;
count++;
}
write_or_die(&count, 4);
rewinddir(dir);
while ((dent = readdir(dir))) {
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 ||
!name_in_tp_list(dent->d_name, tps))
continue;
format = malloc_or_die(strlen(sys) + strlen(dent->d_name) + 10);
sprintf(format, "%s/%s/format", sys, dent->d_name);
ret = stat(format, &st);
if (ret >= 0) {
/* unfortunately, you can not stat debugfs files for size */
size = get_size(format);
write_or_die(&size, 8);
check_size = copy_file(format);
if (size != check_size)
die("error in size of file '%s'", format);
}
free(format);
}
}
static void read_ftrace_files(struct tracepoint_path *tps)
{
char *path;
path = get_tracing_file("events/ftrace");
copy_event_system(path, tps);
put_tracing_file(path);
}
static bool system_in_tp_list(char *sys, struct tracepoint_path *tps)
{
while (tps) {
if (!strcmp(sys, tps->system))
return true;
tps = tps->next;
}
return false;
}
static void read_event_files(struct tracepoint_path *tps)
{
struct dirent *dent;
struct stat st;
char *path;
char *sys;
DIR *dir;
int count = 0;
int ret;
path = get_tracing_file("events");
dir = opendir(path);
if (!dir)
die("can't read directory '%s'", path);
while ((dent = readdir(dir))) {
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 ||
strcmp(dent->d_name, "ftrace") == 0 ||
!system_in_tp_list(dent->d_name, tps))
continue;
sys = malloc_or_die(strlen(path) + strlen(dent->d_name) + 2);
sprintf(sys, "%s/%s", path, dent->d_name);
ret = stat(sys, &st);
free(sys);
if (ret < 0)
continue;
if (S_ISDIR(st.st_mode))
count++;
}
write_or_die(&count, 4);
rewinddir(dir);
while ((dent = readdir(dir))) {
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0 ||
strcmp(dent->d_name, "ftrace") == 0 ||
!system_in_tp_list(dent->d_name, tps))
continue;
sys = malloc_or_die(strlen(path) + strlen(dent->d_name) + 2);
sprintf(sys, "%s/%s", path, dent->d_name);
ret = stat(sys, &st);
if (ret >= 0) {
if (S_ISDIR(st.st_mode)) {
write_or_die(dent->d_name, strlen(dent->d_name) + 1);
copy_event_system(sys, tps);
}
}
free(sys);
}
put_tracing_file(path);
}
static void read_proc_kallsyms(void)
{
unsigned int size, check_size;
const char *path = "/proc/kallsyms";
struct stat st;
int ret;
ret = stat(path, &st);
if (ret < 0) {
/* not found */
size = 0;
write_or_die(&size, 4);
return;
}
size = get_size(path);
write_or_die(&size, 4);
check_size = copy_file(path);
if (size != check_size)
die("error in size of file '%s'", path);
}
static void read_ftrace_printk(void)
{
unsigned int size, check_size;
char *path;
struct stat st;
int ret;
path = get_tracing_file("printk_formats");
ret = stat(path, &st);
if (ret < 0) {
/* not found */
size = 0;
write_or_die(&size, 4);
goto out;
}
size = get_size(path);
write_or_die(&size, 4);
check_size = copy_file(path);
if (size != check_size)
die("error in size of file '%s'", path);
out:
put_tracing_file(path);
}
static struct tracepoint_path *
get_tracepoints_path(struct perf_event_attr *pattrs, int nb_events)
{
struct tracepoint_path path, *ppath = &path;
int i;
for (i = 0; i < nb_events; i++) {
if (pattrs[i].type != PERF_TYPE_TRACEPOINT)
continue;
ppath->next = tracepoint_id_to_path(pattrs[i].config);
if (!ppath->next)
die("%s\n", "No memory to alloc tracepoints list");
ppath = ppath->next;
}
return path.next;
}
void read_tracing_data(struct perf_event_attr *pattrs, int nb_events)
{
char buf[BUFSIZ];
struct tracepoint_path *tps;
output_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644);
if (output_fd < 0)
die("creating file '%s'", output_file);
buf[0] = 23;
buf[1] = 8;
buf[2] = 68;
memcpy(buf + 3, "tracing", 7);
write_or_die(buf, 10);
write_or_die(VERSION, strlen(VERSION) + 1);
/* save endian */
if (bigendian())
buf[0] = 1;
else
buf[0] = 0;
write_or_die(buf, 1);
/* save size of long */
buf[0] = sizeof(long);
write_or_die(buf, 1);
/* save page_size */
page_size = getpagesize();
write_or_die(&page_size, 4);
tps = get_tracepoints_path(pattrs, nb_events);
read_header_files();
read_ftrace_files(tps);
read_event_files(tps);
read_proc_kallsyms();
read_ftrace_printk();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,514 @@
/*
* Copyright (C) 2009, Steven Rostedt <srostedt@redhat.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License (not later!)
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define _LARGEFILE64_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include "../perf.h"
#include "util.h"
#include "trace-event.h"
static int input_fd;
static int read_page;
int file_bigendian;
int host_bigendian;
static int long_size;
static unsigned long page_size;
static int read_or_die(void *data, int size)
{
int r;
r = read(input_fd, data, size);
if (r != size)
die("reading input file (size expected=%d received=%d)",
size, r);
return r;
}
static unsigned int read4(void)
{
unsigned int data;
read_or_die(&data, 4);
return __data2host4(data);
}
static unsigned long long read8(void)
{
unsigned long long data;
read_or_die(&data, 8);
return __data2host8(data);
}
static char *read_string(void)
{
char buf[BUFSIZ];
char *str = NULL;
int size = 0;
int i;
int r;
for (;;) {
r = read(input_fd, buf, BUFSIZ);
if (r < 0)
die("reading input file");
if (!r)
die("no data");
for (i = 0; i < r; i++) {
if (!buf[i])
break;
}
if (i < r)
break;
if (str) {
size += BUFSIZ;
str = realloc(str, size);
if (!str)
die("malloc of size %d", size);
memcpy(str + (size - BUFSIZ), buf, BUFSIZ);
} else {
size = BUFSIZ;
str = malloc_or_die(size);
memcpy(str, buf, size);
}
}
/* trailing \0: */
i++;
/* move the file descriptor to the end of the string */
r = lseek(input_fd, -(r - i), SEEK_CUR);
if (r < 0)
die("lseek");
if (str) {
size += i;
str = realloc(str, size);
if (!str)
die("malloc of size %d", size);
memcpy(str + (size - i), buf, i);
} else {
size = i;
str = malloc_or_die(i);
memcpy(str, buf, i);
}
return str;
}
static void read_proc_kallsyms(void)
{
unsigned int size;
char *buf;
size = read4();
if (!size)
return;
buf = malloc_or_die(size);
read_or_die(buf, size);
parse_proc_kallsyms(buf, size);
free(buf);
}
static void read_ftrace_printk(void)
{
unsigned int size;
char *buf;
size = read4();
if (!size)
return;
buf = malloc_or_die(size);
read_or_die(buf, size);
parse_ftrace_printk(buf, size);
free(buf);
}
static void read_header_files(void)
{
unsigned long long size;
char *header_page;
char *header_event;
char buf[BUFSIZ];
read_or_die(buf, 12);
if (memcmp(buf, "header_page", 12) != 0)
die("did not read header page");
size = read8();
header_page = malloc_or_die(size);
read_or_die(header_page, size);
parse_header_page(header_page, size);
free(header_page);
/*
* The size field in the page is of type long,
* use that instead, since it represents the kernel.
*/
long_size = header_page_size_size;
read_or_die(buf, 13);
if (memcmp(buf, "header_event", 13) != 0)
die("did not read header event");
size = read8();
header_event = malloc_or_die(size);
read_or_die(header_event, size);
free(header_event);
}
static void read_ftrace_file(unsigned long long size)
{
char *buf;
buf = malloc_or_die(size);
read_or_die(buf, size);
parse_ftrace_file(buf, size);
free(buf);
}
static void read_event_file(char *sys, unsigned long long size)
{
char *buf;
buf = malloc_or_die(size);
read_or_die(buf, size);
parse_event_file(buf, size, sys);
free(buf);
}
static void read_ftrace_files(void)
{
unsigned long long size;
int count;
int i;
count = read4();
for (i = 0; i < count; i++) {
size = read8();
read_ftrace_file(size);
}
}
static void read_event_files(void)
{
unsigned long long size;
char *sys;
int systems;
int count;
int i,x;
systems = read4();
for (i = 0; i < systems; i++) {
sys = read_string();
count = read4();
for (x=0; x < count; x++) {
size = read8();
read_event_file(sys, size);
}
}
}
struct cpu_data {
unsigned long long offset;
unsigned long long size;
unsigned long long timestamp;
struct record *next;
char *page;
int cpu;
int index;
int page_size;
};
static struct cpu_data *cpu_data;
static void update_cpu_data_index(int cpu)
{
cpu_data[cpu].offset += page_size;
cpu_data[cpu].size -= page_size;
cpu_data[cpu].index = 0;
}
static void get_next_page(int cpu)
{
off64_t save_seek;
off64_t ret;
if (!cpu_data[cpu].page)
return;
if (read_page) {
if (cpu_data[cpu].size <= page_size) {
free(cpu_data[cpu].page);
cpu_data[cpu].page = NULL;
return;
}
update_cpu_data_index(cpu);
/* other parts of the code may expect the pointer to not move */
save_seek = lseek64(input_fd, 0, SEEK_CUR);
ret = lseek64(input_fd, cpu_data[cpu].offset, SEEK_SET);
if (ret < 0)
die("failed to lseek");
ret = read(input_fd, cpu_data[cpu].page, page_size);
if (ret < 0)
die("failed to read page");
/* reset the file pointer back */
lseek64(input_fd, save_seek, SEEK_SET);
return;
}
munmap(cpu_data[cpu].page, page_size);
cpu_data[cpu].page = NULL;
if (cpu_data[cpu].size <= page_size)
return;
update_cpu_data_index(cpu);
cpu_data[cpu].page = mmap(NULL, page_size, PROT_READ, MAP_PRIVATE,
input_fd, cpu_data[cpu].offset);
if (cpu_data[cpu].page == MAP_FAILED)
die("failed to mmap cpu %d at offset 0x%llx",
cpu, cpu_data[cpu].offset);
}
static unsigned int type_len4host(unsigned int type_len_ts)
{
if (file_bigendian)
return (type_len_ts >> 27) & ((1 << 5) - 1);
else
return type_len_ts & ((1 << 5) - 1);
}
static unsigned int ts4host(unsigned int type_len_ts)
{
if (file_bigendian)
return type_len_ts & ((1 << 27) - 1);
else
return type_len_ts >> 5;
}
static int calc_index(void *ptr, int cpu)
{
return (unsigned long)ptr - (unsigned long)cpu_data[cpu].page;
}
struct record *trace_peek_data(int cpu)
{
struct record *data;
void *page = cpu_data[cpu].page;
int idx = cpu_data[cpu].index;
void *ptr = page + idx;
unsigned long long extend;
unsigned int type_len_ts;
unsigned int type_len;
unsigned int delta;
unsigned int length = 0;
if (cpu_data[cpu].next)
return cpu_data[cpu].next;
if (!page)
return NULL;
if (!idx) {
/* FIXME: handle header page */
if (header_page_ts_size != 8)
die("expected a long long type for timestamp");
cpu_data[cpu].timestamp = data2host8(ptr);
ptr += 8;
switch (header_page_size_size) {
case 4:
cpu_data[cpu].page_size = data2host4(ptr);
ptr += 4;
break;
case 8:
cpu_data[cpu].page_size = data2host8(ptr);
ptr += 8;
break;
default:
die("bad long size");
}
ptr = cpu_data[cpu].page + header_page_data_offset;
}
read_again:
idx = calc_index(ptr, cpu);
if (idx >= cpu_data[cpu].page_size) {
get_next_page(cpu);
return trace_peek_data(cpu);
}
type_len_ts = data2host4(ptr);
ptr += 4;
type_len = type_len4host(type_len_ts);
delta = ts4host(type_len_ts);
switch (type_len) {
case RINGBUF_TYPE_PADDING:
if (!delta)
die("error, hit unexpected end of page");
length = data2host4(ptr);
ptr += 4;
length *= 4;
ptr += length;
goto read_again;
case RINGBUF_TYPE_TIME_EXTEND:
extend = data2host4(ptr);
ptr += 4;
extend <<= TS_SHIFT;
extend += delta;
cpu_data[cpu].timestamp += extend;
goto read_again;
case RINGBUF_TYPE_TIME_STAMP:
ptr += 12;
break;
case 0:
length = data2host4(ptr);
ptr += 4;
die("here! length=%d", length);
break;
default:
length = type_len * 4;
break;
}
cpu_data[cpu].timestamp += delta;
data = malloc_or_die(sizeof(*data));
memset(data, 0, sizeof(*data));
data->ts = cpu_data[cpu].timestamp;
data->size = length;
data->data = ptr;
ptr += length;
cpu_data[cpu].index = calc_index(ptr, cpu);
cpu_data[cpu].next = data;
return data;
}
struct record *trace_read_data(int cpu)
{
struct record *data;
data = trace_peek_data(cpu);
cpu_data[cpu].next = NULL;
return data;
}
void trace_report(void)
{
const char *input_file = "trace.info";
char buf[BUFSIZ];
char test[] = { 23, 8, 68 };
char *version;
int show_version = 0;
int show_funcs = 0;
int show_printk = 0;
input_fd = open(input_file, O_RDONLY);
if (input_fd < 0)
die("opening '%s'\n", input_file);
read_or_die(buf, 3);
if (memcmp(buf, test, 3) != 0)
die("not an trace data file");
read_or_die(buf, 7);
if (memcmp(buf, "tracing", 7) != 0)
die("not a trace file (missing tracing)");
version = read_string();
if (show_version)
printf("version = %s\n", version);
free(version);
read_or_die(buf, 1);
file_bigendian = buf[0];
host_bigendian = bigendian();
read_or_die(buf, 1);
long_size = buf[0];
page_size = read4();
read_header_files();
read_ftrace_files();
read_event_files();
read_proc_kallsyms();
read_ftrace_printk();
if (show_funcs) {
print_funcs();
return;
}
if (show_printk) {
print_printk();
return;
}
return;
}

View File

@@ -0,0 +1,245 @@
#ifndef _TRACE_EVENTS_H
#define _TRACE_EVENTS_H
#include "parse-events.h"
#define __unused __attribute__((unused))
#ifndef PAGE_MASK
#define PAGE_MASK (page_size - 1)
#endif
enum {
RINGBUF_TYPE_PADDING = 29,
RINGBUF_TYPE_TIME_EXTEND = 30,
RINGBUF_TYPE_TIME_STAMP = 31,
};
#ifndef TS_SHIFT
#define TS_SHIFT 27
#endif
#define NSECS_PER_SEC 1000000000ULL
#define NSECS_PER_USEC 1000ULL
enum format_flags {
FIELD_IS_ARRAY = 1,
FIELD_IS_POINTER = 2,
};
struct format_field {
struct format_field *next;
char *type;
char *name;
int offset;
int size;
unsigned long flags;
};
struct format {
int nr_common;
int nr_fields;
struct format_field *common_fields;
struct format_field *fields;
};
struct print_arg_atom {
char *atom;
};
struct print_arg_string {
char *string;
int offset;
};
struct print_arg_field {
char *name;
struct format_field *field;
};
struct print_flag_sym {
struct print_flag_sym *next;
char *value;
char *str;
};
struct print_arg_typecast {
char *type;
struct print_arg *item;
};
struct print_arg_flags {
struct print_arg *field;
char *delim;
struct print_flag_sym *flags;
};
struct print_arg_symbol {
struct print_arg *field;
struct print_flag_sym *symbols;
};
struct print_arg;
struct print_arg_op {
char *op;
int prio;
struct print_arg *left;
struct print_arg *right;
};
struct print_arg_func {
char *name;
struct print_arg *args;
};
enum print_arg_type {
PRINT_NULL,
PRINT_ATOM,
PRINT_FIELD,
PRINT_FLAGS,
PRINT_SYMBOL,
PRINT_TYPE,
PRINT_STRING,
PRINT_OP,
};
struct print_arg {
struct print_arg *next;
enum print_arg_type type;
union {
struct print_arg_atom atom;
struct print_arg_field field;
struct print_arg_typecast typecast;
struct print_arg_flags flags;
struct print_arg_symbol symbol;
struct print_arg_func func;
struct print_arg_string string;
struct print_arg_op op;
};
};
struct print_fmt {
char *format;
struct print_arg *args;
};
struct event {
struct event *next;
char *name;
int id;
int flags;
struct format format;
struct print_fmt print_fmt;
};
enum {
EVENT_FL_ISFTRACE = 1,
EVENT_FL_ISPRINT = 2,
EVENT_FL_ISBPRINT = 4,
EVENT_FL_ISFUNC = 8,
EVENT_FL_ISFUNCENT = 16,
EVENT_FL_ISFUNCRET = 32,
};
struct record {
unsigned long long ts;
int size;
void *data;
};
struct record *trace_peek_data(int cpu);
struct record *trace_read_data(int cpu);
void parse_set_info(int nr_cpus, int long_sz);
void trace_report(void);
void *malloc_or_die(unsigned int size);
void parse_cmdlines(char *file, int size);
void parse_proc_kallsyms(char *file, unsigned int size);
void parse_ftrace_printk(char *file, unsigned int size);
void print_funcs(void);
void print_printk(void);
int parse_ftrace_file(char *buf, unsigned long size);
int parse_event_file(char *buf, unsigned long size, char *system);
void print_event(int cpu, void *data, int size, unsigned long long nsecs,
char *comm);
extern int file_bigendian;
extern int host_bigendian;
int bigendian(void);
static inline unsigned short __data2host2(unsigned short data)
{
unsigned short swap;
if (host_bigendian == file_bigendian)
return data;
swap = ((data & 0xffULL) << 8) |
((data & (0xffULL << 8)) >> 8);
return swap;
}
static inline unsigned int __data2host4(unsigned int data)
{
unsigned int swap;
if (host_bigendian == file_bigendian)
return data;
swap = ((data & 0xffULL) << 24) |
((data & (0xffULL << 8)) << 8) |
((data & (0xffULL << 16)) >> 8) |
((data & (0xffULL << 24)) >> 24);
return swap;
}
static inline unsigned long long __data2host8(unsigned long long data)
{
unsigned long long swap;
if (host_bigendian == file_bigendian)
return data;
swap = ((data & 0xffULL) << 56) |
((data & (0xffULL << 8)) << 40) |
((data & (0xffULL << 16)) << 24) |
((data & (0xffULL << 24)) << 8) |
((data & (0xffULL << 32)) >> 8) |
((data & (0xffULL << 40)) >> 24) |
((data & (0xffULL << 48)) >> 40) |
((data & (0xffULL << 56)) >> 56);
return swap;
}
#define data2host2(ptr) __data2host2(*(unsigned short *)ptr)
#define data2host4(ptr) __data2host4(*(unsigned int *)ptr)
#define data2host8(ptr) __data2host8(*(unsigned long long *)ptr)
extern int header_page_ts_offset;
extern int header_page_ts_size;
extern int header_page_size_offset;
extern int header_page_size_size;
extern int header_page_data_offset;
extern int header_page_data_size;
int parse_header_page(char *buf, unsigned long size);
int trace_parse_common_type(void *data);
struct event *trace_find_event(int id);
unsigned long long
raw_field_value(struct event *event, const char *name, void *data);
void *raw_field_ptr(struct event *event, const char *name, void *data);
void read_tracing_data(struct perf_event_attr *pattrs, int nb_events);
#endif /* _TRACE_EVENTS_H */

View File

@@ -0,0 +1,17 @@
#ifndef _PERF_TYPES_H
#define _PERF_TYPES_H
/*
* We define u64 as unsigned long long for every architecture
* so that we can print it with %Lx without getting warnings.
*/
typedef unsigned long long u64;
typedef signed long long s64;
typedef unsigned int u32;
typedef signed int s32;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned char u8;
typedef signed char s8;
#endif /* _PERF_TYPES_H */

View File

@@ -0,0 +1,80 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "util.h"
static void report(const char *prefix, const char *err, va_list params)
{
char msg[1024];
vsnprintf(msg, sizeof(msg), err, params);
fprintf(stderr, " %s%s\n", prefix, msg);
}
static NORETURN void usage_builtin(const char *err)
{
fprintf(stderr, "\n Usage: %s\n", err);
exit(129);
}
static NORETURN void die_builtin(const char *err, va_list params)
{
report(" Fatal: ", err, params);
exit(128);
}
static void error_builtin(const char *err, va_list params)
{
report(" Error: ", err, params);
}
static void warn_builtin(const char *warn, va_list params)
{
report(" Warning: ", warn, params);
}
/* If we are in a dlopen()ed .so write to a global variable would segfault
* (ugh), so keep things static. */
static void (*usage_routine)(const char *err) NORETURN = usage_builtin;
static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin;
static void (*error_routine)(const char *err, va_list params) = error_builtin;
static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN)
{
die_routine = routine;
}
void usage(const char *err)
{
usage_routine(err);
}
void die(const char *err, ...)
{
va_list params;
va_start(params, err);
die_routine(err, params);
va_end(params);
}
int error(const char *err, ...)
{
va_list params;
va_start(params, err);
error_routine(err, params);
va_end(params);
return -1;
}
void warning(const char *warn, ...)
{
va_list params;
va_start(params, warn);
warn_routine(warn, params);
va_end(params);
}

Some files were not shown because too many files have changed in this diff Show More