Land Sysprof 2.x

This is a major redesign a modernization of Sysprof. The core data
structures and design are largely the same, but it has been ported to
Gtk3 and has lots of additions that should make your profiling experience
smoother. Especially for those that are new to profiling.

There are some very simple help docs added, but we really need the
experts to come in and write some documentation here.
This commit is contained in:
Christian Hergert
2016-04-13 05:24:03 -07:00
parent 34db28db32
commit 29c4ec495f
231 changed files with 35471 additions and 24788 deletions

View File

@ -1 +1,3 @@
Søren Sandmann (sandmann@redhat.com)
# Generated by Makefile. Do not edit.
Christian Hergert

340
COPYING.gpl-2 Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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; either version 2 of the License, or
(at your option) any later version.
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
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

27
DESIGN.md Normal file
View File

@ -0,0 +1,27 @@
# Sysprof 2.x
## Design
### libsysprof-2
This library provides various UI components and profiling tools as a library.
It is intended to be used from IDEs such as GNOME Builder.
### sysprofd
Sometimes, your current user does not have the required capabilities
such as (`CAP_SYS_ADMIN`) to access performance counters. This daemon provides
a way to get access to those counters while going through the normal
authorization flow users are familiar with.
### sysprof-cli
This is a command line tool to help you capture in an automated fashion or
when you don't have access to a UI or want to remove the UI overhead from
system traces.
### sysprof
This is the tranditional Sysprof interface. It uses libsysprof-2 to setup
and manage performance counters and display results.

View File

@ -1,122 +1,37 @@
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
SUBDIRS = daemon data help lib po src tools tests
#SUBDIRS = $(MODULE_SUBDIR)
#DIST_SUBDIRS = module
EXTRA_DIST = AUTHORS
bin_PROGRAMS = sysprof-cli
DISTCHECK_CONFIGURE_FLAGS = --with-systemdsystemunitdir='$(prefix)/etc/systemd/system'
if BUILD_GUI
bin_PROGRAMS += sysprof
endif
AUTHORS:
$(AM_V_GEN)if test -d "$(srcdir)/.git"; \
then \
echo Creating $@ && \
( cd "$(top_srcdir)" && \
echo '# Generated by Makefile. Do not edit.'; echo; \
git log --no-merges --pretty=format:"%an" $(SUBDIRS) \
| sort | uniq ) > $@.tmp \
&& mv -f $@.tmp $@ \
|| ( rm -f $@.tmp ; \
echo Failed to generate $@ >&2 ); \
else touch $@; fi
SYSPROF_CORE = \
binfile.h \
binfile.c \
collector.c \
collector.h \
demangle.c \
elfparser.c \
elfparser.h \
profile.h \
profile.c \
sfile.h \
sfile.c \
sformat.h \
sformat.c \
stackstash.h \
stackstash.c \
tracker.h \
tracker.c \
unwind.h \
unwind.c \
watch.h \
watch.c \
\
util.h
# Generate the ChangeLog.
@GENERATE_CHANGELOG_RULES@
dist-hook: dist-ChangeLog
# module/sysprof-module.h
GITIGNOREFILES = \
**/*.swp \
*.o \
aclocal.m4 \
build-aux \
ChangeLog \
config \
config.h.in \
gtk-doc.m4 \
gtk-doc.make \
INSTALL \
$(NULL)
#
# GUI version
#
if BUILD_GUI
sysprof_SOURCES = \
$(SYSPROF_CORE) \
footreestore.c \
footreestore.h \
footreedatalist.h \
footreedatalist.c \
treeviewutils.h \
treeviewutils.c \
sysprof.c
sysprof_CPPFLAGS = \
$(GUI_DEP_CFLAGS) \
-DDATADIR=\"$(pkgdatadir)\" \
-DPIXMAPDIR=\"$(pixmapsdir)\"
sysprof_LDADD = $(GUI_DEP_LIBS)
endif
udevdir = $(sysconfdir)/udev/rules.d
dist_udev_DATA = 60-sysprof.rules
pixmapsdir = $(datadir)/pixmaps
dist_pkgdata_DATA = sysprof.glade
dist_pixmaps_DATA = sysprof-icon-16.png sysprof-icon-24.png sysprof-icon-32.png sysprof-icon-48.png sysprof-icon-256.png
#
# Command line version
#
sysprof_cli_SOURCES = \
$(SYSPROF_CORE) \
signal-handler.h \
signal-handler.c \
sysprof-cli.c
sysprof_cli_CPPFLAGS = \
$(CORE_DEP_CFLAGS)
sysprof_cli_LDADD = $(CORE_DEP_LIBS)
EXTRA_DIST = \
sysprof-icon-source.svg
#
# Test programs
#
noinst_PROGRAMS = testelf testunwind testdemangle
# testunwind
testunwind_SOURCES = \
testunwind.c \
demangle.c \
elfparser.c \
elfparser.h \
unwind.c \
unwind.h
testunwind_CPPFLAGS = $(CORE_DEP_CFLAGS)
testunwind_LDADD = $(CORE_DEP_LIBS)
# testelf
testelf_SOURCES = \
testelf.c \
demangle.c \
elfparser.c \
elfparser.h
testelf_CPPFLAGS = $(CORE_DEP_CFLAGS)
testelf_LDADD = $(CORE_DEP_LIBS)
# testdemangle
testdemangle_SOURCES = \
testdemangle.c \
elfparser.c \
elfparser.h \
demangle.c
testdemangle_CPPFLAGS = $(CORE_DEP_CFLAGS)
testdemangle_LDADD = $(CORE_DEP_LIBS)
-include $(top_srcdir)/git.mk

7
NEWS
View File

@ -1,7 +0,0 @@
- New 'everything' object
- New commandline version [Lorenzo Colitti]
- Assign time spent in kernel to the user process responsible
- New screenshot window
- Device based on udev [Kristian Hoegsberg]
- Port to RHEL4 [Bastien Nocera]
- Performance improvements

25
TODO Normal file
View File

@ -0,0 +1,25 @@
# TODO
- Follow fork/threads
Currently, when watching a pid, we do not have a way to follow forks.
We need to watch FORK/EXIT events and create/dispose additional perf
streams from the kernel.
- Add cpu/mem/net data source
- Add frame types for generic counters
- Add support for visualizations other than the callgraph.
1) I'd like to see a graph of call depth over time.
2) line graphs for cpu/mem/net.
3) disk I/O
etc.
One quick way to put together the UI is to just use ListBox for the
rows. Then we need a "graph" child that allows drawing. The nice thing
here is that we get pixel caching for free. Resizing the row height
gets a little annoying though.

View File

@ -1,21 +1,37 @@
#!/bin/sh
# Run this to generate all the initial makefiles, etc.
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=$(dirname "$0")
test -n "$srcdir" || srcdir=.
olddir=`pwd`
cd "$srcdir"
olddir=$(pwd)
mkdir m4
cd $srcdir
AUTORECONF=`which autoreconf`
if test -z $AUTORECONF; then
echo "*** No autoreconf found, please install it ***"
exit 1
else
autoreconf --force --install --verbose || exit $?
(test -f configure.ac) || {
echo "*** ERROR: Directory '$srcdir' does not look like the top-level project directory ***"
exit 1
}
# shellcheck disable=SC2016
PKG_NAME=$(autoconf --trace 'AC_INIT:$1' configure.ac)
if [ "$#" = 0 -a "x$NOCONFIGURE" = "x" ]; then
echo "*** WARNING: I am going to run 'configure' with no arguments." >&2
echo "*** If you wish to pass any to it, please specify them on the" >&2
echo "*** '$0' command line." >&2
echo "" >&2
fi
aclocal --install || exit 1
autoreconf --verbose --force --install || exit 1
cd "$olddir"
test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"
if [ "$NOCONFIGURE" = "" ]; then
$srcdir/configure "$@" || exit 1
if [ "$1" = "--help" ]; then exit 0 else
echo "Now type 'make' to compile $PKG_NAME" || exit 1
fi
else
echo "Skipping configure process."
fi

5
build-aux/tap-test Executable file
View File

@ -0,0 +1,5 @@
#! /bin/sh
# run a GTest in tap mode. The test binary is passed as $1
$1 -k --tap

148
build-aux/test-driver Executable file
View File

@ -0,0 +1,148 @@
#! /bin/sh
# test-driver - basic testsuite driver script.
scriptversion=2013-07-13.22; # UTC
# Copyright (C) 2011-2014 Free Software Foundation, Inc.
#
# 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; either version 2, or (at your option)
# any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# This file is maintained in Automake, please report
# bugs to <bug-automake@gnu.org> or send patches to
# <automake-patches@gnu.org>.
# Make unconditional expansion of undefined variables an error. This
# helps a lot in preventing typo-related bugs.
set -u
usage_error ()
{
echo "$0: $*" >&2
print_usage >&2
exit 2
}
print_usage ()
{
cat <<END
Usage:
test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
[--expect-failure={yes|no}] [--color-tests={yes|no}]
[--enable-hard-errors={yes|no}] [--]
TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
The '--test-name', '--log-file' and '--trs-file' options are mandatory.
END
}
test_name= # Used for reporting.
log_file= # Where to save the output of the test script.
trs_file= # Where to save the metadata of the test run.
expect_failure=no
color_tests=no
enable_hard_errors=yes
while test $# -gt 0; do
case $1 in
--help) print_usage; exit $?;;
--version) echo "test-driver $scriptversion"; exit $?;;
--test-name) test_name=$2; shift;;
--log-file) log_file=$2; shift;;
--trs-file) trs_file=$2; shift;;
--color-tests) color_tests=$2; shift;;
--expect-failure) expect_failure=$2; shift;;
--enable-hard-errors) enable_hard_errors=$2; shift;;
--) shift; break;;
-*) usage_error "invalid option: '$1'";;
*) break;;
esac
shift
done
missing_opts=
test x"$test_name" = x && missing_opts="$missing_opts --test-name"
test x"$log_file" = x && missing_opts="$missing_opts --log-file"
test x"$trs_file" = x && missing_opts="$missing_opts --trs-file"
if test x"$missing_opts" != x; then
usage_error "the following mandatory options are missing:$missing_opts"
fi
if test $# -eq 0; then
usage_error "missing argument"
fi
if test $color_tests = yes; then
# Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.
red='' # Red.
grn='' # Green.
lgn='' # Light green.
blu='' # Blue.
mgn='' # Magenta.
std='' # No color.
else
red= grn= lgn= blu= mgn= std=
fi
do_exit='rm -f $log_file $trs_file; (exit $st); exit $st'
trap "st=129; $do_exit" 1
trap "st=130; $do_exit" 2
trap "st=141; $do_exit" 13
trap "st=143; $do_exit" 15
# Test script is run here.
"$@" >$log_file 2>&1
estatus=$?
if test $enable_hard_errors = no && test $estatus -eq 99; then
tweaked_estatus=1
else
tweaked_estatus=$estatus
fi
case $tweaked_estatus:$expect_failure in
0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;
0:*) col=$grn res=PASS recheck=no gcopy=no;;
77:*) col=$blu res=SKIP recheck=no gcopy=yes;;
99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;;
*:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;;
*:*) col=$red res=FAIL recheck=yes gcopy=yes;;
esac
# Report the test outcome and exit status in the logs, so that one can
# know whether the test passed or failed simply by looking at the '.log'
# file, without the need of also peaking into the corresponding '.trs'
# file (automake bug#11814).
echo "$res $test_name (exit status: $estatus)" >>$log_file
# Report outcome to console.
echo "${col}${res}${std}: $test_name"
# Register the test result, and other relevant metadata.
echo ":test-result: $res" > $trs_file
echo ":global-test-result: $res" >> $trs_file
echo ":recheck: $recheck" >> $trs_file
echo ":copy-in-global-log: $gcopy" >> $trs_file
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

View File

@ -1,34 +1,113 @@
AC_PREREQ([2.63])
AC_PREREQ([2.69])
AC_INIT([sysprof], [1.3.1])
dnl ***********************************************************************
dnl Define Versioning Information
dnl ***********************************************************************
m4_define([major_version],[3])
m4_define([minor_version],[19])
m4_define([micro_version],[90])
m4_define([package_version],[major_version.minor_version.micro_version])
m4_define([bug_report_url],[https://bugzilla.gnome.org/enter_bug.cgi?product=sysprof])
m4_define([api_version],[2])
AX_IS_RELEASE([micro-version])
dnl ***********************************************************************
dnl Initialize autoconf
dnl ***********************************************************************
AC_INIT([sysprof],[package_version],[bug_report_url])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([sysprof.glade])
AC_CONFIG_SRCDIR([data/sysprof.pc.in])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
AC_SUBST([ACLOCAL_AMFLAGS], "-I m4")
AC_CANONICAL_HOST
AM_INIT_AUTOMAKE([1.10 -Wall no-define])
dnl ***********************************************************************
dnl Make version information available to autoconf files
dnl ***********************************************************************
AC_SUBST([MAJOR_VERSION],major_version)
AC_SUBST([MINOR_VERSION],minor_version)
AC_SUBST([MICRO_VERSION],micro_version)
AC_SUBST([API_VERSION],api_version)
dnl ***********************************************************************
dnl Initialize automake
dnl ***********************************************************************
AM_SILENT_RULES([yes])
AM_INIT_AUTOMAKE([1.11 foreign subdir-objects tar-ustar no-dist-gzip dist-xz -Wno-portability])
AM_MAINTAINER_MODE([enable])
AX_GENERATE_CHANGELOG
# Support silent build rules, requires at least automake-1.11. Disable
# by either passing --disable-silent-rules to configure or passing V=1
# to make
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
# Check for programs
dnl ***********************************************************************
dnl Add extra debugging with --enable-debug and --enable-compile-warnings
dnl ***********************************************************************
AX_CHECK_ENABLE_DEBUG([no],[]
[G_DISABLE_ASSERT G_DISABLE_CHECKS G_DISABLE_CAST_CHECKS])
AX_COMPILER_FLAGS
dnl ***********************************************************************
dnl Internationalization
dnl ***********************************************************************
GETTEXT_PACKAGE=AC_PACKAGE_TARNAME
AC_SUBST([GETTEXT_PACKAGE])
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [GETTEXT package name])
AM_GNU_GETTEXT_VERSION([0.19.6])
AM_GNU_GETTEXT([external])
dnl ***********************************************************************
dnl Check for required programs
dnl ***********************************************************************
AX_COMPILER_FLAGS_CXXFLAGS
AC_PROG_CC
AM_PROG_CC_C_O
AC_PROG_INSTALL
AX_CXX_COMPILE_STDCXX_0X
AC_PATH_PROG([GLIB_GENMARSHAL],[glib-genmarshal])
AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums])
AC_PATH_PROG([GLIB_COMPILE_RESOURCES],[glib-compile-resources])
PKG_PROG_PKG_CONFIG([0.22])
GLIB_GSETTINGS
GOBJECT_INTROSPECTION_CHECK([1.42.0])
VAPIGEN_CHECK
changequote(,)dnl
if test "x$GCC" = "xyes"; then
case " $CFLAGS " in
*[\ \ ]-Wall[\ \ ]*) ;;
*) CFLAGS="$CFLAGS -Wall" ;;
esac
dnl ***********************************************************************
dnl Check for required packages
dnl ***********************************************************************
PKG_CHECK_MODULES(SYSPROF, [gio-2.0 >= 2.44
gtk+-3.0 >= 3.16
polkit-gobject-1])
PKG_CHECK_MODULES(SYSTEMD, [libsystemd >= 222],
[have_systemd=yes],
[have_systemd=no])
# we require systemd for sysprofd
AM_CONDITIONAL(ENABLE_SYSPROFD, [test x$have_systemd = xyes])
# where to place systemd units if necessary
AC_ARG_WITH([systemdsystemunitdir],
AS_HELP_STRING([--with-systemdsystemunitdir=DIR],
[Directory for systemd service files]),
[],
[with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
if test "x$with_systemdsystemunitdir" != "xno"; then
AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
fi
changequote([,])dnl
dnl ***********************************************************************
dnl Override location of debug symbols for system libraries
dnl ***********************************************************************
debugdir=${libdir}/debug
# Separate debug dir
@ -48,37 +127,70 @@ AC_DEFUN([AC_DEFINE_DIR], [
])
AC_ARG_WITH(separate-debug-dir,
[ --with-separate-debug-dir=path Look for global separate debug info in this path [LIBDIR/debug]],
[debugdir="${withval}"])
[ --with-separate-debug-dir=path Look for global separate debug info in this path [LIBDIR/debug]],
[debugdir="${withval}"])
AC_DEFINE_DIR(DEBUGDIR, debugdir,
[Look for global separate debug info in this path])
# Kernel version
KMAJOR=`uname -r | cut -d"." -f 1`
KMINOR=`uname -r | cut -d"." -f 2`
KMICRO=`uname -r | cut -d"." -f 3 | cut -d"-" -f 1`
AC_SUBST([LIBEXECDIR], `eval echo "${libexecdir}"`)
# Pkgconfig dependencies
core_dep="glib-2.0 >= 2.6.0"
gui_dep="gtk+-2.0 > 2.6.0 gdk-pixbuf-2.0 pangoft2 libglade-2.0"
dnl ***********************************************************************
dnl User documentation
dnl ***********************************************************************
YELP_HELP_INIT
PKG_CHECK_MODULES(CORE_DEP, $core_dep, [], AC_MSG_ERROR([sysprof dependencies not satisfied]))
build_gui=yes
PKG_CHECK_MODULES(GUI_DEP, $gui_dep, [], build_gui=no)
dnl ***********************************************************************
dnl Initialize Libtool
dnl ***********************************************************************
LT_PREREQ([2.2])
LT_INIT
AM_CONDITIONAL([BUILD_GUI], [test "$build_gui" = yes])
# emit files
AC_SUBST(CORE_DEP_LIBS)
AC_SUBST(GUI_DEP_LIBS)
AC_SUBST(MODULE_SUBDIR)
dnl ***********************************************************************
dnl Process .in Files
dnl ***********************************************************************
AC_CONFIG_FILES([
Makefile
])
Makefile
lib/Makefile
lib/sysprof-version.h
daemon/Makefile
data/Makefile
data/icons/Makefile
data/sysprof-$API_VERSION.pc:data/sysprof.pc.in
data/org.gnome.Sysprof2.conf
data/org.gnome.sysprof2.policy
help/Makefile
po/Makefile.in
src/Makefile
tests/Makefile
tools/Makefile
],[],
[API_VERSION='$API_VERSION'])
AC_OUTPUT
echo ""
echo " ${PACKAGE} - ${VERSION}"
echo ""
echo " Options"
echo ""
echo " Prefix ............................... : ${prefix}"
echo " Libdir ............................... : ${libdir}"
echo " Libexecdir ........................... : ${libexecdir}"
echo ""
echo " Debug Directory ...................... : ${debugdir}"
echo ""
echo " Sysprofd ............................. : ${have_systemd}"
echo " Systemd System Units ................. : ${with_systemdsystemunitdir}"
echo ""

14
daemon/Makefile.am Normal file
View File

@ -0,0 +1,14 @@
if ENABLE_SYSPROFD
pkglibexec_PROGRAMS = sysprofd
sysprofd_SOURCES = \
sysprofd.c \
sd-bus-helper.c \
sd-bus-helper.h
sysprofd_LDADD = $(SYSTEMD_LIBS)
endif
-include $(top_srcdir)/git.mk

188
daemon/sd-bus-helper.c Normal file
View File

@ -0,0 +1,188 @@
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <errno.h>
#include "sd-bus-helper.h"
/*
* Various macros to simplify lifing code from sd-bus.
*/
#define assert_return(expr,val) do { if (!(expr)) return (val); } while (0)
#define _cleanup_(f) __attribute__((cleanup(f)))
#define STRV_FOREACH_PAIR(x, y, l) \
for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
/*
* To support systemd 222, we need a couple helpers that were added
* in 229. If we update code from systemd in the future, we can probably
* drop these helpres.
*/
static void
_sd_bus_message_unrefp (sd_bus_message **m)
{
if (m && *m)
{
sd_bus_message_unref (*m);
*m = NULL;
}
}
static void
_sd_bus_creds_unrefp (sd_bus_creds **c)
{
if (c && *c)
{
sd_bus_creds_unref (*c);
*c = NULL;
}
}
/*
* Begin verbatim code from systemd. Please try to keep this in sync until
* systemd exposes helpers for polkit integration.
*/
static int
check_good_user (sd_bus_message *m,
uid_t good_user)
{
_cleanup_(_sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t sender_uid;
int r;
assert (m);
if (good_user == UID_INVALID)
return 0;
r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
/* Don't trust augmented credentials for authorization */
assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM);
r = sd_bus_creds_get_euid(creds, &sender_uid);
if (r < 0)
return r;
return sender_uid == good_user;
}
int
bus_test_polkit (sd_bus_message *call,
int capability,
const char *action,
const char **details,
uid_t good_user,
bool *_challenge,
sd_bus_error *e)
{
int r;
assert (call);
assert (action);
/* Tests non-interactively! */
r = check_good_user(call, good_user);
if (r != 0)
return r;
r = sd_bus_query_sender_privilege(call, capability);
if (r < 0)
return r;
else if (r > 0)
return 1;
else {
_cleanup_(_sd_bus_message_unrefp) sd_bus_message *request = NULL;
_cleanup_(_sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int authorized = false, challenge = false;
const char *sender, **k, **v;
sender = sd_bus_message_get_sender(call);
if (!sender)
return -EBADMSG;
r = sd_bus_message_new_method_call(sd_bus_message_get_bus (call),
&request,
"org.freedesktop.PolicyKit1",
"/org/freedesktop/PolicyKit1/Authority",
"org.freedesktop.PolicyKit1.Authority",
"CheckAuthorization");
if (r < 0)
return r;
r = sd_bus_message_append(request,
"(sa{sv})s",
"system-bus-name", 1, "name", "s", sender,
action);
if (r < 0)
return r;
r = sd_bus_message_open_container(request, 'a', "{ss}");
if (r < 0)
return r;
STRV_FOREACH_PAIR(k, v, details) {
r = sd_bus_message_append(request, "{ss}", *k, *v);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(request);
if (r < 0)
return r;
r = sd_bus_message_append(request, "us", 0, NULL);
if (r < 0)
return r;
r = sd_bus_call(sd_bus_message_get_bus(call), request, 0, e, &reply);
if (r < 0) {
/* Treat no PK available as access denied */
if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) {
sd_bus_error_free(e);
return -EACCES;
}
return r;
}
r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
if (r < 0)
return r;
r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
if (r < 0)
return r;
if (authorized)
return 1;
if (_challenge) {
*_challenge = challenge;
return 0;
}
}
return -EACCES;
}

36
daemon/sd-bus-helper.h Normal file
View File

@ -0,0 +1,36 @@
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef SD_BUS_HELPER_H
#define SD_BUS_HELPER_H
#include <stdbool.h>
#include <systemd/sd-bus.h>
#define UID_INVALID ((uid_t)-1)
int bus_test_polkit (sd_bus_message *call,
int capability,
const char *action,
const char **details,
uid_t good_user,
bool *_challenge,
sd_bus_error *e);
#endif /* SD_BUS_HELPER_H */

347
daemon/sysprofd.c Normal file
View File

@ -0,0 +1,347 @@
/* sysprofd.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <linux/capability.h>
#include <linux/perf_event.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#include "sd-bus-helper.h"
#define BUS_TIMEOUT_USEC (1000000L * 10L)
static int
_perf_event_open (struct perf_event_attr *attr,
pid_t pid,
int cpu,
int group_fd,
unsigned long flags)
{
assert (attr != NULL);
/* Quick sanity check */
if (attr->sample_period < 100000)
return -EINVAL;
return syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}
#if 0
#define GOTO(l) do { \
fprintf (stderr, "GOTO: %s:%d: " #l "\n", __FUNCTION__, __LINE__); \
goto l; \
} while (0)
#else
#define GOTO(l) goto l
#endif
static int
sysprofd_perf_event_open (sd_bus_message *msg,
void *user_data,
sd_bus_error *error)
{
struct perf_event_attr attr = { 0 };
sd_bus_message *reply = NULL;
sd_bus_message *kvpair;
uint64_t flags = 0;
int disabled = 0;
int32_t wakeup_events = 149;
int32_t cpu = -1;
int32_t pid = -1;
bool challenge = false;
int32_t type = 0;
uint64_t sample_period = 0;
uint64_t sample_type = 0;
uint64_t config = 0;
int clockid = CLOCK_MONOTONIC_RAW;
int comm = 0;
int mmap_ = 0;
int task = 0;
int exclude_idle = 0;
int fd = -1;
int use_clockid = 0;
int r;
assert (msg);
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
return r;
for (;;)
{
const char *name = NULL;
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_DICT_ENTRY, "sv");
if (r < 0)
return r;
if (r == 0)
break;
r = sd_bus_message_read (msg, "s", &name);
if (r < 0)
goto cleanup;
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_VARIANT, NULL);
if (r < 0)
goto cleanup;
if (strcmp (name, "disabled") == 0)
{
r = sd_bus_message_read (msg, "b", &disabled);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "wakeup_events") == 0)
{
r = sd_bus_message_read (msg, "u", &wakeup_events);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "clockid") == 0)
{
r = sd_bus_message_read (msg, "i", &clockid);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "comm") == 0)
{
r = sd_bus_message_read (msg, "b", &comm);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "exclude_idle") == 0)
{
r = sd_bus_message_read (msg, "b", &exclude_idle);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "mmap") == 0)
{
r = sd_bus_message_read (msg, "b", &mmap_);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "config") == 0)
{
r = sd_bus_message_read (msg, "t", &config);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "sample_period") == 0)
{
r = sd_bus_message_read (msg, "t", &sample_period);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "sample_type") == 0)
{
r = sd_bus_message_read (msg, "t", &sample_type);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "task") == 0)
{
r = sd_bus_message_read (msg, "b", &task);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "type") == 0)
{
r = sd_bus_message_read (msg, "u", &type);
if (r < 0)
GOTO (cleanup);
}
else if (strcmp (name, "use_clockid") == 0)
{
r = sd_bus_message_read (msg, "b", &use_clockid);
if (r < 0)
GOTO (cleanup);
}
r = sd_bus_message_exit_container (msg);
if (r < 0)
goto cleanup;
sd_bus_message_exit_container (msg);
if (r < 0)
goto cleanup;
cleanup:
if (r < 0)
return r;
}
r = sd_bus_message_exit_container (msg);
if (r < 0)
return r;
r = sd_bus_message_read (msg, "iit", &pid, &cpu, &flags);
if (r < 0)
return r;
if (pid < -1 || cpu < -1)
return -EINVAL;
r = sd_bus_message_new_method_return (msg, &reply);
if (r < 0)
return r;
/* Authorize peer */
r = bus_test_polkit (msg,
CAP_SYS_ADMIN,
"org.gnome.sysprof2.perf-event-open",
NULL,
UID_INVALID,
&challenge,
error);
if (r < 0)
return r;
else if (r == 0)
return -EACCES;
if (!use_clockid || clockid < 0)
clockid = CLOCK_MONOTONIC_RAW;
attr.comm = !!comm;
attr.config = config;
attr.disabled = disabled;
attr.exclude_idle = !!exclude_idle;
attr.mmap = !!mmap_;
attr.sample_period = sample_period;
attr.sample_type = sample_type;
attr.size = sizeof attr;
attr.task = !!task;
attr.type = type;
attr.clockid = clockid;
attr.use_clockid = use_clockid;
attr.wakeup_events = wakeup_events;
fd = _perf_event_open (&attr, pid, cpu, -1, 0);
if (fd < 0)
{
fprintf (stderr,
"Failed to open perf event stream: %s\n",
strerror (errno));
return -EINVAL;
}
sd_bus_message_append_basic (reply, SD_BUS_TYPE_UNIX_FD, &fd);
r = sd_bus_send (NULL, reply, NULL);
sd_bus_message_unref (reply);
close (fd);
return r;
}
static const sd_bus_vtable sysprofd_vtable[] = {
SD_BUS_VTABLE_START (0),
SD_BUS_METHOD ("PerfEventOpen", "a{sv}iit", "h", sysprofd_perf_event_open, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int
main (int argc,
char *argv[])
{
sd_bus_slot *slot = NULL;
sd_bus *bus = NULL;
int r;
/* Connect to the system bus */
r = sd_bus_default_system (&bus);
if (r < 0)
{
fprintf (stderr,
"Failed to connect to system bus: %s\n",
strerror (-r));
goto failure;
}
/* Install our object */
r = sd_bus_add_object_vtable (bus,
&slot,
"/org/gnome/Sysprof2",
"org.gnome.Sysprof2",
sysprofd_vtable,
NULL);
if (r < 0)
{
fprintf (stderr,
"Failed to install object on bus: %s\n",
strerror (-r));
goto failure;
}
/* Request our well-known name on the bus */
r = sd_bus_request_name (bus, "org.gnome.Sysprof2", 0);
if (r < 0)
{
fprintf (stderr,
"Failed to register name on the bus: %s\n",
strerror (-r));
goto failure;
}
for (;;)
{
/* Process requests */
r = sd_bus_process (bus, NULL);
if (r < 0)
{
fprintf (stderr,
"Failed to process bus: %s\n",
strerror (-r));
goto failure;
}
/* If we processed a request, continue processing */
if (r > 0)
continue;
/* Wait for the next request to process */
r = sd_bus_wait (bus, BUS_TIMEOUT_USEC);
if (r < 0)
{
fprintf (stderr,
"Failed to wait on bus: %s\n",
strerror (-r));
goto failure;
}
/*
* If we timed out, we can expire, we will be auto-started by
* systemd or dbus on the next activation request.
*/
if (r == 0)
break;
}
failure:
sd_bus_slot_unref (slot);
sd_bus_unref (bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

86
data/Makefile.am Normal file
View File

@ -0,0 +1,86 @@
SUBDIRS = icons
mimedir = $(datadir)/mime/packages
mime_DATA = sysprof-mime.xml
desktopdir = $(datadir)/applications
desktop_DATA = org.gnome.Sysprof2.desktop
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = sysprof-$(API_VERSION).pc
gsettings_SCHEMAS = org.gnome.sysprof2.gschema.xml
.PRECIOUS: $(gsettings_SCHEMAS)
@GSETTINGS_RULES@
EXTRA_DIST = \
sysprof.pc.in \
org.gnome.Sysprof2.desktop \
$(mime_DATA) \
$(gsettings_SCHEMAS) \
$(NULL)
DISTCLEANFILES = $(pkgconfig_DATA)
GITIGNOREFILES = $(dbusservice_DATA)
if ENABLE_SYSPROFD
dbusservicedir = $(datadir)/dbus-1/system-services
dbusservice_in_files = org.gnome.Sysprof2.service.in
dbusservice_DATA = $(dbusservice_in_files:.service.in=.service)
$(dbusservice_DATA): $(dbusservice_in_files) Makefile
@sed -e "s|\@sysprofdprivdir\@|$(libexecdir)/$(PACKAGE)|" $< > $@
dbusconfdir = $(datadir)/dbus-1/system.d
dbusconf_in_files = org.gnome.Sysprof2.conf.in
dbusconf_DATA = $(dbusconf_in_files:.conf.in=.conf)
systemdservice_in_files = sysprof2.service.in
systemdservicedir = $(systemdsystemunitdir)
systemdservice_DATA = $(systemdservice_in_files:.service.in=.service)
$(systemdservice_DATA): $(systemdservice_in_files) Makefile
@sed -e "s|\@sysprofdprivdir\@|$(libexecdir)/$(PACKAGE)|" $< > $@
polkitdir = $(datadir)/polkit-1/actions
polkit_in_files = org.gnome.sysprof2.policy.in
polkit_DATA = $(polkit_in_files:.policy.in=.policy)
DISTCLEANFILES += \
$(systemdservice_DATA) \
$(dbusservice_DATA) \
$(NULL)
EXTRA_DIST += \
org.gnome.Sysprof2.xml \
$(dbusservice_in_files) \
$(dbusconf_in_files) \
$(systemdservice_in_files) \
$(polkit_in_files) \
$(NULL)
GITIGNOREFILES += $(systemdservice_DATA)
endif
install-data-local: install-mimeDATA
if [ -f $(DESTDIR)$(datadir)/mime/packages/freedesktop.org.xml ] ; then \
if which update-mime-database>/dev/null 2>&1; then \
update-mime-database $(DESTDIR)$(datadir)/mime; \
fi \
fi
uninstall-local:
if [ -f $(DESTDIR)$(datadir)/mime/packages/freedesktop.org.xml ] ; then \
if which update-mime-database>/dev/null 2>&1; then \
update-mime-database $(DESTDIR)$(datadir)/mime; \
fi \
fi
-include $(top_srcdir)/git.mk

29
data/icons/Makefile.am Normal file
View File

@ -0,0 +1,29 @@
icondir = $(datadir)/icons/hicolor
nobase_icon_DATA = \
16x16/apps/sysprof.png \
24x24/apps/sysprof.png \
32x32/apps/sysprof.png \
48x48/apps/sysprof.png \
256x256/apps/sysprof.png \
scalable/apps/sysprof-symbolic.svg \
$(NULL)
EXTRA_DIST = \
$(nobase_icon_DATA) \
sysprof-source.svg
gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
install-data-hook: update-icon-cache
uninstall-hook: update-icon-cache
update-icon-cache:
@-if test -z "$(DESTDIR)"; then \
echo "Updating Gtk icon cache."; \
$(gtk_update_icon_cache); \
else \
echo "*** Icon cache not updated. After (un)install, run this:"; \
echo "*** $(gtk_update_icon_cache)"; \
fi
-include $(top_srcdir)/git.mk

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only root can own the service -->
<policy user="root">
<allow own="org.gnome.Sysprof2"/>
</policy>
<!-- Anyone can send messages to the owner of org.gnome.Sysprof2 -->
<policy context="default">
<allow send_destination="org.gnome.Sysprof2"/>
</policy>
</busconfig>

View File

@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Name=Sysprof
GenericName=Profiler
Comment=Profile an application or entire system.
Exec=sysprof %u
TryExec=sysprof
Icon=sysprof
StartupNotify=true
Terminal=false
Type=Application
Categories=GNOME;Development;
MimeType=application/x-sysprof-capture;

View File

@ -0,0 +1,5 @@
[D-BUS Service]
Name=org.gnome.Sysprof2
Exec=@sysprofdprivdir@/sysprofd
User=root
SystemdService=sysprof2.service

View File

@ -0,0 +1,24 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.gnome.Sysprof2">
<!--
PerfEventOpen:
@options: key-value pair of attributes for the perf_event_open() syscall.
@pid: the process id to monitor, or -1 for system-wide.
@cpu: affinity to cpu.
@flags: flags for perf_event_open() syscall.
@perf_stream_fd: (out): A fd to communicate with perf.
Performs the perf_event_open() syscall with elevated privileges and passes
the resulting fd back to the calling process.
-->
<method name="PerfEventOpen">
<arg name="options" type="a{sv}" direction="in"/>
<arg name="pid" type="i" direction="in"/>
<arg name="cpu" type="i" direction="in"/>
<arg name="flags" type="t" direction="in"/>
<arg name="perf_stream_fd" type="h" direction="out"/>
</method>
</interface>
</node>

View File

@ -0,0 +1,34 @@
<schemalist>
<schema id="org.gnome.sysprof2" path="/org/gnome/sysprof/" gettext-domain="sysprof">
<key name="window-size" type="(ii)">
<default>(-1, -1)</default>
<summary>Window size</summary>
<description>Window size (width and height).</description>
</key>
<key name="window-position" type="(ii)">
<default>(-1,-1)</default>
<summary>Window position</summary>
<description>Window position (x and y).</description>
</key>
<key name="window-maximized" type="b">
<default>true</default>
<summary>Window maximized</summary>
<description>Window maximized state</description>
</key>
<key name="last-spawn-argv" type="s">
<default>''</default>
<summary>Last Spawn Program</summary>
<description>The last spawned program, which will be set in the UI upon restart of the application.</description>
</key>
<key name="last-spawn-inherit-env" type="b">
<default>true</default>
<summary>Last Spawn Inherit Environment</summary>
<description>If the last spawned environment inherits the parent environment.</description>
</key>
<key name="last-spawn-env" type="as">
<default>[]</default>
<summary>Last Spawn Environment</summary>
<description>The last spawned environment, which will be set in the UI upon restart of the application.</description>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<vendor>The sysprof Project</vendor>
<vendor_url>https://wiki.gnome.org/Apps/Sysprof</vendor_url>
<icon_name>sysprof</icon_name>
<action id="org.gnome.sysprof2.perf-event-open">
<description>Open a perf event stream</description>
<message>Authentication is required to access system performance counters.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>

7
data/sysprof-mime.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-sysprof-capture">
<comment>Sysprof Capture</comment>
<glob pattern="*.syscap"/>
</mime-type>
</mime-info>

11
data/sysprof.pc.in Normal file
View File

@ -0,0 +1,11 @@
prefix=@prefix@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${exec_prefix}/include
Name: libsysprof-@API_VERSION@
Description: The sysprof library for integrating profiling into IDEs
Version: @VERSION@
Libs: -L${libdir} -lsysprof-@API_VERSION@
Cflags: -I${includedir}/sysprof-@API_VERSION@
Requires: gio-2.0 gtk+-3.0

8
data/sysprof2.service.in Normal file
View File

@ -0,0 +1,8 @@
[Unit]
Description=Sysprof Daemon
[Service]
Type=dbus
BusName=org.gnome.Sysprof2
ExecStart=@sysprofdprivdir@/sysprofd

333
git.mk Normal file
View File

@ -0,0 +1,333 @@
# git.mk, a small Makefile to autogenerate .gitignore files
# for autotools-based projects.
#
# Copyright 2009, Red Hat, Inc.
# Copyright 2010,2011,2012,2013 Behdad Esfahbod
# Written by Behdad Esfahbod
#
# Copying and distribution of this file, with or without modification,
# is permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.
#
# The latest version of this file can be downloaded from:
GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk
#
# Bugs, etc, should be reported upstream at:
# https://github.com/behdad/git.mk
#
# To use in your project, import this file in your git repo's toplevel,
# then do "make -f git.mk". This modifies all Makefile.am files in
# your project to -include git.mk. Remember to add that line to new
# Makefile.am files you create in your project, or just rerun the
# "make -f git.mk".
#
# This enables automatic .gitignore generation. If you need to ignore
# more files, add them to the GITIGNOREFILES variable in your Makefile.am.
# But think twice before doing that. If a file has to be in .gitignore,
# chances are very high that it's a generated file and should be in one
# of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES.
#
# The only case that you need to manually add a file to GITIGNOREFILES is
# when remove files in one of mostlyclean-local, clean-local, distclean-local,
# or maintainer-clean-local make targets.
#
# Note that for files like editor backup, etc, there are better places to
# ignore them. See "man gitignore".
#
# If "make maintainer-clean" removes the files but they are not recognized
# by this script (that is, if "git status" shows untracked files still), send
# me the output of "git status" as well as your Makefile.am and Makefile for
# the directories involved and I'll diagnose.
#
# For a list of toplevel files that should be in MAINTAINERCLEANFILES, see
# Makefile.am.sample in the git.mk git repo.
#
# Don't EXTRA_DIST this file. It is supposed to only live in git clones,
# not tarballs. It serves no useful purpose in tarballs and clutters the
# build dir.
#
# This file knows how to handle autoconf, automake, libtool, gtk-doc,
# gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata,
# appstream.
#
# This makefile provides the following targets:
#
# - all: "make all" will build all gitignore files.
# - gitignore: makes all gitignore files in the current dir and subdirs.
# - .gitignore: make gitignore file for the current dir.
# - gitignore-recurse: makes all gitignore files in the subdirs.
#
# KNOWN ISSUES:
#
# - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the
# submodule doesn't find us. If you have configure.{in,ac} files in
# subdirs, add a proxy git.mk file in those dirs that simply does:
# "include $(top_srcdir)/../git.mk". Add more ..'s to your taste.
# And add those files to git. See vte/gnome-pty-helper/git.mk for
# example.
#
###############################################################################
# Variables user modules may want to add to toplevel MAINTAINERCLEANFILES:
###############################################################################
#
# Most autotools-using modules should be fine including this variable in their
# toplevel MAINTAINERCLEANFILES:
GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \
$(srcdir)/aclocal.m4 \
$(srcdir)/autoscan.log \
$(srcdir)/configure.scan \
`AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \
test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \
for x in \
ar-lib \
compile \
config.guess \
config.sub \
depcomp \
install-sh \
ltmain.sh \
missing \
mkinstalldirs \
test-driver \
ylwrap \
; do echo "$$AUX_DIR/$$x"; done` \
`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \
head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done`
#
# All modules should also be fine including the following variable, which
# removes automake-generated Makefile.in files:
GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \
`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \
while read f; do \
case $$f in Makefile|*/Makefile) \
test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \
done`
#
# Modules that use libtool and use AC_CONFIG_MACRO_DIR() may also include this,
# though it's harmless to include regardless.
GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \
`MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \
if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \
for x in \
libtool.m4 \
ltoptions.m4 \
ltsugar.m4 \
ltversion.m4 \
lt~obsolete.m4 \
; do echo "$$MACRO_DIR/$$x"; done; \
fi`
###############################################################################
# Default rule is to install ourselves in all Makefile.am files:
###############################################################################
git-all: git-mk-install
git-mk-install:
@echo "Installing git makefile"
@any_failed=; \
find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \
if grep 'include .*/git.mk' $$x >/dev/null; then \
echo "$$x already includes git.mk"; \
else \
failed=; \
echo "Updating $$x"; \
{ cat $$x; \
echo ''; \
echo '-include $$(top_srcdir)/git.mk'; \
} > $$x.tmp || failed=1; \
if test x$$failed = x; then \
mv $$x.tmp $$x || failed=1; \
fi; \
if test x$$failed = x; then : else \
echo "Failed updating $$x"; >&2 \
any_failed=1; \
fi; \
fi; done; test -z "$$any_failed"
git-mk-update:
wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk
.PHONY: git-all git-mk-install git-mk-update
###############################################################################
# Actual .gitignore generation:
###############################################################################
$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk
@echo "git.mk: Generating $@"
@{ \
if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \
for x in \
$(DOC_MODULE)-decl-list.txt \
$(DOC_MODULE)-decl.txt \
tmpl/$(DOC_MODULE)-unused.sgml \
"tmpl/*.bak" \
xml html \
; do echo "/$$x"; done; \
FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \
case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \
fi; \
if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
for lc in $(DOC_LINGUAS); do \
for x in \
$(if $(DOC_MODULE),$(DOC_MODULE).xml) \
$(DOC_PAGES) \
$(DOC_INCLUDES) \
; do echo "/$$lc/$$x"; done; \
done; \
for x in \
$(_DOC_OMF_ALL) \
$(_DOC_DSK_ALL) \
$(_DOC_HTML_ALL) \
$(_DOC_MOFILES) \
$(DOC_H_FILE) \
"*/.xml2po.mo" \
"*/*.omf.out" \
; do echo /$$x; done; \
fi; \
if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \
for lc in $(HELP_LINGUAS); do \
for x in \
$(HELP_FILES) \
"$$lc.stamp" \
"$$lc.mo" \
; do echo "/$$lc/$$x"; done; \
done; \
fi; \
if test "x$(gsettings_SCHEMAS)" = x; then :; else \
for x in \
$(gsettings_SCHEMAS:.xml=.valid) \
$(gsettings__enum_file) \
; do echo "/$$x"; done; \
fi; \
if test "x$(appdata_XML)" = x; then :; else \
for x in \
$(appdata_XML:.xml=.valid) \
; do echo "/$$x"; done; \
fi; \
if test "x$(appstream_XML)" = x; then :; else \
for x in \
$(appstream_XML:.xml=.valid) \
; do echo "/$$x"; done; \
fi; \
if test -f $(srcdir)/po/Makefile.in.in; then \
for x in \
po/Makefile.in.in \
po/Makefile.in.in~ \
po/Makefile.in \
po/Makefile \
po/Makevars.template \
po/POTFILES \
po/Rules-quot \
po/stamp-it \
po/.intltool-merge-cache \
"po/*.gmo" \
"po/*.header" \
"po/*.mo" \
"po/*.sed" \
"po/*.sin" \
po/$(GETTEXT_PACKAGE).pot \
intltool-extract.in \
intltool-merge.in \
intltool-update.in \
; do echo "/$$x"; done; \
fi; \
if test -f $(srcdir)/configure; then \
for x in \
autom4te.cache \
configure \
config.h \
stamp-h1 \
libtool \
config.lt \
; do echo "/$$x"; done; \
fi; \
if test "x$(DEJATOOL)" = x; then :; else \
for x in \
$(DEJATOOL) \
; do echo "/$$x.sum"; echo "/$$x.log"; done; \
echo /site.exp; \
fi; \
if test "x$(am__dirstamp)" = x; then :; else \
echo "$(am__dirstamp)"; \
fi; \
if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \
for x in \
"*.lo" \
".libs" "_libs" \
; do echo "$$x"; done; \
fi; \
for x in \
.gitignore \
$(GITIGNOREFILES) \
$(CLEANFILES) \
$(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \
$(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \
$(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \
so_locations \
$(MOSTLYCLEANFILES) \
$(TEST_LOGS) \
$(TEST_LOGS:.log=.trs) \
$(TEST_SUITE_LOG) \
$(TESTS:=.test) \
"*.gcda" \
"*.gcno" \
$(DISTCLEANFILES) \
$(am__CONFIG_DISTCLEAN_FILES) \
$(CONFIG_CLEAN_FILES) \
TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
"*.tab.c" \
$(MAINTAINERCLEANFILES) \
$(BUILT_SOURCES) \
$(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \
$(filter %_vala.stamp,$(DIST_COMMON)) \
$(filter %.vapi,$(DIST_COMMON)) \
$(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \
Makefile \
Makefile.in \
"*.orig" \
"*.rej" \
"*.bak" \
"*~" \
".*.sw[nop]" \
".dirstamp" \
; do echo "/$$x"; done; \
for x in \
"*.$(OBJEXT)" \
$(DEPDIR) \
; do echo "$$x"; done; \
} | \
sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \
sed 's@/[.]/@/@g' | \
LC_ALL=C sort | uniq > $@.tmp && \
mv $@.tmp $@;
all: $(srcdir)/.gitignore gitignore-recurse-maybe
gitignore: $(srcdir)/.gitignore gitignore-recurse
gitignore-recurse-maybe:
@for subdir in $(DIST_SUBDIRS); do \
case " $(SUBDIRS) " in \
*" $$subdir "*) :;; \
*) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \
esac; \
done
gitignore-recurse:
@for subdir in $(DIST_SUBDIRS); do \
test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \
done
maintainer-clean: gitignore-clean
gitignore-clean:
-rm -f $(srcdir)/.gitignore
.PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe

62
gresources.mk Normal file
View File

@ -0,0 +1,62 @@
# Rules for generating gresources using glib-compile-resources
#
# Define:
# glib_resources_h = header template file
# glib_resources_c = source template file
# glib_resources_xml = path to *.gresource.xml
# glib_resources_namespace = c prefix for resources
#
# before including Makefile.am.resources. You will also need to have
# the following targets already defined:
#
# CLEANFILES
# DISTCLEANFILES
# BUILT_SOURCES
# EXTRA_DIST
#
# Author: Christian Hergert <christian@hergert.me>
# Basic sanity checks
$(if $(GLIB_COMPILE_RESOURCES),,$(error Need to define GLIB_COMPILE_RESOURCES))
$(if $(or $(glib_resources_h), \
$(glib_resources_c)),, \
$(error Need to define glib_resources_h and glib_resources_c))
$(if $(glib_resources_xml),,$(error Need to define glib_resources_xml))
$(if $(glib_resources_namespace),,$(error Need to define glib_resources_namespace))
resources_xml=$(addprefix $(srcdir)/,$(glib_resources_xml))
resources_srcdir=$(dir $(resources_xml))
DISTCLEANFILES += $(glib_resources_h) $(glib_resources_c)
BUILT_SOURCES += $(glib_resources_h) $(glib_resources_c)
CLEANFILES += stamp-resources $(glib_resources_c) $(glib_resources_h)
EXTRA_DIST += \
$(glib_resources_xml) \
$(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(resources_srcdir) --generate-dependencies $(resources_xml)) \
$(NULL)
stamp-resources: $(glib_resources_c) $(resources_xml)
$(AM_V_GEN)$(GLIB_COMPILE_RESOURCES) \
--target=xgen-gr.h \
--sourcedir=$(resources_srcdir) \
--generate-header \
--c-name $(glib_resources_namespace) \
$(resources_xml) \
&& (cmp -s xgen-gr.h $(glib_resources_h) || cp -f xgen-gr.h $(glib_resources_h)) \
&& rm -f xgen-gr.h \
&& echo timestamp > $(@F)
$(glib_resources_h): stamp-resources
@true
$(glib_resources_c): $(resources_xml) $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(resources_srcdir) --generate-dependencies $(resources_xml))
$(AM_V_GEN)$(GLIB_COMPILE_RESOURCES) \
--target=xgen-gr.c \
--sourcedir=$(resources_srcdir) \
--generate-source \
--c-name $(glib_resources_namespace) \
$(resources_xml) \
&& (cmp -s xgen-gr.c $(glib_resources_c) || cp -f xgen-gr.c $(glib_resources_c)) \
&& rm -f xgen-gr.c

18
help/C/index.page Normal file
View File

@ -0,0 +1,18 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:its="http://www.w3.org/2005/11/its"
type="guide"
id="index">
<info>
<revision pkgversion="3.20" date="2016-01-23" status="stub" />
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
</info>
<title>Sysprof</title>
<section id="profiling" style="2column">
<title>Profiling</title>
</section>
</page>

57
help/C/introduction.page Normal file
View File

@ -0,0 +1,57 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:its="http://www.w3.org/2005/11/its"
type="topic"
id="introduction">
<info>
<link type="guide" xref="index"/>
<!--
<link type="guide" xref="index#features"/>
<link type="seealso" xref="anotherpageid"/>
-->
<revision pkgversion="3.20" date="2016-04-04" status="stub" />
<credit type="author">
<name>Christian Hergert</name>
<email its:translate="no">christian@hergert.me</email>
<years>2016</years>
</credit>
<include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
<desc>Welcome to <app>Sysprof</app>!</desc>
</info>
<title>Introduction</title>
<p><app>Sysprof</app> is a system profiler for Linux that targets
the GNOME desktop.</p>
<section id="what-is-a-profiler">
<info>
<link type="guide" xref="index"/>
<desc>Differences between tracing and sampling</desc>
</info>
<title>What is a Profiler?</title>
<p>A profiler is an application that records information about an
application or system while it runs. That information can be explored to
gain insight into how the application could be changed to perform
better.</p>
<p>Two common categories of software profilers exist, commonly referred
to as either tracing or sampling profilers. What is meant by tracing
profiler is that every function call executed by the program is known to the
profiler. A sampling profiler works by inspecting the state of the
program on a regular frequency and therefore does not see every function
call executed by the program.</p>
<p>Both tracing and sampling profilers have their advantages. A
notable advtantage of a sampling profiler is that the overhead is much
less than that of a tracing profiler, making it easier to use for
software that requires interactivity.</p>
<p><app>Sysprof</app> is a sampling profiler.</p>
</section>
</page>

12
help/C/legal.xml Normal file
View File

@ -0,0 +1,12 @@
<license xmlns="http://projectmallard.org/1.0/"
href="http://creativecommons.org/licenses/by-sa/4.0/">
<p>This work is licensed under a
<link href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons
Attribution-ShareAlike 4.0 International</link> license.</p>
<p>As a special exception, the copyright holders give you permission to copy,
modify, and distribute the example code contained in this documentation under
the terms of your choosing, without restriction.</p>
</license>

140
help/C/profiling.page Normal file
View File

@ -0,0 +1,140 @@
<page xmlns="http://projectmallard.org/1.0/"
xmlns:its="http://www.w3.org/2005/11/its"
type="topic"
id="profiling">
<section id="system-profiling">
<info>
<link type="guide" xref="index#profiling"/>
</info>
<title>How to profile your system</title>
<p>When <app>Sysprof</app> profiles your system, it records stack
information for all applications executing, including the Linux kernel. This
can sometimes be confusing if you only want to look at a single process. If
your application does not interact much with the host system, you may have more
success with <link href="profiling#new-process-profiling">profiling an existing
process</link>.</p>
<p>To profile your entire system, ensure the target button is set to
<em>All Processes</em> and click <em>Record</em>.</p>
<p>At this point, you may be asked to <em>authorize</em> access to
profile the system. This is required as the Linux kernel's perf
implementation requires root to perform whole-system profiling.</p>
<p>During the profiling session, you will see the number of
seconds the profile has been active. Clicking the <em>Record</em>
button again will stop the profiling session. Afterwhich, the callgraph
will be displayed.</p>
<note>
<p>If you find that the <app>sysprof</app> application is showing up in
your profiling callgraph, you might consider recording the profiling session
with <cmd>sysprof-cli</cmd>. This is a command line program that will capture
your profiling session to disk to be viewed at a later time.</p>
</note>
<p>See <link href="profiling#interpreting-results">interpreting
results</link> for more guidance.</p>
</section>
<section id="existing-process-profiling">
<info>
<link type="guide" xref="index#profiling"/>
</info>
<title>Profile an existing process</title>
<p>With <app>Sysprof</app>, you can profile one or more existing
processes on your system. First, select the <em>profiling target</em>
button next to the <em>Record</em> button. Select <em>Existing Process</em>
in the popover that is displayed. Next, select as many processes as you'd
like to profile. Processes selected for profiling will have a checkmark
next to their name.</p>
<p>After selecting your target processes, click the <em>Record</em>
button to start profiling.</p>
<p>When you have completed, click the <em>Record</em> button again
to stop profiling.</p>
<p>See <link href="profiling#interpreting-results">interpreting
results</link> for more guidance.</p>
</section>
<section id="new-process-profiling">
<info>
<link type="guide" xref="index#profiling"/>
</info>
<title>Profile a new process</title>
<p>Often times, you may need to spawn a new process to profile.
First, select the <em>profiling target</em> button next to the
<em>Record</em> button. Next, select <em>New Process</em> and fill
out the necessary information to spawn the process.</p>
<note>
<p>If you are spawning a process that requires access to your current
display, such as a GTK+ application, you will want to make sure <em>Inherit
current environment</em> is set.</p>
</note>
</section>
<section id="sysprof-cli">
<info>
<link type="guide" xref="index#profiling"/>
</info>
<title>Profiling with the sysprof-cli command line tool</title>
<p>For minimal overhead, you might consider using the <cmd>sysprof-cli</cmd>
command line tool. When run without any arguments, it will record your entire
system and save the output to <file>capture.syscap</file>. This file can be
opened with the <app>Sysprof</app> application to view the callgraph.</p>
<p>You can also attach to an existing process using
<cmd>sysprof-cli -p pid</cmd>.</p>
<p>If you would like to spawn a new process, use <cmd>sysprof-cli -c
'command'</cmd> to specify a command to be launched. The command will inherit
the current environment.</p>
</section>
<section id="interpreting-results">
<info>
<link type="guide" xref="index#profiling"/>
</info>
<title>Interpreting results</title>
<p>The profiling results in <app>Sysprof</app> are split into three
sections. On the top left is a list of all the functions profiled. They
are sorted by how often they were called during the sampling frequency.</p>
<note>
<p>It is important to note that the amount of time spent in each function
is not captured. That would require a tracing profiler to accurately record.
The percentage is calculated by determining how often that function showed
up in the current stacktrace when a sample was recorded.</p>
</note>
<p>After selecting a function from the functions list, all of the recorded
callers of that function will be displayed on the bottom left. They are also
sorted by the percentage of samples that included that function in the
stacktrace.</p>
<p>On the right, are all of the decendants of a selected function. You can
select a function either from the functions list, or the callers list.</p>
<p>You can jump into a function by activating a row in the tree of descendants
with a double-click or by pressing <key>Enter</key> or <key>Spacebar</key>.</p>
<note>
<p>If you see <em>- - kernel - -</em> in your results, that means that the
application transitioned into or from the Linux kernel. There can be many reasons
for this such as a <em>syscall</em> or <em>signal</em>.</p>
</note>
</section>
</page>

18
help/Makefile.am Normal file
View File

@ -0,0 +1,18 @@
@YELP_HELP_RULES@
HELP_ID = sysprof
# Media files
HELP_MEDIA =
# Help pages
HELP_FILES = \
index.page \
introduction.page \
profiling.page \
legal.xml
# Translated languages
HELP_LINGUAS =
-include $(top_srcdir)/git.mk

128
lib/Makefile.am Normal file
View File

@ -0,0 +1,128 @@
EXTRA_DIST =
CLEANFILES =
DISTCLEANFILES =
BUILT_SOURCES =
lib_LTLIBRARIES = libsysprof-@API_VERSION@.la
nodist_libsysprof_@API_VERSION@_la_SOURCES = \
sp-resources.c \
sp-resources.h
headersdir = $(includedir)/sysprof-@API_VERSION@
headers_DATA = \
sysprof.h \
sysprof-version.h \
sp-address.h \
sp-callgraph-profile.h \
sp-callgraph-view.h \
sp-capture-reader.h \
sp-capture-writer.h \
sp-capture-types.h \
sp-cell-renderer-percent.h \
sp-clock.h \
sp-elf-symbol-resolver.h \
sp-empty-state-view.h \
sp-failed-state-view.h \
sp-error.h \
sp-gjs-source.h \
sp-jitmap-symbol-resolver.h \
sp-kernel-symbol.h \
sp-kernel-symbol-resolver.h \
sp-map-lookaside.h \
sp-model-filter.h \
sp-perf-source.h \
sp-proc-source.h \
sp-process-model.h \
sp-process-model-item.h \
sp-process-model-row.h \
sp-profile.h \
sp-profiler.h \
sp-profiler-menu-button.h \
sp-recording-state-view.h \
sp-source.h \
sp-symbol-resolver.h \
$(NULL)
libsysprof_@API_VERSION@_la_SOURCES = \
$(headers_DATA) \
sp-address.c \
sp-callgraph-profile.c \
sp-callgraph-profile-private.h \
sp-callgraph-view.c \
sp-capture-reader.c \
sp-capture-writer.c \
sp-cell-renderer-percent.c \
sp-clock.c \
sp-elf-symbol-resolver.c \
sp-empty-state-view.c \
sp-failed-state-view.c \
sp-error.c \
sp-gjs-source.c \
sp-jitmap-symbol-resolver.c \
sp-kernel-symbol.c \
sp-kernel-symbol-resolver.c \
sp-line-reader.c \
sp-line-reader.h \
sp-map-lookaside.c \
sp-model-filter.c \
sp-perf-counter.c \
sp-perf-counter.h \
sp-perf-source.c \
sp-proc-source.c \
sp-process-model.c \
sp-process-model-item.c \
sp-process-model-row.c \
sp-profile.c \
sp-profiler.c \
sp-profiler-menu-button.c \
sp-recording-state-view.c \
sp-scrolled-window.c \
sp-scrolled-window.h \
sp-source.c \
sp-symbol-resolver.c \
util/binfile.c \
util/binfile.h \
util/demangle.cpp \
util/demangle.h \
util/elfparser.c \
util/elfparser.h \
util/stackstash.c \
util/stackstash.h \
util/util.h \
$(NULL)
libsysprof_@API_VERSION@_la_CFLAGS = \
-I$(srcdir)/util \
$(SYSPROF_CFLAGS) \
$(WARN_CFLAGS) \
$(NULL)
libsysprof_@API_VERSION@_la_CXXFLAGS = \
-I$(srcdir)/util \
$(SYSPROF_CFLAGS) \
$(WARN_CXXFLAGS) \
$(NULL)
if ENABLE_SYSPROFD
libsysprof_@API_VERSION@_la_CFLAGS += -DENABLE_SYSPROFD
endif
libsysprof_@API_VERSION@_la_LIBADD = \
$(SYSPROF_LIBS) \
$(NULL)
libsysprof_@API_VERSION@_la_LDFLAGS = \
$(WARN_LDFLAGS) \
$(NULL)
glib_resources_xml = resources/libsysprof.gresource.xml
glib_resources_c = sp-resources.c
glib_resources_h = sp-resources.h
glib_resources_namespace = sp
include $(top_srcdir)/gresources.mk
-include $(top_srcdir)/git.mk

View File

@ -1,530 +0,0 @@
/* MemProf -- memory profiler and leak detector
* Copyright 1999, 2000, 2001, Red Hat, Inc.
* Copyright 2002, Kristian Rietveld
*
* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, 2005, 2006, 2007, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
/* Most interesting code in this file is lifted from bfdutils.c
* and process.c from Memprof,
*/
#include "config.h"
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include "binfile.h"
#include "elfparser.h"
#include "util.h"
struct bin_file_t
{
int ref_count;
GList * elf_files;
char * filename;
char * undefined_name;
gulong text_offset;
gboolean inode_check;
ino_t inode;
};
static ino_t
read_inode (const char *filename)
{
struct stat statbuf;
if (strcmp (filename, "[vdso]") == 0)
return (ino_t)0;
if (stat (filename, &statbuf) < 0)
return (ino_t)-1;
return statbuf.st_ino;
}
static gboolean
already_warned (const char *name)
{
static GPtrArray *warnings;
int i;
if (!warnings)
warnings = g_ptr_array_new ();
for (i = 0; i < warnings->len; ++i)
{
if (strcmp (warnings->pdata[i], name) == 0)
return TRUE;
}
g_ptr_array_add (warnings, g_strdup (name));
return FALSE;
}
static const char *const debug_file_directory = DEBUGDIR;
static ElfParser *
get_build_id_file (ElfParser *elf)
{
const char *build_id;
GList *tries = NULL, *list;
char *init, *rest;
ElfParser *result = NULL;
char *tmp;
build_id = elf_parser_get_build_id (elf);
if (!build_id)
return NULL;
if (strlen (build_id) < 4)
return NULL;
init = g_strndup (build_id, 2);
rest = g_strdup_printf ("%s%s", build_id + 2, ".debug");
tmp = g_build_filename (
"/usr", "lib", "debug", ".build-id", init, rest, NULL);
tries = g_list_append (tries, tmp);
tmp = g_build_filename (
debug_file_directory, ".build-id", init, rest, NULL);
tries = g_list_append (tries, tmp);
for (list = tries; list != NULL; list = list->next)
{
char *name = list->data;
ElfParser *parser = elf_parser_new (name, NULL);
if (parser)
{
const char *file_id = elf_parser_get_build_id (parser);
if (file_id && strcmp (build_id, file_id) == 0)
{
result = parser;
break;
}
elf_parser_free (parser);
}
}
g_list_foreach (tries, (GFunc)g_free, NULL);
g_list_free (tries);
g_free (init);
g_free (rest);
return result;
}
static ElfParser *
get_debuglink_file (ElfParser *elf,
const char *filename,
char **new_name)
{
#define N_TRIES 4
const char *basename;
char *dir;
guint32 crc32;
GList *tries = NULL, *list;
ElfParser *result = NULL;
const char *build_id;
if (!elf)
return NULL;
basename = elf_parser_get_debug_link (elf, &crc32);
build_id = elf_parser_get_build_id (elf);
#if 0
g_print (" debug link for %s is %s\n", filename, basename);
#endif
if (!basename)
return NULL;
dir = g_path_get_dirname (filename);
tries = g_list_append (tries, g_build_filename (dir, basename, NULL));
tries = g_list_append (tries, g_build_filename (dir, ".debug", basename, NULL));
tries = g_list_append (tries, g_build_filename ("/usr", "lib", "debug", dir, basename, NULL));
tries = g_list_append (tries, g_build_filename (debug_file_directory, dir, basename, NULL));
for (list = tries; list != NULL; list = list->next)
{
const char *name = list->data;
ElfParser *parser = elf_parser_new (name, NULL);
guint32 file_crc;
const char *file_build_id;
if (parser)
{
/* If both files have build ids, and they don't match,
* there is no point computing a CRC32 that we know
* will fail
*/
file_build_id = elf_parser_get_build_id (parser);
if (build_id && file_build_id && strcmp (build_id, file_build_id) != 0)
goto skip;
file_crc = elf_parser_get_crc32 (parser);
if (file_crc == crc32)
{
result = parser;
*new_name = g_strdup (name);
break;
}
else
{
if (!already_warned (name))
{
g_print ("warning: %s has wrong crc %x, %s has crc %x)\n",
name, file_crc, filename, crc32);
}
}
skip:
elf_parser_free (parser);
}
}
g_free (dir);
g_list_foreach (tries, (GFunc)g_free, NULL);
g_list_free (tries);
return result;
}
static GList *
get_debug_binaries (GList *files,
ElfParser *elf,
const char *filename)
{
ElfParser *build_id_file;
GHashTable *seen_names;
GList *free_us = NULL;
build_id_file = get_build_id_file (elf);
if (build_id_file)
return g_list_prepend (files, build_id_file);
/* .gnu_debuglink is actually a chain of debuglinks, and
* there have been real-world cases where following it was
* necessary to get useful debug information.
*/
seen_names = g_hash_table_new (g_str_hash, g_str_equal);
while (elf)
{
char *debug_name;
if (g_hash_table_lookup (seen_names, filename))
break;
g_hash_table_insert (seen_names, (char *)filename, (char *)filename);
elf = get_debuglink_file (elf, filename, &debug_name);
if (elf)
{
files = g_list_prepend (files, elf);
free_us = g_list_prepend (free_us, debug_name);
filename = debug_name;
}
}
g_list_foreach (free_us, (GFunc)g_free, NULL);
g_list_free (free_us);
g_hash_table_destroy (seen_names);
return files;
}
static char **
get_lines (const char *format, pid_t pid)
{
char *filename = g_strdup_printf (format, pid);
char **result = NULL;
char *contents;
if (g_file_get_contents (filename, &contents, NULL, NULL))
{
result = g_strsplit (contents, "\n", -1);
g_free (contents);
}
g_free (filename);
return result;
}
static const uint8_t *
get_vdso_bytes (size_t *length)
{
static const uint8_t *bytes = NULL;
static size_t n_bytes = 0;
static gboolean has_data;
if (!has_data)
{
char **lines = get_lines ("/proc/%d/maps", getpid());
int i;
for (i = 0; lines[i] != NULL; ++i)
{
char file[256];
gulong start;
gulong end;
int count = sscanf (
lines[i], "%lx-%lx %*15s %*x %*x:%*x %*u %255s",
&start, &end, file);
if (count == 3 && strcmp (file, "[vdso]") == 0)
{
n_bytes = end - start;
/* Dup the memory here so that valgrind will only
* report one 1 byte invalid read instead of
* a ton when the elf parser scans the vdso
*
* The reason we get a spurious invalid read from
* valgrind is that we are getting the address directly
* from /proc/maps, and valgrind knows that its mmap()
* wrapper never returned that address. But since it
* is a legal mapping, it is legal to read it.
*/
bytes = g_memdup ((uint8_t *)start, n_bytes);
has_data = TRUE;
}
}
}
if (length)
*length = n_bytes;
return bytes;
}
bin_file_t *
bin_file_new (const char *filename)
{
ElfParser *elf = NULL;
bin_file_t *bf;
bf = g_new0 (bin_file_t, 1);
bf->inode_check = FALSE;
bf->filename = g_strdup (filename);
bf->undefined_name = g_strdup_printf ("In file %s", filename);
bf->ref_count = 1;
bf->elf_files = NULL;
if (strcmp (filename, "[vdso]") == 0)
{
const guint8 *vdso_bytes;
gsize length;
vdso_bytes = get_vdso_bytes (&length);
if (vdso_bytes)
elf = elf_parser_new_from_data (vdso_bytes, length);
}
else
{
elf = elf_parser_new (filename, NULL);
}
if (elf)
{
/* We need the text offset of the actual binary, not the
* (potential) debug binaries
*/
bf->text_offset = elf_parser_get_text_offset (elf);
bf->elf_files = get_debug_binaries (bf->elf_files, elf, filename);
bf->elf_files = g_list_append (bf->elf_files, elf);
bf->inode = read_inode (filename);
}
return bf;
}
void
bin_file_free (bin_file_t *bin_file)
{
if (--bin_file->ref_count == 0)
{
g_list_foreach (bin_file->elf_files, (GFunc)elf_parser_free, NULL);
g_list_free (bin_file->elf_files);
g_free (bin_file->filename);
g_free (bin_file->undefined_name);
g_free (bin_file);
}
}
const bin_symbol_t *
bin_file_lookup_symbol (bin_file_t *bin_file,
gulong address)
{
GList *list;
#if 0
g_print ("-=-=-=- \n");
g_print ("bin file lookup lookup %d\n", address);
#endif
address -= bin_file->text_offset;
#if 0
g_print ("lookup %d in %s\n", address, bin_file->filename);
#endif
for (list = bin_file->elf_files; list != NULL; list = list->next)
{
ElfParser *elf = list->data;
const ElfSym *sym = elf_parser_lookup_symbol (elf, address);
if (sym)
{
#if 0
g_print ("found %lx => %s\n", address,
bin_symbol_get_name (bin_file, sym));
#endif
return (const bin_symbol_t *)sym;
}
}
#if 0
g_print ("%lx undefined in %s (textoffset %x)\n",
address + bin_file->text_offset,
bin_file->filename,
bin_file->text_offset);
#endif
return (const bin_symbol_t *)bin_file->undefined_name;
}
gboolean
bin_file_check_inode (bin_file_t *bin_file,
ino_t inode)
{
if (bin_file->inode == inode)
return TRUE;
if (!bin_file->elf_files)
return FALSE;
if (!bin_file->inode_check)
{
g_print ("warning: Inode mismatch for %s (disk: "FMT64", memory: "FMT64")\n",
bin_file->filename,
(guint64)bin_file->inode, (guint64)inode);
bin_file->inode_check = TRUE;
}
return FALSE;
}
static const ElfSym *
get_elf_sym (bin_file_t *file,
const bin_symbol_t *symbol,
ElfParser **elf_ret)
{
GList *list;
for (list = file->elf_files; list != NULL; list = list->next)
{
const ElfSym *sym = (const ElfSym *)symbol;
ElfParser *elf = list->data;
if (elf_parser_owns_symbol (elf, sym))
{
*elf_ret = elf;
return sym;
}
}
g_critical ("Internal error: unrecognized symbol pointer");
*elf_ret = NULL;
return NULL;
}
const char *
bin_symbol_get_name (bin_file_t *file,
const bin_symbol_t *symbol)
{
if (file->undefined_name == (char *)symbol)
{
return file->undefined_name;
}
else
{
ElfParser *elf;
const ElfSym *sym;
sym = get_elf_sym (file, symbol, &elf);
return elf_parser_get_sym_name (elf, sym);
}
}
gulong
bin_symbol_get_address (bin_file_t *file,
const bin_symbol_t *symbol)
{
if (file->undefined_name == (char *)symbol)
{
return 0x0;
}
else
{
ElfParser *elf;
const ElfSym *sym;
sym = get_elf_sym (file, symbol, &elf);
return elf_parser_get_sym_address (elf, sym);
}
}

View File

@ -1,806 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <stdint.h>
#include <glib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "stackstash.h"
#include "collector.h"
#include "watch.h"
#include "elfparser.h"
#include "tracker.h"
#include <linux/perf_event.h>
#include "util.h"
#define d_print(...)
#define N_PAGES 32 /* Number of pages in the ringbuffer */
#define N_WAKEUP_EVENTS 149
typedef struct counter_t counter_t;
typedef struct sample_event_t sample_event_t;
typedef struct mmap_event_t mmap_event_t;
typedef struct comm_event_t comm_event_t;
typedef struct exit_event_t exit_event_t;
typedef struct fork_event_t fork_event_t;
typedef union counter_event_t counter_event_t;
static void process_event (Collector *collector,
counter_t *counter,
counter_event_t *event);
struct counter_t
{
Collector * collector;
int fd;
struct perf_event_mmap_page * mmap_page;
uint8_t * data;
uint64_t tail;
int cpu;
};
struct sample_event_t
{
struct perf_event_header header;
uint64_t ip;
uint32_t pid, tid;
uint64_t n_ips;
uint64_t ips[1];
};
struct comm_event_t
{
struct perf_event_header header;
uint32_t pid, tid;
char comm[1];
};
struct mmap_event_t
{
struct perf_event_header header;
uint32_t pid, tid;
uint64_t addr;
uint64_t len;
uint64_t pgoff;
char filename[1];
};
struct fork_event_t
{
struct perf_event_header header;
uint32_t pid, ppid;
uint32_t tid, ptid;
};
struct exit_event_t
{
struct perf_event_header header;
uint32_t pid, ppid;
uint32_t tid, ptid;
};
union counter_event_t
{
struct perf_event_header header;
mmap_event_t mmap;
comm_event_t comm;
sample_event_t sample;
fork_event_t fork;
exit_event_t exit;
};
struct Collector
{
CollectorFunc callback;
gpointer data;
tracker_t * tracker;
GTimeVal latest_reset;
int prev_samples;
int n_samples;
GList * counters;
gboolean use_hw_counters;
};
static int
get_n_cpus (void)
{
return sysconf (_SC_NPROCESSORS_ONLN);
}
static int
sysprof_perf_counter_open (struct perf_event_attr *attr,
pid_t pid,
int cpu,
int group_fd,
unsigned long flags)
{
#ifndef __NR_perf_counter_open
#if defined(__i386__)
#define __NR_perf_counter_open 336
#elif defined(__x86_64__)
#define __NR_perf_counter_open 298
#elif defined(__arm__)
#define __NR_perf_counter_open 364
#elif defined(__bfin__)
#define __NR_perf_counter_open 369
#elif defined(__frv__)
#define __NR_perf_counter_open 336
#elif defined(__m68k__)
#define __NR_perf_counter_open 332
#elif defined(__MICROBLAZE__)
#define __NR_perf_counter_open 366
#elif defined(__mips__) && defined(_ABIO32)
#define __NR_perf_counter_open 4333
#elif defined(__mips__) && defined(_ABIN32)
#define __NR_perf_counter_open 6296
#elif defined(__mips__) && defined(_ABI64)
#define __NR_perf_counter_open 5292
#elif defined(__mn10300__)
#define __NR_perf_counter_open 337
#elif defined(__hppa__)
#define __NR_perf_counter_open 318
#elif defined(__powerpc__) || defined(__powerpc64__)
#define __NR_perf_counter_open 319
#elif defined(__s390__)
#define __NR_perf_counter_open 331
#elif defined(__sh__) && (!defined(__SH5__) || __SH5__ == 32)
#define __NR_perf_counter_open 336
#elif defined(__sh__) && defined(__SH5__) && __SH5__ == 64
#define __NR_perf_counter_open 364
#elif defined(__sparc__) || defined(__sparc64__)
#define __NR_perf_counter_open 327
#endif
#endif
attr->size = sizeof(*attr);
return syscall (__NR_perf_counter_open, attr, pid, cpu, group_fd, flags);
}
static double
timeval_to_ms (const GTimeVal *timeval)
{
return (timeval->tv_sec * G_USEC_PER_SEC + timeval->tv_usec) / 1000.0;
}
static double
time_diff (const GTimeVal *first,
const GTimeVal *second)
{
double first_ms = timeval_to_ms (first);
double second_ms = timeval_to_ms (second);
return first_ms - second_ms;
}
#define RESET_DEAD_PERIOD 250
static gboolean
in_dead_period (Collector *collector)
{
GTimeVal now;
double diff;
g_get_current_time (&now);
diff = time_diff (&now, &collector->latest_reset);
if (diff >= 0.0 && diff < RESET_DEAD_PERIOD)
return TRUE;
return FALSE;
}
static int
get_page_size (void)
{
static int page_size;
static gboolean has_page_size = FALSE;
if (!has_page_size)
{
page_size = getpagesize();
has_page_size = TRUE;
}
return page_size;
}
static void
on_read (gpointer data)
{
counter_t *counter = data;
int mask = (N_PAGES * get_page_size() - 1);
int n_bytes = mask + 1;
gboolean skip_samples;
Collector *collector;
uint64_t head, tail;
collector = counter->collector;
tail = counter->tail;
head = counter->mmap_page->data_head;
rmb();
if (head < tail)
{
g_warning ("sysprof fails at ring buffers (head "FMT64", tail "FMT64"\n", head, tail);
tail = head;
}
#if 0
/* Verify that the double mapping works */
x = g_random_int() & mask;
g_assert (*(counter->data + x) == *(counter->data + x + n_bytes));
#endif
skip_samples = in_dead_period (collector);
#if 0
g_print ("n bytes %d\n", head - tail);
#endif
while (head - tail >= sizeof (struct perf_event_header))
{
struct perf_event_header *header;
guint8 buffer[4096];
guint8 *free_me;
free_me = NULL;
/* Note that:
*
* - perf events are a multiple of 64 bits
* - the perf event header is 64 bits
* - the data area is a multiple of 64 bits
*
* which means there will always be space for one header, which means we
* can safely dereference the size field.
*/
header = (struct perf_event_header *)(counter->data + (tail & mask));
if (header->size > head - tail)
{
/* The kernel did not generate a complete event.
* I don't think that can happen, but we may as well
* be paranoid.
*/
break;
}
if (counter->data + (tail & mask) + header->size > counter->data + n_bytes)
{
int n_before, n_after;
guint8 *b;
if (header->size > sizeof (buffer))
free_me = b = g_malloc (header->size);
else
b = buffer;
n_after = (tail & mask) + header->size - n_bytes;
n_before = header->size - n_after;
memcpy (b, counter->data + (tail & mask), n_before);
memcpy (b + n_before, counter->data, n_after);
header = (struct perf_event_header *)b;
}
if (!skip_samples || header->type != PERF_RECORD_SAMPLE)
{
if (header->type == PERF_RECORD_SAMPLE)
collector->n_samples++;
process_event (collector, counter, (counter_event_t *)header);
}
if (free_me)
g_free (free_me);
tail += header->size;
}
counter->tail = tail;
counter->mmap_page->data_tail = tail;
if (collector->callback)
{
if (collector->n_samples - collector->prev_samples >= N_WAKEUP_EVENTS)
{
gboolean first_sample = collector->prev_samples == 0;
collector->callback (first_sample, collector->data);
collector->prev_samples = collector->n_samples;
}
}
}
static void *
fail (GError **err, const char *what)
{
g_set_error (err, COLLECTOR_ERROR, COLLECTOR_ERROR_FAILED,
"%s: %s", what, g_strerror (errno));
return NULL;
}
static void *
map_buffer (counter_t *counter, GError **err)
{
int n_bytes = N_PAGES * get_page_size();
void *address;
address = mmap (NULL, n_bytes + get_page_size(), PROT_READ | PROT_WRITE, MAP_SHARED, counter->fd, 0);
if (address == MAP_FAILED)
return fail (err, "mmap");
return address;
}
static gboolean
counter_set_output (counter_t *counter, int output)
{
return ioctl (counter->fd, PERF_EVENT_IOC_SET_OUTPUT, output) == 0;
}
static void
counter_enable (counter_t *counter)
{
ioctl (counter->fd, PERF_EVENT_IOC_ENABLE);
}
static void
counter_disable (counter_t *counter)
{
d_print ("disable\n");
ioctl (counter->fd, PERF_EVENT_IOC_DISABLE);
}
static counter_t *
counter_new (Collector *collector,
pid_t pid,
int cpu,
counter_t *output,
GError **err)
{
struct perf_event_attr attr;
counter_t *counter;
int fd;
counter = g_new (counter_t, 1);
memset (&attr, 0, sizeof (attr));
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CPU_CYCLES;
attr.sample_period = 1200000 ; /* In number of clock cycles -
* FIXME: consider using frequency instead
*/
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_CALLCHAIN;
attr.wakeup_events = N_WAKEUP_EVENTS;
attr.disabled = TRUE;
attr.mmap = 1;
attr.comm = 1;
attr.task = 1;
attr.exclude_idle = 1;
if (!collector->use_hw_counters || (fd = sysprof_perf_counter_open (&attr, pid, cpu, -1, 0)) < 0)
{
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_CPU_CLOCK;
attr.sample_period = 1000000;
fd = sysprof_perf_counter_open (&attr, pid, cpu, -1, 0);
}
if (fd < 0)
return fail (err, "Could not open performance counter");
counter->collector = collector;
counter->fd = fd;
counter->cpu = cpu;
if (output && counter_set_output (counter, output->fd))
{
counter->mmap_page = NULL;
counter->data = NULL;
counter->tail = 0;
}
else
{
counter->mmap_page = map_buffer (counter, err);
if (!counter->mmap_page || counter->mmap_page == MAP_FAILED)
return NULL;
counter->data = (uint8_t *)counter->mmap_page + get_page_size ();
counter->tail = 0;
fd_add_watch (fd, counter);
fd_set_read_callback (fd, on_read);
}
return counter;
}
static void
counter_free (counter_t *counter)
{
d_print ("munmap\n");
munmap (counter->mmap_page, (N_PAGES + 1) * get_page_size());
fd_remove_watch (counter->fd);
close (counter->fd);
g_free (counter);
}
/*
* Collector
*/
static void
enable_counters (Collector *collector)
{
GList *list;
d_print ("enable\n");
for (list = collector->counters; list != NULL; list = list->next)
{
counter_t *counter = list->data;
counter_enable (counter);
}
}
static void
disable_counters (Collector *collector)
{
GList *list;
d_print ("disable\n");
for (list = collector->counters; list != NULL; list = list->next)
{
counter_t *counter = list->data;
counter_disable (counter);
}
}
void
collector_reset (Collector *collector)
{
/* Disable the counters so that we won't track
* the activity of tracker_free()/tracker_new()
*
* They will still record fork/mmap/etc. so
* we can keep an accurate log of process creation
*/
if (collector->counters)
{
d_print ("disable counters\n");
disable_counters (collector);
}
if (collector->tracker)
{
tracker_free (collector->tracker);
collector->tracker = tracker_new ();
}
collector->n_samples = 0;
collector->prev_samples = 0;
g_get_current_time (&collector->latest_reset);
if (collector->counters)
{
d_print ("enable counters\n");
enable_counters (collector);
}
}
/* callback is called whenever a new sample arrives */
Collector *
collector_new (gboolean use_hw_counters,
CollectorFunc callback,
gpointer data)
{
Collector *collector = g_new0 (Collector, 1);
collector->callback = callback;
collector->data = data;
collector->tracker = NULL;
collector->use_hw_counters = use_hw_counters;
collector_reset (collector);
return collector;
}
static void
process_mmap (Collector *collector, mmap_event_t *mmap)
{
tracker_add_map (collector->tracker,
mmap->pid,
mmap->addr,
mmap->addr + mmap->len,
mmap->pgoff,
0, /* inode */
mmap->filename);
}
static void
process_comm (Collector *collector, comm_event_t *comm)
{
d_print ("pid, tid: %d %d", comm->pid, comm->tid);
tracker_add_process (collector->tracker,
comm->pid,
comm->comm);
}
static void
process_fork (Collector *collector, fork_event_t *fork)
{
d_print ("ppid: %d pid: %d ptid: %d tid %d\n",
fork->ppid, fork->pid, fork->ptid, fork->tid);
tracker_add_fork (collector->tracker, fork->ppid, fork->pid);
}
static void
process_exit (Collector *collector, exit_event_t *exit)
{
d_print ("for %d %d", exit->pid, exit->tid);
tracker_add_exit (collector->tracker, exit->pid);
}
static void
process_sample (Collector *collector,
sample_event_t *sample)
{
uint64_t *ips;
int n_ips;
d_print ("pid, tid: %d %d", sample->pid, sample->tid);
if (sample->n_ips == 0)
{
uint64_t trace[3];
if (sample->header.misc & PERF_RECORD_MISC_KERNEL)
{
trace[0] = PERF_CONTEXT_KERNEL;
trace[1] = sample->ip;
trace[2] = PERF_CONTEXT_USER;
ips = trace;
n_ips = 3;
}
else
{
trace[0] = PERF_CONTEXT_USER;
trace[1] = sample->ip;
ips = trace;
n_ips = 2;
}
}
else
{
ips = sample->ips;
n_ips = sample->n_ips;
}
tracker_add_sample (collector->tracker,
sample->pid, ips, n_ips);
}
static void
process_event (Collector *collector,
counter_t *counter,
counter_event_t *event)
{
char *name;
switch (event->header.type)
{
case PERF_RECORD_MMAP: name = "mmap"; break;
case PERF_RECORD_LOST: name = "lost"; break;
case PERF_RECORD_COMM: name = "comm"; break;
case PERF_RECORD_EXIT: name = "exit"; break;
case PERF_RECORD_THROTTLE: name = "throttle"; break;
case PERF_RECORD_UNTHROTTLE: name = "unthrottle"; break;
case PERF_RECORD_FORK: name = "fork"; break;
case PERF_RECORD_READ: name = "read"; break;
case PERF_RECORD_SAMPLE: name = "samp"; break;
default: name = "unknown"; break;
}
d_print ("cpu %d :: %s :: ", counter->cpu, name);
switch (event->header.type)
{
case PERF_RECORD_MMAP:
process_mmap (collector, &event->mmap);
break;
case PERF_RECORD_LOST:
g_print ("lost event\n");
break;
case PERF_RECORD_COMM:
process_comm (collector, &event->comm);
break;
case PERF_RECORD_EXIT:
process_exit (collector, &event->exit);
break;
case PERF_RECORD_THROTTLE:
g_print ("throttle\n");
break;
case PERF_RECORD_UNTHROTTLE:
g_print ("unthrottle\n");
break;
case PERF_RECORD_FORK:
process_fork (collector, &event->fork);
break;
case PERF_RECORD_READ:
break;
case PERF_RECORD_SAMPLE:
process_sample (collector, &event->sample);
break;
default:
g_warning ("unknown event: %d (%d)\n",
event->header.type, event->header.size);
break;
}
d_print ("\n");
}
gboolean
collector_start (Collector *collector,
pid_t pid,
GError **err)
{
int n_cpus = get_n_cpus ();
int i;
counter_t *output;
if (!collector->tracker)
collector->tracker = tracker_new ();
output = NULL;
for (i = 0; i < n_cpus; ++i)
{
counter_t *counter = counter_new (collector, pid, i, output, err);
if (!counter)
{
GList *list;
for (list = collector->counters; list != NULL; list = list->next)
counter_free (list->data);
collector->tracker = NULL;
return FALSE;
}
collector->counters = g_list_append (collector->counters, counter);
if (!output)
output = counter;
}
enable_counters (collector);
return TRUE;
}
void
collector_stop (Collector *collector)
{
GList *list;
if (!collector->counters)
return;
/* Read any remaining data */
for (list = collector->counters; list != NULL; list = list->next)
{
counter_t *counter = list->data;
if (counter->data)
on_read (counter);
counter_free (counter);
}
g_list_free (collector->counters);
collector->counters = NULL;
}
int
collector_get_n_samples (Collector *collector)
{
return collector->n_samples;
}
Profile *
collector_create_profile (Collector *collector)
{
/* The collector must be stopped when you create a profile */
g_assert (!collector->counters);
return tracker_create_profile (collector->tracker);
}
GQuark
collector_error_quark (void)
{
static GQuark q = 0;
if (q == 0)
q = g_quark_from_static_string ("collector-error-quark");
return q;
}

View File

@ -1,46 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include "profile.h"
typedef struct Collector Collector;
typedef void (* CollectorFunc) (gboolean first_sample,
gpointer data);
#define COLLECTOR_ERROR collector_error_quark ()
GQuark collector_error_quark (void);
typedef enum
{
COLLECTOR_ERROR_FAILED
} CollectorError;
/* callback is called whenever a new sample arrives */
Collector *collector_new (gboolean use_hw_counters,
CollectorFunc callback,
gpointer data);
gboolean collector_start (Collector *collector,
pid_t pid,
GError **err);
void collector_stop (Collector *collector);
void collector_reset (Collector *collector);
int collector_get_n_samples (Collector *collector);
Profile * collector_create_profile (Collector *collector);

File diff suppressed because it is too large Load Diff

View File

@ -1,814 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2006, 2007, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include <elf.h>
#include <sys/mman.h>
#include "elfparser.h"
typedef struct Section Section;
struct ElfSym
{
gulong table;
gulong offset;
gulong address;
};
struct Section
{
const gchar * name;
gsize offset;
gsize size;
gboolean allocated;
gulong load_address;
guint type;
};
struct ElfParser
{
gboolean is_64;
const guchar * data;
gsize length;
int n_sections;
Section ** sections;
int n_symbols;
ElfSym * symbols;
gsize sym_strings;
GMappedFile * file;
char * filename;
gboolean checked_build_id;
char * build_id;
const Section * text_section;
};
/* FIXME: All of these should in principle do endian swapping,
* but sysprof never has to deal with binaries of a different
* endianness than sysprof itself
*/
#define GET_FIELD(parser, offset, struct_name, idx, field_name) \
(((parser))->is_64? \
((Elf64_ ## struct_name *)(((parser)->data + offset)) + (idx))->field_name : \
((Elf32_ ## struct_name *)(((parser)->data + offset)) + (idx))->field_name)
#define GET_UINT32(parser, offset) \
*((uint32_t *)(parser->data + offset)) \
#define GET_SIZE(parser, struct_name) \
(((parser)->is_64? \
sizeof (Elf64_ ## struct_name) : \
sizeof (Elf32_ ## struct_name)))
#define MAKE_ELF_UINT_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser) \
{ \
return GET_FIELD (parser, 0, Ehdr, 0, field_name); \
}
MAKE_ELF_UINT_ACCESSOR (e_shoff)
MAKE_ELF_UINT_ACCESSOR (e_shnum)
MAKE_ELF_UINT_ACCESSOR (e_shstrndx)
#define MAKE_SECTION_HEADER_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser, int nth_section) \
{ \
gsize offset = e_shoff (parser); \
\
return GET_FIELD (parser, offset, Shdr, nth_section, field_name); \
}
MAKE_SECTION_HEADER_ACCESSOR (sh_name);
MAKE_SECTION_HEADER_ACCESSOR (sh_type);
MAKE_SECTION_HEADER_ACCESSOR (sh_flags);
MAKE_SECTION_HEADER_ACCESSOR (sh_addr);
MAKE_SECTION_HEADER_ACCESSOR (sh_offset);
MAKE_SECTION_HEADER_ACCESSOR (sh_size);
#define MAKE_SYMBOL_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser, gulong offset, gulong nth) \
{ \
return GET_FIELD (parser, offset, Sym, nth, field_name); \
}
MAKE_SYMBOL_ACCESSOR(st_name);
MAKE_SYMBOL_ACCESSOR(st_info);
MAKE_SYMBOL_ACCESSOR(st_value);
MAKE_SYMBOL_ACCESSOR(st_size);
MAKE_SYMBOL_ACCESSOR(st_shndx);
static void
section_free (Section *section)
{
g_free (section);
}
static const Section *
find_section (ElfParser *parser,
const char *name,
guint type)
{
int i;
for (i = 0; i < parser->n_sections; ++i)
{
Section *section = parser->sections[i];
if (strcmp (section->name, name) == 0 && section->type == type)
return section;
}
return NULL;
}
static gboolean
parse_elf_signature (const guchar *data,
gsize length,
gboolean *is_64,
gboolean *is_be)
{
/* FIXME: this function should be able to return an error */
if (length < EI_NIDENT)
{
/* FIXME set error */
return FALSE;
}
if (data[EI_CLASS] != ELFCLASS32 &&
data[EI_CLASS] != ELFCLASS64)
{
/* FIXME set error */
return FALSE;
}
if (data[EI_DATA] != ELFDATA2LSB &&
data[EI_DATA] != ELFDATA2MSB)
{
/* FIXME set error */
return FALSE;
}
if (is_64)
*is_64 = (data[EI_CLASS] == ELFCLASS64);
if (is_be)
*is_be = (data[EI_DATA] == ELFDATA2MSB);
return TRUE;
}
ElfParser *
elf_parser_new_from_data (const guchar *data,
gsize length)
{
ElfParser *parser;
gboolean is_64, is_big_endian;
int section_names_idx;
const guchar *section_names;
gsize section_headers;
int i;
if (!parse_elf_signature (data, length, &is_64, &is_big_endian))
{
/* FIXME: set error */
return NULL;
}
parser = g_new0 (ElfParser, 1);
parser->is_64 = is_64;
parser->data = data;
parser->length = length;
#if 0
g_print (" new parser : %p\n", parser);
#endif
/* Read ELF header */
parser->n_sections = e_shnum (parser);
section_names_idx = e_shstrndx (parser);
section_headers = e_shoff (parser);
/* Read section headers */
parser->sections = g_new0 (Section *, parser->n_sections);
section_names = parser->data + sh_offset (parser, section_names_idx);
for (i = 0; i < parser->n_sections; ++i)
{
Section *section = g_new (Section, 1);
section->name = (char *)(section_names + sh_name (parser, i));
section->size = sh_size (parser, i);
section->offset = sh_offset (parser, i);
section->allocated = !!(sh_flags (parser, i) & SHF_ALLOC);
if (section->allocated)
section->load_address = sh_addr (parser, i);
else
section->load_address = 0;
section->type = sh_type (parser, i);
parser->sections[i] = section;
}
/* Cache the text section */
parser->text_section = find_section (parser, ".text", SHT_PROGBITS);
if (!parser->text_section)
parser->text_section = find_section (parser, ".text", SHT_NOBITS);
parser->filename = NULL;
parser->build_id = NULL;
return parser;
}
ElfParser *
elf_parser_new (const char *filename,
GError **err)
{
const guchar *data;
gsize length;
ElfParser *parser;
GMappedFile *file = g_mapped_file_new (filename, FALSE, NULL);
if (!file)
return NULL;
#if 0
g_print ("elf parser new : %s\n", filename);
#endif
data = (guchar *)g_mapped_file_get_contents (file);
length = g_mapped_file_get_length (file);
#if 0
g_print ("data %p: for %s\n", data, filename);
#endif
parser = elf_parser_new_from_data (data, length);
#if 0
g_print ("Parser for %s: %p\n", filename, parser);
#endif
if (!parser)
{
g_mapped_file_free (file);
return NULL;
}
parser->filename = g_strdup (filename);
parser->file = file;
#if 0
g_print ("Elf file: %s (debug: %s)\n",
filename, elf_parser_get_debug_link (parser, NULL));
if (!parser->symbols)
g_print ("at this point %s has no symbols\n", filename);
#endif
return parser;
}
guint32
elf_parser_get_crc32 (ElfParser *parser)
{
static const unsigned long crc32_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
const guchar *data;
gsize length;
gulong crc;
gsize i;
data = parser->data;
length = parser->length;
crc = 0xffffffff;
madvise ((char *)data, length, MADV_SEQUENTIAL);
for (i = 0; i < length; ++i)
crc = crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
/* We just read the entire file into memory, but we only really
* need the symbol table, so swap the whole thing out.
*
* We could be more exact here, but it's only a few minor
* pagefaults.
*/
if (parser->file)
madvise ((char *)data, length, MADV_DONTNEED);
return ~crc & 0xffffffff;
}
void
elf_parser_free (ElfParser *parser)
{
int i;
for (i = 0; i < parser->n_sections; ++i)
section_free (parser->sections[i]);
g_free (parser->sections);
if (parser->file)
g_mapped_file_free (parser->file);
g_free (parser->symbols);
if (parser->filename)
g_free (parser->filename);
if (parser->build_id)
g_free (parser->build_id);
g_free (parser);
}
extern char *sysprof_cplus_demangle (const char *name, int options);
char *
elf_demangle (const char *name)
{
#define DMGL_PARAMS (1 << 0) /* Include function args */
#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */
char *demangled = sysprof_cplus_demangle (name, DMGL_PARAMS | DMGL_ANSI);
if (demangled)
return demangled;
else
return g_strdup (name);
}
/*
* Looking up symbols
*/
static int
compare_sym (const void *a, const void *b)
{
const ElfSym *sym_a = a;
const ElfSym *sym_b = b;
if (sym_a->address < sym_b->address)
return -1;
else if (sym_a->address == sym_b->address)
return 0;
else
return 1;
}
#if 0
static void
dump_symbols (ElfParser *parser, ElfSym *syms, guint n_syms)
{
int i;
for (i = 0; i < n_syms; ++i)
{
ElfSym *s = &(syms[i]);
g_print (" %s: %lx\n", elf_parser_get_sym_name (parser, s), s->address);
}
}
#endif
static void
read_table (ElfParser *parser,
const Section *sym_table,
const Section *str_table)
{
int sym_size = GET_SIZE (parser, Sym);
int i, n_symbols;
#if 0
g_print ("elf: Reading table for %s\n", parser->filename? parser->filename : "<unknown>");
#endif
parser->n_symbols = sym_table->size / sym_size;
parser->symbols = g_new (ElfSym, parser->n_symbols);
#if 0
g_print ("sym table offset: %d\n", sym_table->offset);
#endif
n_symbols = 0;
#if 0
g_print ("n syms: %d\n", parser->n_symbols);
#endif
for (i = 0; i < parser->n_symbols; ++i)
{
guint info;
gulong addr;
gulong shndx;
info = st_info (parser, sym_table->offset, i);
addr = st_value (parser, sym_table->offset, i);
shndx = st_shndx (parser, sym_table->offset, i);
#if 0
g_print ("read symbol: %s (section: %d)\n", get_string_indirct (parser->parser,
parser->sym_format, "st_name",
str_table->offset),
shndx);
#endif
if (addr != 0 &&
shndx < parser->n_sections &&
parser->sections[shndx] == parser->text_section &&
(info & 0xf) == STT_FUNC &&
((info >> 4) == STB_GLOBAL ||
(info >> 4) == STB_LOCAL ||
(info >> 4) == STB_WEAK))
{
parser->symbols[n_symbols].address = addr;
parser->symbols[n_symbols].table = sym_table->offset;
parser->symbols[n_symbols].offset = i;
n_symbols++;
#if 0
g_print (" symbol: %s: %lx\n",
get_string_indirect (parser->parser,
parser->sym_format, "st_name",
str_table->offset),
addr - parser->text_section->load_address);
g_print (" sym %d in %p (info: %d:%d) (func:global %d:%d)\n",
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
#endif
}
else if (addr != 0)
{
#if 0
g_print (" rejecting %d in %p (info: %d:%d) (func:global %d:%d)\n",
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
#endif
}
}
parser->sym_strings = str_table->offset;
parser->n_symbols = n_symbols;
/* Allocate space for at least one symbol, so that parser->symbols will be
* non-NULL. If it ends up being NULL, we will be parsing the file over and
* over.
*/
parser->symbols = g_renew (ElfSym, parser->symbols, parser->n_symbols + 1);
qsort (parser->symbols, parser->n_symbols, sizeof (ElfSym), compare_sym);
}
static void
read_symbols (ElfParser *parser)
{
const Section *symtab = find_section (parser, ".symtab", SHT_SYMTAB);
const Section *strtab = find_section (parser, ".strtab", SHT_STRTAB);
const Section *dynsym = find_section (parser, ".dynsym", SHT_DYNSYM);
const Section *dynstr = find_section (parser, ".dynstr", SHT_STRTAB);
if (symtab && strtab)
{
#if 0
g_print ("reading symbol table of %s\n", parser->filename);
#endif
read_table (parser, symtab, strtab);
}
else if (dynsym && dynstr)
{
#if 0
g_print ("reading dynamic symbol table of %s\n", parser->filename);
#endif
read_table (parser, dynsym, dynstr);
}
else
{
/* To make sure parser->symbols is non-NULL */
parser->n_symbols = 0;
parser->symbols = g_new (ElfSym, 1);
}
}
static ElfSym *
do_lookup (ElfSym *symbols,
gulong address,
int first,
int last)
{
if (address >= symbols[last].address)
{
return &(symbols[last]);
}
else if (last - first < 3)
{
while (last >= first)
{
if (address >= symbols[last].address)
return &(symbols[last]);
last--;
}
return NULL;
}
else
{
int mid = (first + last) / 2;
if (symbols[mid].address > address)
return do_lookup (symbols, address, first, mid);
else
return do_lookup (symbols, address, mid, last);
}
}
/* Address should be given in 'offset into text segment' */
const ElfSym *
elf_parser_lookup_symbol (ElfParser *parser,
gulong address)
{
const ElfSym *result;
if (!parser->symbols)
{
#if 0
g_print ("reading symbols at %p\n", parser);
#endif
read_symbols (parser);
}
if (parser->n_symbols == 0)
return NULL;
if (!parser->text_section)
return NULL;
address += parser->text_section->load_address;
#if 0
g_print ("elf: the address we are looking up is %p\n", address);
#endif
result = do_lookup (parser->symbols, address, 0, parser->n_symbols - 1);
#if 0
if (result)
{
g_print (" elf: found %s at %lx\n", elf_parser_get_sym_name (parser, result), result->address);
}
else
{
g_print ("elf: not found\n");
}
#endif
if (result)
{
gulong size = st_size (parser, result->table, result->offset);
if (size > 0 && result->address + size <= address)
{
#if 0
g_print (" elf: ends at %lx, so rejecting\n",
result->address + size);
#endif
result = NULL;
}
}
if (result)
{
/* Reject the symbols if the address is outside the text section */
if (address > parser->text_section->load_address + parser->text_section->size)
result = NULL;
}
return result;
}
gulong
elf_parser_get_text_offset (ElfParser *parser)
{
g_return_val_if_fail (parser != NULL, (gulong)-1);
if (!parser->text_section)
return (gulong)-1;
return parser->text_section->offset;
}
static gchar *
make_hex_string (const guchar *data, int n_bytes)
{
static const char hex_digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
GString *string = g_string_new (NULL);
int i;
for (i = 0; i < n_bytes; ++i)
{
char c = data[i];
g_string_append_c (string, hex_digits[(c & 0xf0) >> 4]);
g_string_append_c (string, hex_digits[(c & 0x0f)]);
}
return g_string_free (string, FALSE);
}
const gchar *
elf_parser_get_build_id (ElfParser *parser)
{
if (!parser->checked_build_id)
{
const Section *build_id =
find_section (parser, ".note.gnu.build-id", SHT_NOTE);
guint64 name_size;
guint64 desc_size;
guint64 type;
const char *name;
guint64 offset;
parser->checked_build_id = TRUE;
if (!build_id)
return NULL;
offset = build_id->offset;
name_size = GET_FIELD (parser, offset, Nhdr, 0, n_namesz);
desc_size = GET_FIELD (parser, offset, Nhdr, 0, n_descsz);
type = GET_FIELD (parser, offset, Nhdr, 0, n_type);
offset += GET_SIZE (parser, Nhdr);
name = (char *)(parser->data + offset);
if (strncmp (name, ELF_NOTE_GNU, name_size) != 0 || type != NT_GNU_BUILD_ID)
return NULL;
offset += strlen (name);
offset = (offset + 3) & (~0x3);
parser->build_id = make_hex_string (parser->data + offset, desc_size);
}
return parser->build_id;
}
const char *
elf_parser_get_debug_link (ElfParser *parser, guint32 *crc32)
{
guint64 offset;
const Section *debug_link = find_section (parser, ".gnu_debuglink",
SHT_PROGBITS);
const gchar *result;
if (!debug_link)
return NULL;
offset = debug_link->offset;
result = (char *)(parser->data + offset);
if (crc32)
{
int len = strlen (result) + 1;
offset = (offset + len + 3) & ~0x3;
*crc32 = GET_UINT32 (parser, offset);
}
return result;
}
const guchar *
get_section (ElfParser *parser,
const char *name)
{
const Section *section = find_section (parser, name, SHT_PROGBITS);
if (section)
return parser->data + section->offset;
else
return NULL;
}
const guchar *
elf_parser_get_eh_frame (ElfParser *parser)
{
return get_section (parser, ".eh_frame");
}
const guchar *
elf_parser_get_debug_frame (ElfParser *parser)
{
return get_section (parser, ".debug_frame");
}
const char *
elf_parser_get_sym_name (ElfParser *parser,
const ElfSym *sym)
{
g_return_val_if_fail (parser != NULL, NULL);
return (char *)(parser->data + parser->sym_strings +
st_name (parser, sym->table, sym->offset));
}
gboolean
elf_parser_owns_symbol (ElfParser *parser,
const ElfSym *sym)
{
ElfSym *first, *last;
if (!parser->n_symbols)
return FALSE;
first = parser->symbols;
last = parser->symbols + parser->n_symbols - 1;
return first <= sym && sym <= last;
}
gulong
elf_parser_get_sym_address (ElfParser *parser,
const ElfSym *sym)
{
return sym->address - parser->text_section->load_address;
}
/*
* Utility functions
*/

View File

@ -1,577 +0,0 @@
/* gtktreedatalist.c
* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* This file contains code shared between GtkTreeStore and GtkListStore. Please
* do not use it.
*/
#include "footreedatalist.h"
#include <string.h>
static FooTreeDataList *cache;
/* node allocation
*/
#define N_DATA_LISTS (64)
FooTreeDataList *
_foo_tree_data_list_alloc (void)
{
FooTreeDataList *list;
if (!cache)
{
int i;
list = g_malloc (N_DATA_LISTS * sizeof (FooTreeDataList));
for (i = 0; i < N_DATA_LISTS; ++i)
{
list[i].next = cache;
cache = &(list[i]);
}
}
list = cache;
cache = cache->next;
memset (list, 0, sizeof (FooTreeDataList));
return list;
}
void
_foo_tree_data_list_free (FooTreeDataList *list,
GType *column_headers)
{
FooTreeDataList *tmp, *next;
gint i = 0;
tmp = list;
while (tmp)
{
next = tmp->next;
if (g_type_is_a (column_headers [i], G_TYPE_STRING))
g_free ((gchar *) tmp->data.v_pointer);
else if (g_type_is_a (column_headers [i], G_TYPE_OBJECT) && tmp->data.v_pointer != NULL)
g_object_unref (tmp->data.v_pointer);
else if (g_type_is_a (column_headers [i], G_TYPE_BOXED) && tmp->data.v_pointer != NULL)
g_boxed_free (column_headers [i], (gpointer) tmp->data.v_pointer);
tmp->next = cache;
cache = tmp;
i++;
tmp = next;
}
}
gboolean
_foo_tree_data_list_check_type (GType type)
{
gint i = 0;
static const GType type_list[] =
{
G_TYPE_BOOLEAN,
G_TYPE_CHAR,
G_TYPE_UCHAR,
G_TYPE_INT,
G_TYPE_UINT,
G_TYPE_LONG,
G_TYPE_ULONG,
G_TYPE_INT64,
G_TYPE_UINT64,
G_TYPE_ENUM,
G_TYPE_FLAGS,
G_TYPE_FLOAT,
G_TYPE_DOUBLE,
G_TYPE_STRING,
G_TYPE_POINTER,
G_TYPE_BOXED,
G_TYPE_OBJECT,
G_TYPE_INVALID
};
if (! G_TYPE_IS_VALUE_TYPE (type))
return FALSE;
while (type_list[i] != G_TYPE_INVALID)
{
if (g_type_is_a (type, type_list[i]))
return TRUE;
i++;
}
return FALSE;
}
static inline GType
get_fundamental_type (GType type)
{
GType result;
result = G_TYPE_FUNDAMENTAL (type);
if (result == G_TYPE_INTERFACE)
{
if (g_type_is_a (type, G_TYPE_OBJECT))
result = G_TYPE_OBJECT;
}
return result;
}
void
_foo_tree_data_list_node_to_value (FooTreeDataList *list,
GType type,
GValue *value)
{
g_value_init (value, type);
switch (get_fundamental_type (type))
{
case G_TYPE_BOOLEAN:
g_value_set_boolean (value, (gboolean) list->data.v_int);
break;
case G_TYPE_CHAR:
g_value_set_char (value, (gchar) list->data.v_char);
break;
case G_TYPE_UCHAR:
g_value_set_uchar (value, (guchar) list->data.v_uchar);
break;
case G_TYPE_INT:
g_value_set_int (value, (gint) list->data.v_int);
break;
case G_TYPE_UINT:
g_value_set_uint (value, (guint) list->data.v_uint);
break;
case G_TYPE_LONG:
g_value_set_long (value, list->data.v_long);
break;
case G_TYPE_ULONG:
g_value_set_ulong (value, list->data.v_ulong);
break;
case G_TYPE_INT64:
g_value_set_int64 (value, list->data.v_int64);
break;
case G_TYPE_UINT64:
g_value_set_uint64 (value, list->data.v_uint64);
break;
case G_TYPE_ENUM:
g_value_set_enum (value, list->data.v_int);
break;
case G_TYPE_FLAGS:
g_value_set_flags (value, list->data.v_uint);
break;
case G_TYPE_FLOAT:
g_value_set_float (value, (gfloat) list->data.v_float);
break;
case G_TYPE_DOUBLE:
g_value_set_double (value, (gdouble) list->data.v_double);
break;
case G_TYPE_STRING:
g_value_set_string (value, (gchar *) list->data.v_pointer);
break;
case G_TYPE_POINTER:
g_value_set_pointer (value, (gpointer) list->data.v_pointer);
break;
case G_TYPE_BOXED:
g_value_set_boxed (value, (gpointer) list->data.v_pointer);
break;
case G_TYPE_OBJECT:
g_value_set_object (value, (GObject *) list->data.v_pointer);
break;
default:
g_warning ("%s: Unsupported type (%s) retrieved.", G_STRLOC, g_type_name (value->g_type));
break;
}
}
void
_foo_tree_data_list_value_to_node (FooTreeDataList *list,
GValue *value)
{
switch (get_fundamental_type (G_VALUE_TYPE (value)))
{
case G_TYPE_BOOLEAN:
list->data.v_int = g_value_get_boolean (value);
break;
case G_TYPE_CHAR:
list->data.v_char = g_value_get_char (value);
break;
case G_TYPE_UCHAR:
list->data.v_uchar = g_value_get_uchar (value);
break;
case G_TYPE_INT:
list->data.v_int = g_value_get_int (value);
break;
case G_TYPE_UINT:
list->data.v_uint = g_value_get_uint (value);
break;
case G_TYPE_LONG:
list->data.v_long = g_value_get_long (value);
break;
case G_TYPE_ULONG:
list->data.v_ulong = g_value_get_ulong (value);
break;
case G_TYPE_INT64:
list->data.v_int64 = g_value_get_int64 (value);
break;
case G_TYPE_UINT64:
list->data.v_uint64 = g_value_get_uint64 (value);
break;
case G_TYPE_ENUM:
list->data.v_int = g_value_get_enum (value);
break;
case G_TYPE_FLAGS:
list->data.v_uint = g_value_get_flags (value);
break;
case G_TYPE_POINTER:
list->data.v_pointer = g_value_get_pointer (value);
break;
case G_TYPE_FLOAT:
list->data.v_float = g_value_get_float (value);
break;
case G_TYPE_DOUBLE:
list->data.v_double = g_value_get_double (value);
break;
case G_TYPE_STRING:
g_free (list->data.v_pointer);
list->data.v_pointer = g_value_dup_string (value);
break;
case G_TYPE_OBJECT:
if (list->data.v_pointer)
g_object_unref (list->data.v_pointer);
list->data.v_pointer = g_value_dup_object (value);
break;
case G_TYPE_BOXED:
if (list->data.v_pointer)
g_boxed_free (G_VALUE_TYPE (value), list->data.v_pointer);
list->data.v_pointer = g_value_dup_boxed (value);
break;
default:
g_warning ("%s: Unsupported type (%s) stored.", G_STRLOC, g_type_name (G_VALUE_TYPE (value)));
break;
}
}
FooTreeDataList *
_foo_tree_data_list_node_copy (FooTreeDataList *list,
GType type)
{
FooTreeDataList *new_list;
g_return_val_if_fail (list != NULL, NULL);
new_list = _foo_tree_data_list_alloc ();
new_list->next = NULL;
switch (get_fundamental_type (type))
{
case G_TYPE_BOOLEAN:
case G_TYPE_CHAR:
case G_TYPE_UCHAR:
case G_TYPE_INT:
case G_TYPE_UINT:
case G_TYPE_LONG:
case G_TYPE_ULONG:
case G_TYPE_INT64:
case G_TYPE_UINT64:
case G_TYPE_ENUM:
case G_TYPE_FLAGS:
case G_TYPE_POINTER:
case G_TYPE_FLOAT:
case G_TYPE_DOUBLE:
new_list->data = list->data;
break;
case G_TYPE_STRING:
new_list->data.v_pointer = g_strdup (list->data.v_pointer);
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
new_list->data.v_pointer = list->data.v_pointer;
if (new_list->data.v_pointer)
g_object_ref (new_list->data.v_pointer);
break;
case G_TYPE_BOXED:
if (list->data.v_pointer)
new_list->data.v_pointer = g_boxed_copy (type, list->data.v_pointer);
else
new_list->data.v_pointer = NULL;
break;
default:
g_warning ("Unsupported node type (%s) copied.", g_type_name (type));
break;
}
return new_list;
}
gint
_foo_tree_data_list_compare_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer user_data)
{
gint column = GPOINTER_TO_INT (user_data);
GType type = gtk_tree_model_get_column_type (model, column);
GValue a_value = {0, };
GValue b_value = {0, };
gint retval;
const gchar *stra, *strb;
gtk_tree_model_get_value (model, a, column, &a_value);
gtk_tree_model_get_value (model, b, column, &b_value);
switch (get_fundamental_type (type))
{
case G_TYPE_BOOLEAN:
if (g_value_get_boolean (&a_value) < g_value_get_boolean (&b_value))
retval = -1;
else if (g_value_get_boolean (&a_value) == g_value_get_boolean (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_CHAR:
if (g_value_get_char (&a_value) < g_value_get_char (&b_value))
retval = -1;
else if (g_value_get_char (&a_value) == g_value_get_char (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_UCHAR:
if (g_value_get_uchar (&a_value) < g_value_get_uchar (&b_value))
retval = -1;
else if (g_value_get_uchar (&a_value) == g_value_get_uchar (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_INT:
if (g_value_get_int (&a_value) < g_value_get_int (&b_value))
retval = -1;
else if (g_value_get_int (&a_value) == g_value_get_int (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_UINT:
if (g_value_get_uint (&a_value) < g_value_get_uint (&b_value))
retval = -1;
else if (g_value_get_uint (&a_value) == g_value_get_uint (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_LONG:
if (g_value_get_long (&a_value) < g_value_get_long (&b_value))
retval = -1;
else if (g_value_get_long (&a_value) == g_value_get_long (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_ULONG:
if (g_value_get_ulong (&a_value) < g_value_get_ulong (&b_value))
retval = -1;
else if (g_value_get_ulong (&a_value) == g_value_get_ulong (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_INT64:
if (g_value_get_int64 (&a_value) < g_value_get_int64 (&b_value))
retval = -1;
else if (g_value_get_int64 (&a_value) == g_value_get_int64 (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_UINT64:
if (g_value_get_uint64 (&a_value) < g_value_get_uint64 (&b_value))
retval = -1;
else if (g_value_get_uint64 (&a_value) == g_value_get_uint64 (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_ENUM:
/* this is somewhat bogus. */
if (g_value_get_enum (&a_value) < g_value_get_enum (&b_value))
retval = -1;
else if (g_value_get_enum (&a_value) == g_value_get_enum (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_FLAGS:
/* this is even more bogus. */
if (g_value_get_flags (&a_value) < g_value_get_flags (&b_value))
retval = -1;
else if (g_value_get_flags (&a_value) == g_value_get_flags (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_FLOAT:
if (g_value_get_float (&a_value) < g_value_get_float (&b_value))
retval = -1;
else if (g_value_get_float (&a_value) == g_value_get_float (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_DOUBLE:
if (g_value_get_double (&a_value) < g_value_get_double (&b_value))
retval = -1;
else if (g_value_get_double (&a_value) == g_value_get_double (&b_value))
retval = 0;
else
retval = 1;
break;
case G_TYPE_STRING:
stra = g_value_get_string (&a_value);
strb = g_value_get_string (&b_value);
if (stra == NULL) stra = "";
if (strb == NULL) strb = "";
retval = g_utf8_collate (stra, strb);
break;
case G_TYPE_POINTER:
case G_TYPE_BOXED:
case G_TYPE_OBJECT:
default:
g_warning ("Attempting to sort on invalid type %s\n", g_type_name (type));
retval = FALSE;
break;
}
g_value_unset (&a_value);
g_value_unset (&b_value);
return retval;
}
GList *
_foo_tree_data_list_header_new (gint n_columns,
GType *types)
{
GList *retval = NULL;
gint i;
for (i = 0; i < n_columns; i ++)
{
GtkTreeDataSortHeader *header;
header = g_slice_new (GtkTreeDataSortHeader);
retval = g_list_prepend (retval, header);
header->sort_column_id = i;
header->func = _foo_tree_data_list_compare_func;
header->destroy = NULL;
header->data = GINT_TO_POINTER (i);
}
return g_list_reverse (retval);
}
void
_foo_tree_data_list_header_free (GList *list)
{
GList *tmp;
for (tmp = list; tmp; tmp = tmp->next)
{
GtkTreeDataSortHeader *header = (GtkTreeDataSortHeader *) tmp->data;
if (header->destroy)
{
GDestroyNotify d = header->destroy;
header->destroy = NULL;
d (header->data);
}
g_slice_free (GtkTreeDataSortHeader, header);
}
g_list_free (list);
}
GtkTreeDataSortHeader *
_foo_tree_data_list_get_header (GList *header_list,
gint sort_column_id)
{
GtkTreeDataSortHeader *header = NULL;
for (; header_list; header_list = header_list->next)
{
header = (GtkTreeDataSortHeader*) header_list->data;
if (header->sort_column_id == sort_column_id)
return header;
}
return NULL;
}
GList *
_foo_tree_data_list_set_header (GList *header_list,
gint sort_column_id,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy)
{
GList *list = header_list;
GtkTreeDataSortHeader *header = NULL;
for (; list; list = list->next)
{
header = (GtkTreeDataSortHeader*) list->data;
if (header->sort_column_id == sort_column_id)
break;
header = NULL;
if (list->next == NULL)
break;
}
if (header == NULL)
{
header = g_slice_new0 (GtkTreeDataSortHeader);
header->sort_column_id = sort_column_id;
if (list)
list = g_list_append (list, header);
else
header_list = g_list_append (header_list, header);
}
if (header->destroy)
{
GDestroyNotify d = header->destroy;
header->destroy = NULL;
d (header->data);
}
header->func = func;
header->data = data;
header->destroy = destroy;
return header_list;
}

View File

@ -1,82 +0,0 @@
/* gtktreedatalist.h
* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GTK_TREE_DATA_LIST_H__
#define __GTK_TREE_DATA_LIST_H__
#include <gtk/gtk.h>
typedef struct _FooTreeDataList FooTreeDataList;
struct _FooTreeDataList
{
FooTreeDataList *next;
union {
gint v_int;
gint8 v_char;
guint8 v_uchar;
guint v_uint;
glong v_long;
gulong v_ulong;
gint64 v_int64;
guint64 v_uint64;
gfloat v_float;
gdouble v_double;
gpointer v_pointer;
} data;
};
typedef struct _GtkTreeDataSortHeader
{
gint sort_column_id;
GtkTreeIterCompareFunc func;
gpointer data;
GDestroyNotify destroy;
} GtkTreeDataSortHeader;
FooTreeDataList *_foo_tree_data_list_alloc (void);
void _foo_tree_data_list_free (FooTreeDataList *list,
GType *column_headers);
gboolean _foo_tree_data_list_check_type (GType type);
void _foo_tree_data_list_node_to_value (FooTreeDataList *list,
GType type,
GValue *value);
void _foo_tree_data_list_value_to_node (FooTreeDataList *list,
GValue *value);
FooTreeDataList *_foo_tree_data_list_node_copy (FooTreeDataList *list,
GType type);
/* Header code */
gint _foo_tree_data_list_compare_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer user_data);
GList * _foo_tree_data_list_header_new (gint n_columns,
GType *types);
void _foo_tree_data_list_header_free (GList *header_list);
GtkTreeDataSortHeader *_foo_tree_data_list_get_header (GList *header_list,
gint sort_column_id);
GList *_foo_tree_data_list_set_header (GList *header_list,
gint sort_column_id,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy);
#endif /* __GTK_TREE_DATA_LIST_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -1,158 +0,0 @@
/* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __FOO_TREE_STORE_H__
#define __FOO_TREE_STORE_H__
#include <gtk/gtk.h>
#include <stdarg.h>
G_BEGIN_DECLS
#define FOO_TYPE_TREE_STORE (foo_tree_store_get_type ())
#define FOO_TREE_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOO_TYPE_TREE_STORE, FooTreeStore))
#define FOO_TREE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), FOO_TYPE_TREE_STORE, FooTreeStoreClass))
#define FOO_IS_TREE_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOO_TYPE_TREE_STORE))
#define FOO_IS_TREE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FOO_TYPE_TREE_STORE))
#define FOO_TREE_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), FOO_TYPE_TREE_STORE, FooTreeStoreClass))
typedef struct _FooTreeStore FooTreeStore;
typedef struct _FooTreeStoreClass FooTreeStoreClass;
struct _FooTreeStore
{
GObject parent;
gint stamp;
gpointer root;
gpointer last;
gint n_columns;
gint sort_column_id;
GList *sort_list;
GtkSortType order;
GType *column_headers;
GtkTreeIterCompareFunc default_sort_func;
gpointer default_sort_data;
GDestroyNotify default_sort_destroy;
guint row_changed_id;
guint row_inserted_id;
guint row_has_child_toggled_id;
guint rows_reordered_id;
guint (columns_dirty) : 1;
};
struct _FooTreeStoreClass
{
GObjectClass parent_class;
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
};
GType foo_tree_store_get_type (void) G_GNUC_CONST;
FooTreeStore *foo_tree_store_new (gint n_columns,
...);
FooTreeStore *foo_tree_store_newv (gint n_columns,
GType *types);
void foo_tree_store_set_column_types (FooTreeStore *tree_store,
gint n_columns,
GType *types);
/* NOTE: use gtk_tree_model_get to get values from a FooTreeStore */
void foo_tree_store_set_value (FooTreeStore *tree_store,
GtkTreeIter *iter,
gint column,
GValue *value);
void foo_tree_store_set (FooTreeStore *tree_store,
GtkTreeIter *iter,
...);
void foo_tree_store_set_valuesv (FooTreeStore *tree_store,
GtkTreeIter *iter,
gint *columns,
GValue *values,
gint n_values);
void foo_tree_store_set_valist (FooTreeStore *tree_store,
GtkTreeIter *iter,
va_list var_args);
gboolean foo_tree_store_remove (FooTreeStore *tree_store,
GtkTreeIter *iter);
void foo_tree_store_insert (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint position);
void foo_tree_store_insert_before (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent,
GtkTreeIter *sibling);
void foo_tree_store_insert_after (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent,
GtkTreeIter *sibling);
void foo_tree_store_insert_with_values (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint position,
...);
void foo_tree_store_insert_with_valuesv (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint position,
gint *columns,
GValue *values,
gint n_values);
void foo_tree_store_prepend (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent);
void foo_tree_store_append (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *parent);
gboolean foo_tree_store_is_ancestor (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *descendant);
gint foo_tree_store_iter_depth (FooTreeStore *tree_store,
GtkTreeIter *iter);
void foo_tree_store_clear (FooTreeStore *tree_store);
gboolean foo_tree_store_iter_is_valid (FooTreeStore *tree_store,
GtkTreeIter *iter);
void foo_tree_store_reorder (FooTreeStore *tree_store,
GtkTreeIter *parent,
gint *new_order);
void foo_tree_store_swap (FooTreeStore *tree_store,
GtkTreeIter *a,
GtkTreeIter *b);
void foo_tree_store_move_before (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *position);
void foo_tree_store_move_after (FooTreeStore *tree_store,
GtkTreeIter *iter,
GtkTreeIter *position);
G_END_DECLS
#endif /* __FOO_TREE_STORE_H__ */

View File

@ -1,605 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include "binfile.h"
#include "stackstash.h"
#include "profile.h"
#include "sfile.h"
struct Profile
{
StackStash *stash;
};
static SFormat *
create_format (void)
{
SFormat *format;
SForward *object_forward;
SForward *node_forward;
format = sformat_new();
object_forward = sformat_declare_forward (format);
node_forward = sformat_declare_forward (format);
sformat_set_type (
format,
sformat_make_record (
format, "profile", NULL,
sformat_make_integer (format, "size"),
sformat_make_pointer (format, "call_tree", node_forward),
sformat_make_list (
format, "objects", NULL,
sformat_make_record (
format, "object", object_forward,
sformat_make_string (format, "name"),
sformat_make_integer (format, "total"),
sformat_make_integer (format, "self"),
NULL)),
sformat_make_list (
format, "nodes", NULL,
sformat_make_record (
format, "node", node_forward,
sformat_make_pointer (format, "object", object_forward),
sformat_make_pointer (format, "siblings", node_forward),
sformat_make_pointer (format, "children", node_forward),
sformat_make_pointer (format, "parent", node_forward),
sformat_make_integer (format, "total"),
sformat_make_integer (format, "self"),
sformat_make_integer (format, "toplevel"),
NULL)),
NULL));
return format;
}
static void
serialize_call_tree (StackNode *node,
SFileOutput *output)
{
if (!node)
return;
sfile_begin_add_record (output, "node");
sfile_add_pointer (output, "object", U64_TO_POINTER (node->data));
sfile_add_pointer (output, "siblings", node->siblings);
sfile_add_pointer (output, "children", node->children);
sfile_add_pointer (output, "parent", node->parent);
sfile_add_integer (output, "total", node->total);
sfile_add_integer (output, "self", node->size);
sfile_add_integer (output, "toplevel", node->toplevel);
sfile_end_add (output, "node", node);
serialize_call_tree (node->siblings, output);
serialize_call_tree (node->children, output);
}
gboolean
profile_save (Profile *profile,
const char *file_name,
GError **err)
{
gboolean result;
GList *profile_objects;
GList *list;
SFormat *format = create_format ();
SFileOutput *output = sfile_output_new (format);
sfile_begin_add_record (output, "profile");
sfile_add_integer (output, "size", profile_get_size (profile));
sfile_add_pointer (output, "call_tree",
stack_stash_get_root (profile->stash));
profile_objects = profile_get_objects (profile);
sfile_begin_add_list (output, "objects");
for (list = profile_objects; list != NULL; list = list->next)
{
ProfileObject *object = list->data;
sfile_begin_add_record (output, "object");
sfile_add_string (output, "name", object->name);
sfile_add_integer (output, "total", object->total);
sfile_add_integer (output, "self", object->self);
sfile_end_add (output, "object", object->name);
}
g_list_foreach (profile_objects, (GFunc)g_free, NULL);
g_list_free (profile_objects);
sfile_end_add (output, "objects", NULL);
sfile_begin_add_list (output, "nodes");
serialize_call_tree (stack_stash_get_root (profile->stash), output);
sfile_end_add (output, "nodes", NULL);
sfile_end_add (output, "profile", NULL);
result = sfile_output_save (output, file_name, err);
sfile_output_free (output);
sformat_free (format);
return result;
}
Profile *
profile_load (const char *filename, GError **err)
{
SFormat *format;
SFileInput *input;
Profile *profile;
int n, i;
StackNode *root;
format = create_format ();
input = sfile_load (filename, format, err);
if (!input)
return NULL;
profile = g_new (Profile, 1);
sfile_begin_get_record (input, "profile");
sfile_get_integer (input, "size", NULL);
sfile_get_pointer (input, "call_tree", (gpointer *)&root);
n = sfile_begin_get_list (input, "objects");
for (i = 0; i < n; ++i)
{
char *string;
sfile_begin_get_record (input, "object");
sfile_get_string (input, "name", &string);
sfile_get_integer (input, "total", NULL);
sfile_get_integer (input, "self", NULL);
sfile_end_get (input, "object", string);
}
sfile_end_get (input, "objects", NULL);
profile->stash = stack_stash_new ((GDestroyNotify)g_free);
n = sfile_begin_get_list (input, "nodes");
for (i = 0; i < n; ++i)
{
StackNode *node = stack_node_new (profile->stash);
gint32 size;
gint32 total;
sfile_begin_get_record (input, "node");
sfile_get_pointer (input, "object", (gpointer *)&node->data);
sfile_get_pointer (input, "siblings", (gpointer *)&node->siblings);
sfile_get_pointer (input, "children", (gpointer *)&node->children);
sfile_get_pointer (input, "parent", (gpointer *)&node->parent);
sfile_get_integer (input, "total", &total);
sfile_get_integer (input, "self", (gint32 *)&size);
sfile_get_integer (input, "toplevel", NULL);
node->total = total;
node->size = size;
sfile_end_get (input, "node", node);
g_assert (node->siblings != (void *)0x11);
}
sfile_end_get (input, "nodes", NULL);
sfile_end_get (input, "profile", NULL);
sfile_input_free (input);
sformat_free (format);
stack_stash_set_root (profile->stash, root);
return profile;
}
Profile *
profile_new (StackStash *stash)
{
Profile *profile = g_new (Profile, 1);
profile->stash = stack_stash_ref (stash);
return profile;
}
static void
add_trace_to_tree (StackLink *trace, gint size, gpointer data)
{
StackLink *link;
ProfileDescendant *parent = NULL;
ProfileDescendant **tree = data;
link = trace;
while (link->next)
link = link->next;
for (; link != NULL; link = link->prev)
{
gpointer address = U64_TO_POINTER (link->data);
ProfileDescendant *prev = NULL;
ProfileDescendant *match = NULL;
for (match = *tree; match != NULL; prev = match, match = match->siblings)
{
if (match->name == address)
{
if (prev)
{
/* Move to front */
prev->siblings = match->siblings;
match->siblings = *tree;
*tree = match;
}
break;
}
}
if (!match)
{
/* Have we seen this object further up the tree? */
for (match = parent; match != NULL; match = match->parent)
{
if (match->name == address)
break;
}
}
if (!match)
{
match = g_new (ProfileDescendant, 1);
match->name = address;
match->cumulative = 0;
match->self = 0;
match->children = NULL;
match->parent = parent;
match->siblings = *tree;
*tree = match;
}
tree = &(match->children);
parent = match;
}
parent->self += size;
while (parent)
{
parent->cumulative += size;
parent = parent->parent;
}
}
ProfileDescendant *
profile_create_descendants (Profile *profile,
char *object_name)
{
ProfileDescendant *tree = NULL;
StackNode *node = stack_stash_find_node (profile->stash, object_name);
while (node)
{
if (node->toplevel)
stack_node_foreach_trace (node, add_trace_to_tree, &tree);
node = node->next;
}
return tree;
}
static ProfileCaller *
profile_caller_new (void)
{
ProfileCaller *caller = g_new (ProfileCaller, 1);
caller->next = NULL;
caller->self = 0;
caller->total = 0;
return caller;
}
ProfileCaller *
profile_list_callers (Profile *profile,
char *callee_name)
{
StackNode *node;
StackNode *callees;
GHashTable *callers_by_name;
GHashTable *processed_callers;
ProfileCaller *result = NULL;
callers_by_name = g_hash_table_new (g_direct_hash, g_direct_equal);
processed_callers = g_hash_table_new (g_direct_hash, g_direct_equal);
callees = stack_stash_find_node (profile->stash, callee_name);
for (node = callees; node != NULL; node = node->next)
{
ProfileCaller *caller;
if (!node->parent)
continue;
caller = g_hash_table_lookup (
callers_by_name, U64_TO_POINTER (node->parent->data));
if (!caller)
{
caller = profile_caller_new ();
caller->name = U64_TO_POINTER (node->parent->data);
caller->total = 0;
caller->self = 0;
caller->next = result;
result = caller;
g_hash_table_insert (
callers_by_name, U64_TO_POINTER (node->parent->data), caller);
}
}
for (node = callees; node != NULL; node = node->next)
{
StackNode *top_caller = node->parent;
StackNode *top_callee = node;
StackNode *n;
ProfileCaller *caller;
if (!node->parent)
continue;
for (n = node; n && n->parent; n = n->parent)
{
if (n->data == node->data &&
n->parent->data == node->parent->data)
{
top_caller = n->parent;
top_callee = n;
}
}
caller = g_hash_table_lookup (
callers_by_name, U64_TO_POINTER (node->parent->data));
if (!g_hash_table_lookup (processed_callers, top_caller))
{
caller->total += top_callee->total;
g_hash_table_insert (processed_callers, top_caller, top_caller);
}
caller->self += node->size;
}
g_hash_table_destroy (processed_callers);
g_hash_table_destroy (callers_by_name);
return result;
}
#if 0
/* This code generates a list of all ancestors, rather than
* all callers. It turned out to not work well in practice,
* but on the other hand the single list of callers is not
* all that great either, so we'll keep it around commented
* out for now
*/
static void
add_to_list (gpointer key,
gpointer value,
gpointer user_data)
{
GList **list = user_data;
*list = g_list_prepend (*list, value);
}
static GList *
listify_hash_table (GHashTable *hash_table)
{
GList *result = NULL;
g_hash_table_foreach (hash_table, add_to_list, &result);
return result;
}
ProfileCaller *
profile_list_ancestors (Profile *profile,
char *callee_name)
{
StackNode *callees;
StackNode *node;
GHashTable *callers_by_name;
ProfileCaller *result = NULL;
callers_by_name = g_hash_table_new (g_direct_hash, g_direct_equal);
callees = stack_stash_find_node (profile->stash, callee_name);
for (node = callees; node != NULL; node = node->next)
{
StackNode *n;
gboolean seen_recursive_call;
GHashTable *total_ancestors;
GHashTable *all_ancestors;
GList *all, *list;
/* Build a list of those ancestors that should get assigned
* totals. If this callee does not have any recursive calls
* higher up, that means all of it's ancestors. If it does
* have a recursive call, only the one between this node
* and the recursive call should get assigned total
*/
seen_recursive_call = FALSE;
all_ancestors = g_hash_table_new (g_direct_hash, g_direct_equal);
total_ancestors = g_hash_table_new (g_direct_hash, g_direct_equal);
for (n = node->parent; n; n = n->parent)
{
if (!seen_recursive_call)
{
g_hash_table_insert (total_ancestors, n->address, n);
}
else
{
g_hash_table_remove (total_ancestors, n->address);
}
g_hash_table_insert (all_ancestors, n->address, n);
if (n->address == node->address)
seen_recursive_call = TRUE;
}
all = listify_hash_table (all_ancestors);
for (list = all; list; list = list->next)
{
ProfileCaller *caller;
StackNode *ancestor = list->data;
caller = g_hash_table_lookup (callers_by_name, ancestor->address);
if (!caller)
{
caller = profile_caller_new ();
g_hash_table_insert (
callers_by_name, ancestor->address, caller);
caller->name = ancestor->address;
caller->next = result;
result = caller;
}
caller->self += node->size;
if (g_hash_table_lookup (total_ancestors, ancestor->address))
caller->total += node->total;
}
g_list_free (all);
g_hash_table_destroy (all_ancestors);
g_hash_table_destroy (total_ancestors);
}
return result;
}
#endif
void
profile_free (Profile *profile)
{
stack_stash_unref (profile->stash);
g_free (profile);
}
void
profile_descendant_free (ProfileDescendant *descendant)
{
if (!descendant)
return;
profile_descendant_free (descendant->siblings);
profile_descendant_free (descendant->children);
g_free (descendant);
}
void
profile_caller_free (ProfileCaller *caller)
{
if (!caller)
return;
profile_caller_free (caller->next);
g_free (caller);
}
static int
compute_total (StackNode *node)
{
StackNode *n;
int total = 0;
for (n = node; n != NULL; n = n->next)
{
if (n->toplevel)
total += n->total;
}
return total;
}
static void
build_object_list (StackNode *node, gpointer data)
{
GList **objects = data;
ProfileObject *obj;
StackNode *n;
obj = g_new (ProfileObject, 1);
obj->name = U64_TO_POINTER (node->data);
obj->total = compute_total (node);
obj->self = 0;
for (n = node; n != NULL; n = n->next)
obj->self += n->size;
*objects = g_list_prepend (*objects, obj);
}
GList *
profile_get_objects (Profile *profile)
{
GList *objects = NULL;
stack_stash_foreach_by_address (
profile->stash, build_object_list, &objects);
return objects;
}
gint
profile_get_size (Profile *profile)
{
StackNode *n;
gint size = 0;
for (n = stack_stash_get_root (profile->stash); n != NULL; n = n->siblings)
size += n->total;
return size;
}

View File

@ -1,78 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#ifndef PROFILE_H
#define PROFILE_H
#include <glib.h>
#include "stackstash.h"
typedef struct Profile Profile;
typedef struct ProfileObject ProfileObject;
typedef struct ProfileDescendant ProfileDescendant;
typedef struct ProfileCaller ProfileCaller;
struct ProfileObject
{
char * name; /* identifies this object uniquely
* (the pointer itself, not the
* string)
*/
guint total; /* sum of all toplevel totals */
guint self; /* sum of all selfs */
};
struct ProfileDescendant
{
char * name;
guint self;
guint cumulative;
ProfileDescendant * parent;
ProfileDescendant * siblings;
ProfileDescendant * children;
};
struct ProfileCaller
{
char * name;
guint total;
guint self;
ProfileCaller * next;
};
Profile * profile_new (StackStash *stash);
void profile_free (Profile *profile);
gint profile_get_size (Profile *profile);
GList * profile_get_objects (Profile *profile);
ProfileDescendant *profile_create_descendants (Profile *prf,
char *object);
ProfileCaller * profile_list_callers (Profile *profile,
char *object);
void profile_caller_free (ProfileCaller *caller);
void profile_descendant_free (ProfileDescendant *descendant);
gboolean profile_save (Profile *profile,
const char *file_name,
GError **err);
Profile * profile_load (const char *filename,
GError **err);
#endif /* PROFILE_H */

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<gresources>
<gresource prefix="/org/gnome/sysprof">
<file compressed="true">ui/sp-callgraph-view.ui</file>
<file compressed="true">ui/sp-empty-state-view.ui</file>
<file compressed="true">ui/sp-failed-state-view.ui</file>
<file compressed="true">ui/sp-process-model-row.ui</file>
<file compressed="true">ui/sp-profiler-menu-button.ui</file>
<file compressed="true">ui/sp-recording-state-view.ui</file>
</gresource>
</gresources>

View File

@ -0,0 +1,198 @@
<interface>
<template class="SpCallgraphView" parent="GtkBin">
<child>
<object class="GtkPaned">
<property name="orientation">horizontal</property>
<property name="position">450</property>
<property name="visible">true</property>
<child>
<object class="GtkPaned">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="functions_view">
<property name="fixed-height-mode">true</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="function_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Functions</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="callers_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="callers_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Callers</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="descendants_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="descendants_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Descendants</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Cumulative</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SpEmptyStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">sysprof-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Welcome to Sysprof</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="2"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Start profiling your system with the &lt;b&gt;Record&lt;/b&gt; button above</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0"?>
<interface>
<template class="SpFailedStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">computer-fail-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Ouch, that hurt!</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="2"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Something unexpectedly went wrong while trying to profile your system.</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<interface>
<template class="SpProcessModelRow" parent="GtkListBoxRow">
<child>
<object class="GtkBox">
<property name="visible">true</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image">
<!-- Todo -->
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="hexpand">false</property>
<property name="visible">true</property>
<property name="ellipsize">middle</property>
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkImage" id="check">
<property name="hexpand">false</property>
<property name="icon-name">object-select-symbolic</property>
<property name="visible">false</property>
</object>
</child>
<child>
<object class="GtkLabel" id="pid">
<property name="hexpand">true</property>
<property name="visible">true</property>
<property name="xalign">1.0</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,221 @@
<?xml version="1.0"?>
<interface>
<template class="SpProfilerMenuButton" parent="GtkMenuButton">
<property name="popover">popover</property>
<property name="width-request">150</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<property name="visible">true</property>
<child>
<object class="GtkLabel" id="label">
<property name="ellipsize">end</property>
<property name="hexpand">true</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">pan-down-symbolic</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</template>
<object class="SpProcessModel" id="process_model">
</object>
<object class="GtkPopover" id="popover">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
<object class="GtkStackSwitcher">
<property name="border-width">6</property>
<property name="halign">center</property>
<property name="orientation">horizontal</property>
<property name="stack">stack</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="visible">true</property>
<property name="hexpand">true</property>
<property name="margin">10</property>
<child type="center">
<object class="GtkLabel">
<property name="hexpand">true</property>
<property name="label" translatable="yes">Profile my _entire system</property>
<property name="use-underline">true</property>
<property name="visible">true</property>
<property name="xalign">1</property>
<property name="mnemonic-widget">whole_system_switch</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="whole_system_switch">
<property name="visible">true</property>
</object>
<packing>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="hhomogeneous">true</property>
<property name="vhomogeneous">false</property>
<property name="interpolate-size">true</property>
<property name="visible">true</property>
<property name="border-width">10</property>
<child>
<object class="GtkBox" id="processes_box">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkEntry" id="process_filter_entry">
<property name="placeholder-text" translatable="yes">Search</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="SpScrolledWindow">
<property name="min-content-width">100</property>
<property name="max-content-width">400</property>
<property name="max-content-height">450</property>
<property name="shadow-type">in</property>
<property name="visible">true</property>
<child>
<object class="GtkListBox" id="process_list_box">
<property name="visible">true</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">existing</property>
<property name="title" translatable="yes">Existing Process</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Command Line</property>
<property name="visible">true</property>
<property name="xalign">0.0</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkEntry" id="spawn_entry">
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Environment</property>
<property name="visible">true</property>
<property name="xalign">0.0</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkCheckButton" id="inherit_environ">
<property name="active">true</property>
<property name="label" translatable="yes">Inherit current environment</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="SpScrolledWindow">
<property name="shadow-type">in</property>
<property name="min-content-height">100</property>
<property name="max-content-height">400</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="env_tree_view">
<property name="model">environment_model</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="env_key_column">
<property name="expand">true</property>
<property name="resizable">true</property>
<property name="title" translatable="yes">Key</property>
<child>
<object class="GtkCellRendererText" id="key_cell">
<property name="editable">true</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="env_value_column">
<property name="expand">true</property>
<property name="resizable">true</property>
<property name="title" translatable="yes">Value</property>
<child>
<object class="GtkCellRendererText" id="value_cell">
<property name="editable">true</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">spawn</property>
<property name="title" translatable="yes">New Process</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="environment_model">
<columns>
<column type="gchararray"/>
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0"></col>
<col id="1"></col>
</row>
</data>
</object>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0"?>
<interface>
<template class="SpRecordingStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">sysprof-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="elapsed">
<property name="label" translatable="yes">00:00</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="4"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Did you know you can use &lt;a href="help:sysprof"&gt;sysprof-cli&lt;/a&gt; to record?</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

File diff suppressed because it is too large Load Diff

View File

@ -1,169 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include "sformat.h"
typedef struct SFileInput SFileInput;
typedef struct SFileOutput SFileOutput;
#if 0
Serializer *serializer_new (const char *version);
void serializer_set_format (Serializer *serializer,
SerializerFormat *format);
SerializerFormat *serializer_make_list (Serializer *serializer,
const char *name,
SerializerFormat *contents);
SerializerFormat *serializer_make_record (Serializer *serializer,
const char *name,
SerializerFormat *contents1,
...);
SerializerFormat *serializer_make_integer (Serializer *serialiser,
const char *name);
SerializerFormat *serializer_make_pointer (Serializer *serialiser,
const char *name,
SerializerFormat *target_type);
#endif
/* A possibly better API/naming scheme
*
* Serializer *serializer_new (SerializerFormat *format);
*
* SerializerReadContext *serializer_begin_read_filename (serializer *serialize,
* const char *filename,
* GError *err);
* serializer_get_blah (SerializerReadContext *);
* void serialzier_end_read (...);
*
* SerializerWritecontext *...;
* serializer_begin_write (context);
* serializer_write_int ();
* serializer_end_write (..., GError **err);
*
*
* For formats consider:
*
* Format *format_new (void);
* void format_free (void);
* Content *format_create_record (Format *format, Content *c1, ...);
* Content *format_create_list (Format *format, Content *t);
* Content *format_create_pointer (Format *format, Content *pointer_type);
*
* void format_set_record (Format *f, Content *r);
* Content *new_record (Content *c1, ...);
*
* List *format_new_list (Format *f
*
*
* Consider adding optional elements:
*
* sformat_new_optional (gpointer content)
*
* enums, optionals, selections, empties
*
*
* Other things:
*
* "selections" - when several different types are possible - would need lambda transitions in and out
*
* ability to allow 'ignored' elements that are simply skipped at parse time. This could become important
* for future-proofing files.
*
* unions maybe?
*
*
*
*
*==============================================
* Also think about versioning - apps will want to be able to read and write
* different versions of the format, and they want to be able to sniff the
* format + version
*
* The version should be part of the format. There should be a
* const char *sfile_sniff (const filename);
* that will return NULL (+ error) if the file can't be parsed
*
*/
/* - Describing Types - */
/* - Reading - */
SFileInput * sfile_load (const char *filename,
SFormat *format,
GError **err);
void sfile_begin_get_record (SFileInput *file,
const char *name);
int sfile_begin_get_list (SFileInput *file,
const char *name);
void sfile_get_pointer (SFileInput *file,
const char *name,
gpointer *pointer);
void sfile_get_integer (SFileInput *file,
const char *name,
gint32 *integer);
void sfile_get_string (SFileInput *file,
const char *name,
char **string);
void sfile_end_get (SFileInput *file,
const char *name,
gpointer object);
void sfile_input_free (SFileInput *file);
#if 0
/* incremental loading (worth considering at least) */
SFileLoader *sfile_loader_new (SFormat *format);
void sfile_loader_add_text (SFileLoader *loader,
const char *text,
int len);
SFile * sfile_loader_finish (SFileLoader *loader,
GError **err);
void sfile_loader_free (SFileLoader *loader);
#endif
/* - Writing - */
/* FIXME - not10: see if we can't get rid of the names. It
* should be possible to pass NULL to state_transition_check()
* and have it interprete that as "whatever". We would need
* a way to get the name back then, though.
*/
SFileOutput * sfile_output_new (SFormat *format);
void sfile_begin_add_record (SFileOutput *file,
const char *name);
void sfile_begin_add_list (SFileOutput *file,
const char *name);
void sfile_end_add (SFileOutput *file,
const char *name,
gpointer object);
void sfile_add_string (SFileOutput *file,
const char *name,
const char *string);
void sfile_add_integer (SFileOutput *file,
const char *name,
int integer);
void sfile_add_pointer (SFileOutput *file,
const char *name,
gpointer pointer);
gboolean sfile_output_save (SFileOutput *sfile,
const char *filename,
GError **err);
void sfile_output_free (SFileOutput *sfile);

View File

@ -1,646 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <glib.h>
#include <string.h>
#include "sformat.h"
typedef struct State State;
typedef struct Transition Transition;
typedef struct Fragment Fragment;
/*
* Format
*/
struct SFormat
{
State * begin;
State * end;
GQueue * types;
GQueue * transitions;
GQueue * states;
};
/*
* Type
*/
typedef enum
{
TYPE_POINTER,
TYPE_STRING,
TYPE_INTEGER,
TYPE_RECORD,
TYPE_LIST,
TYPE_FORWARD
} TypeKind;
struct SType
{
TypeKind kind;
char *name;
Transition *enter, *exit;
SType *target; /* If kind is TYPE_POINTER */
};
static void type_free (SType *type);
/*
* Transition
*/
typedef enum
{
BEGIN,
VALUE,
END
} TransitionKind;
struct Transition
{
SType * type;
TransitionKind kind;
State * to;
};
static Transition *transition_new (SFormat *format,
TransitionKind kind,
SType *type,
State *from,
State *to);
static void transition_free (Transition *transition);
static void transition_set_to_state (Transition *transition,
State *to_state);
/*
* State
*/
struct State
{
GQueue *transitions;
};
static State *state_new (SFormat *format);
static void state_add_transition (State *state,
Transition *transition);
static void state_free (State *state);
/*
* Format
*/
SFormat *
sformat_new (void)
{
/* FIXME: should probably be refcounted, and an SContext
* should have a ref on the format
*/
SFormat *format = g_new0 (SFormat, 1);
format->begin = NULL;
format->end = NULL;
format->types = g_queue_new ();
format->states = g_queue_new ();
format->transitions = g_queue_new ();
return format;
}
void
sformat_free (SFormat *format)
{
GList *list;
for (list = format->types->head; list; list = list->next)
{
SType *type = list->data;
type_free (type);
}
g_queue_free (format->types);
for (list = format->states->head; list; list = list->next)
{
State *state = list->data;
state_free (state);
}
g_queue_free (format->states);
for (list = format->transitions->head; list; list = list->next)
{
Transition *transition = list->data;
transition_free (transition);
}
g_queue_free (format->transitions);
g_free (format);
}
void
sformat_set_type (SFormat *format,
SType *type)
{
format->begin = state_new (format);
format->end = state_new (format);
state_add_transition (format->begin, type->enter);
transition_set_to_state (type->exit, format->end);
}
/*
* Type
*/
static SType *
type_new (SFormat *format,
TypeKind kind,
const char *name)
{
SType *type = g_new0 (SType, 1);
type->kind = kind;
type->name = name? g_strdup (name) : NULL;
type->enter = NULL;
type->exit = NULL;
type->target = NULL;
g_queue_push_tail (format->types, type);
return type;
}
static SType *
type_new_from_forward (SFormat *format,
TypeKind kind,
const char *name,
SForward *forward)
{
SType *type;
if (forward)
{
type = (SType *)forward;
type->kind = kind;
type->name = g_strdup (name);
}
else
{
type = type_new (format, kind, name);
}
return type;
}
static SType *
type_new_value (SFormat *format, TypeKind kind, const char *name)
{
SType *type = type_new (format, kind, name);
State *before, *after;
Transition *value;
before = state_new (format);
after = state_new (format);
type->enter = transition_new (format, BEGIN, type, NULL, before);
type->exit = transition_new (format, END, type, after, NULL);
value = transition_new (format, VALUE, type, before, after);
return type;
}
static void
type_free (SType *type)
{
g_free (type->name);
g_free (type);
}
SForward *
sformat_declare_forward (SFormat *format)
{
SType *type = type_new (format, TYPE_FORWARD, NULL);
return (SForward *)type;
}
static GQueue *
expand_varargs (SType *content1,
va_list args)
{
GQueue *types = g_queue_new ();
SType *type;
g_queue_push_tail (types, content1);
type = va_arg (args, SType *);
while (type)
{
g_queue_push_tail (types, type);
type = va_arg (args, SType *);
}
return types;
}
SType *
sformat_make_record (SFormat *format,
const char *name,
SForward *forward,
SType *content,
...)
{
SType *type;
va_list args;
GQueue *types;
GList *list;
State *begin, *state;
/* Build queue of child types */
va_start (args, content);
types = expand_varargs (content, args);
va_end (args);
/* chain types together */
state = begin = state_new (format);
for (list = types->head; list != NULL; list = list->next)
{
SType *child_type = list->data;
state_add_transition (state, child_type->enter);
state = state_new (format);
transition_set_to_state (child_type->exit, state);
}
g_queue_free (types);
/* create and return the new type */
type = type_new_from_forward (format, TYPE_RECORD, name, forward);
type->enter = transition_new (format, BEGIN, type, NULL, begin);
type->exit = transition_new (format, END, type, state, NULL);
return type;
}
SType *
sformat_make_list (SFormat *format,
const char *name,
SForward *forward,
SType *child_type)
{
SType *type;
State *list_state;
type = type_new_from_forward (format, TYPE_LIST, name, forward);
list_state = state_new (format);
type->enter = transition_new (format, BEGIN, type, NULL, list_state);
type->exit = transition_new (format, END, type, list_state, NULL);
state_add_transition (list_state, child_type->enter);
transition_set_to_state (child_type->exit, list_state);
return type;
}
SType *
sformat_make_pointer (SFormat *format,
const char *name,
SForward *forward)
{
SType *type = type_new_value (format, TYPE_POINTER, name);
type->target = (SType *)forward;
return type;
}
SType *
sformat_make_integer (SFormat *format,
const char *name)
{
return type_new_value (format, TYPE_INTEGER, name);
}
SType *
sformat_make_string (SFormat *format,
const char *name)
{
return type_new_value (format, TYPE_STRING, name);
}
gboolean
stype_is_record (SType *type)
{
return type->kind == TYPE_RECORD;
}
gboolean
stype_is_list (SType *type)
{
return type->kind == TYPE_LIST;
}
gboolean
stype_is_pointer (SType *type)
{
return type->kind == TYPE_POINTER;
}
gboolean
stype_is_integer (SType *type)
{
return type->kind == TYPE_INTEGER;
}
gboolean
stype_is_string (SType *type)
{
return type->kind == TYPE_STRING;
}
SType *
stype_get_target_type (SType *type)
{
g_return_val_if_fail (stype_is_pointer (type), NULL);
return type->target;
}
const char *
stype_get_name (SType *type)
{
return type->name;
}
/* Consider adding unions at some point
*
* To be useful they should probably be anonymous, so that
* the union itself doesn't have a representation in the
* xml file.
*
* API:
* sformat_new_union (gpointer content1, ...);
*
* char *content = begin_get_union ();
* if (strcmp (content, ...) == 0)
* get_pointer ();
* else if (strcmp (content, ...) == 0)
*
* ;
*
* Annoying though, that we then won't have the nice one-to-one
* correspondence between begin()/end() calls and <element></element>s
* Actually, we will probably have to have <union>asdlfkj</union>
* elements. That will make things a lot easier, and unions are
* still pretty useful if you put big things like lists in them.
*
* Or maybe just give them a name ...
*
* We may also consider adding anonymous records. These will
* not be able to have pointers associated with them though
* (because there wouldn't be a natural place
*
*
* Also consider adding the following data types:
*
* - Binary blobs of data, stored as base64 perhaps
*
* - floating point values. How do we store those portably
* without losing precision? Gnumeric may know.
*
* - enums, stored as strings
*
* - booleans.
*/
/*
* State
*/
static State *
state_new (SFormat *format)
{
State *state = g_new0 (State, 1);
state->transitions = g_queue_new ();
g_queue_push_tail (format->states, state);
return state;
}
static void
state_add_transition (State *state,
Transition *transition)
{
g_queue_push_tail (state->transitions, transition);
}
static void
state_free (State *state)
{
g_queue_free (state->transitions);
g_free (state);
}
/*
* Transition
*/
static Transition *
transition_new (SFormat *format,
TransitionKind kind,
SType *type,
State *from,
State *to)
{
Transition *transition = g_new0 (Transition, 1);
transition->type = type;
transition->kind = kind;
transition->to = to;
if (from)
state_add_transition (from, transition);
g_queue_push_tail (format->transitions, transition);
return transition;
}
static void
transition_free (Transition *transition)
{
g_free (transition);
}
static void
transition_set_to_state (Transition *transition,
State *to_state)
{
transition->to = to_state;
}
/* Context */
struct SContext
{
SFormat *format;
State *state;
};
SContext *
scontext_new (SFormat *format)
{
SContext *context = g_new0 (SContext, 1);
context->format = format;
context->state = format->begin;
return context;
}
static SType *
do_transition (SContext *context,
TransitionKind kind,
const char *element)
{
GList *list;
for (list = context->state->transitions->head; list; list = list->next)
{
Transition *transition = list->data;
if (transition->kind == kind)
{
if (kind == VALUE || strcmp (transition->type->name, element) == 0)
{
context->state = transition->to;
return transition->type;
}
}
}
return NULL;
}
SType *
scontext_begin (SContext *context,
const char *element)
{
return do_transition (context, BEGIN, element);
}
SType *
scontext_text (SContext *context)
{
return do_transition (context, VALUE, NULL);
}
SType *
scontext_end (SContext *context,
const char *element)
{
return do_transition (context, END, element);
}
gboolean
scontext_is_finished (SContext *context)
{
return context->state == context->format->end;
}
void
scontext_free (SContext *context)
{
g_free (context);
}
/* assorted stuff */
#if 0
static const State *
state_transition_check (const State *state,
const char *element,
TransitionKind kind,
SType *type)
{
GList *list;
for (list = state->transitions->head; list; list = list->next)
{
Transition *transition = list->data;
if (transition->kind == kind &&
strcmp (element, transition->element) == 0)
{
*type = transition->type;
return transition->to;
}
}
return NULL;
}
static const State *
state_transition_begin (const State *state, const char *element, SType *type)
{
return state_transition_check (state, element, BEGIN, type);
}
static const State *
state_transition_end (const State *state, const char *element, SType *type)
{
return state_transition_check (state, element, END, type);
}
static const State *
state_transition_text (const State *state, SType *type, SType *target_type)
{
GList *list;
for (list = state->transitions->head; list; list = list->next)
{
Transition *transition = list->data;
if (transition->kind == VALUE)
{
*type = transition->type;
if (*type == TYPE_POINTER && target_type)
*target_type = transition->target_type;
/* There will never be more than one allowed value transition for
* a given state
*/
return transition->to;
}
}
return NULL;
}
#endif

View File

@ -1,67 +0,0 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#ifndef SFORMAT_H
#define SFORMAT_H
typedef struct SFormat SFormat;
typedef struct SType SType;
typedef struct SForward SForward;
typedef struct SContext SContext;
SFormat *sformat_new (void);
void sformat_free (SFormat *format);
void sformat_set_type (SFormat *format,
SType *type);
SForward *sformat_declare_forward (SFormat *format);
SType *sformat_make_record (SFormat *format,
const char *name,
SForward *forward,
SType *content,
...);
SType *sformat_make_list (SFormat *format,
const char *name,
SForward *forward,
SType *type);
SType *sformat_make_pointer (SFormat *format,
const char *name,
SForward *type);
SType *sformat_make_integer (SFormat *format,
const char *name);
SType *sformat_make_string (SFormat *format,
const char *name);
gboolean stype_is_record (SType *type);
gboolean stype_is_list (SType *type);
gboolean stype_is_pointer (SType *type);
gboolean stype_is_integer (SType *type);
gboolean stype_is_string (SType *type);
SType *stype_get_target_type (SType *type);
const char *stype_get_name (SType *type);
SContext *scontext_new (SFormat *format);
SType *scontext_begin (SContext *context,
const char *element);
SType *scontext_text (SContext *context);
SType *scontext_end (SContext *context,
const char *element);
gboolean scontext_is_finished (SContext *context);
void scontext_free (SContext *context);
#endif

View File

@ -1,215 +0,0 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright (C) 2005 Søren Sandmann (sandmann@daimi.au.dk)
*
* This library 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; either version 2 of the License, or
* (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include "watch.h"
#include "signal-handler.h"
typedef struct SignalWatch SignalWatch;
struct SignalWatch
{
int signo;
SignalFunc handler;
gpointer user_data;
struct sigaction old_action;
SignalWatch * next;
};
static int read_end = -1;
static int write_end = -1;
static SignalWatch *signal_watches = NULL;
static SignalWatch *
lookup_signal_watch (int signo)
{
SignalWatch *w;
for (w = signal_watches; w != NULL; w = w->next)
{
if (w->signo == signo)
return w;
}
return NULL;
}
static void
remove_signal_watch (SignalWatch *watch)
{
SignalWatch *prev, *w;
g_return_if_fail (watch != NULL);
prev = NULL;
for (w = signal_watches; w != NULL; w = w->next)
{
if (w == watch)
{
if (prev)
prev->next = w->next;
else
signal_watches = w->next;
break;
}
prev = w;
}
}
static void
signal_handler (int signo,
siginfo_t *info,
void *data)
{
/* FIXME: I suppose we should handle short
* and non-successful writes ...
*
* And also, there is a deadlock if so many signals arrive that
* write() blocks. Then we will be stuck right here, and the
* main loop will never run. Kinda hard to fix without dropping
* signals ...
*
*/
write (write_end, &signo, sizeof (int));
}
static void
on_read (gpointer data)
{
SignalWatch *watch;
int signo;
/* FIXME: handle short read I suppose */
read (read_end, &signo, sizeof (int));
watch = lookup_signal_watch (signo);
if (watch)
watch->handler (signo, watch->user_data);
}
static gboolean
create_pipe (int *read_end,
int *write_end,
GError **err)
{
int p[2];
if (pipe (p) < 0)
{
/* FIXME - create an error */
return FALSE;
}
/* FIXME: We should probably make the fd's non-blocking */
if (read_end)
*read_end = p[0];
if (write_end)
*write_end = p[1];
return TRUE;
}
static gboolean
install_signal_handler (int signo,
struct sigaction *old_action,
GError **err)
{
struct sigaction action;
memset (&action, 0, sizeof (action));
action.sa_sigaction = signal_handler;
sigemptyset (&action.sa_mask);
action.sa_flags = SA_SIGINFO;
if (sigaction (signo, &action, old_action) < 0)
{
/* FIXME - create an error */
return TRUE;
}
return TRUE;
}
static void
signal_watch_free (SignalWatch *watch)
{
remove_signal_watch (watch);
g_free (watch);
}
gboolean
signal_set_handler (int signo,
SignalFunc handler,
gpointer data,
GError **err)
{
SignalWatch *watch;
g_return_val_if_fail (lookup_signal_watch (signo) == NULL, FALSE);
if (read_end == -1)
{
if (!create_pipe (&read_end, &write_end, err))
return FALSE;
fd_add_watch (read_end, NULL);
fd_set_read_callback (read_end, on_read);
}
watch = g_new0 (SignalWatch, 1);
watch->signo = signo;
watch->handler = handler;
watch->user_data = data;
watch->next = signal_watches;
signal_watches = watch;
if (!install_signal_handler (signo, &watch->old_action, err))
{
signal_watch_free (watch);
return FALSE;
}
return TRUE;
}
void
signal_unset_handler (int signo)
{
SignalWatch *watch;
watch = lookup_signal_watch (signo);
g_return_if_fail (watch != NULL);
sigaction (signo, &watch->old_action, NULL);
signal_watch_free (watch);
}

View File

@ -1,27 +0,0 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright (C) 2005 Søren Sandmann (sandmann@daimi.au.dk)
*
* This library 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; either version 2 of the License, or
* (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
typedef void (* SignalFunc) (int signo, gpointer data);
gboolean signal_set_handler (int signo,
SignalFunc handler,
gpointer data,
GError **err);
void signal_unset_handler (int signo);

91
lib/sp-address.c Normal file
View File

@ -0,0 +1,91 @@
/* sp-address.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <linux/perf_event.h>
#include "sp-address.h"
gboolean
sp_address_is_context_switch (SpAddress address,
SpAddressContext *context)
{
SpAddressContext dummy;
if (context == NULL)
context = &dummy;
switch (address)
{
case PERF_CONTEXT_HV:
*context = SP_ADDRESS_CONTEXT_HYPERVISOR;
return TRUE;
case PERF_CONTEXT_KERNEL:
*context = SP_ADDRESS_CONTEXT_KERNEL;
return TRUE;
case PERF_CONTEXT_USER:
*context = SP_ADDRESS_CONTEXT_USER;
return TRUE;
case PERF_CONTEXT_GUEST:
*context = SP_ADDRESS_CONTEXT_GUEST;
return TRUE;
case PERF_CONTEXT_GUEST_KERNEL:
*context = SP_ADDRESS_CONTEXT_GUEST_KERNEL;
return TRUE;
case PERF_CONTEXT_GUEST_USER:
*context = SP_ADDRESS_CONTEXT_GUEST_USER;
return TRUE;
default:
*context = SP_ADDRESS_CONTEXT_NONE;
return FALSE;
}
}
const gchar *
sp_address_context_to_string (SpAddressContext context)
{
switch (context)
{
case SP_ADDRESS_CONTEXT_HYPERVISOR:
return "- - hypervisor - -";
case SP_ADDRESS_CONTEXT_KERNEL:
return "- - kernel - -";
case SP_ADDRESS_CONTEXT_USER:
return "- - user - -";
case SP_ADDRESS_CONTEXT_GUEST:
return "- - guest - -";
case SP_ADDRESS_CONTEXT_GUEST_KERNEL:
return "- - guest kernel - -";
case SP_ADDRESS_CONTEXT_GUEST_USER:
return "- - guest user - -";
case SP_ADDRESS_CONTEXT_NONE:
default:
return "- - unknown - -";
}
}

59
lib/sp-address.h Normal file
View File

@ -0,0 +1,59 @@
/* sp-address.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_ADDRESS_H
#define SP_ADDRESS_H
#include <glib.h>
G_BEGIN_DECLS
typedef guint64 SpAddress;
G_STATIC_ASSERT (sizeof (SpAddress) >= sizeof (gpointer));
typedef enum
{
SP_ADDRESS_CONTEXT_NONE = 0,
SP_ADDRESS_CONTEXT_HYPERVISOR,
SP_ADDRESS_CONTEXT_KERNEL,
SP_ADDRESS_CONTEXT_USER,
SP_ADDRESS_CONTEXT_GUEST,
SP_ADDRESS_CONTEXT_GUEST_KERNEL,
SP_ADDRESS_CONTEXT_GUEST_USER,
} SpAddressContext;
gboolean sp_address_is_context_switch (SpAddress address,
SpAddressContext *context);
const gchar *sp_address_context_to_string (SpAddressContext context);
static inline gint
sp_address_compare (SpAddress a,
SpAddress b)
{
if (a < b)
return -1;
else if (a == b)
return 0;
else
return 1;
}
G_END_DECLS
#endif /* SP_ADDRESS_H */

View File

@ -0,0 +1,31 @@
/* sp-callgraph-profile-private.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CALLGRAPH_PROFILE_PRIVATE_H
#define SP_CALLGRAPH_PROFILE_PRIVATE_H
#include "sp-callgraph-profile.h"
#include "stackstash.h"
G_BEGIN_DECLS
StackStash *sp_callgraph_profile_get_stash (SpCallgraphProfile *self);
G_END_DECLS
#endif /* SP_CALLGRAPH_PROFILE_PRIVATE_H */

376
lib/sp-callgraph-profile.c Normal file
View File

@ -0,0 +1,376 @@
/* sp-callgraph-profile.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2009-2012 Soeren Sandmann and others
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <glib/gi18n.h>
#include <string.h>
#include <unistd.h>
#include "sp-address.h"
#include "sp-callgraph-profile.h"
#include "sp-callgraph-profile-private.h"
#include "sp-capture-reader.h"
#include "sp-elf-symbol-resolver.h"
#include "sp-jitmap-symbol-resolver.h"
#include "sp-map-lookaside.h"
#include "sp-kernel-symbol-resolver.h"
#include "stackstash.h"
struct _SpCallgraphProfile
{
GObject parent_instance;
SpCaptureReader *reader;
GStringChunk *symbols;
StackStash *stash;
GHashTable *tags;
};
static void profile_iface_init (SpProfileInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpCallgraphProfile, sp_callgraph_profile, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILE, profile_iface_init))
SpProfile *
sp_callgraph_profile_new (void)
{
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE, NULL);
}
static void
sp_callgraph_profile_finalize (GObject *object)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)object;
g_clear_pointer (&self->symbols, g_string_chunk_free);
g_clear_pointer (&self->stash, stack_stash_unref);
g_clear_pointer (&self->reader, sp_capture_reader_unref);
g_clear_pointer (&self->tags, g_hash_table_unref);
G_OBJECT_CLASS (sp_callgraph_profile_parent_class)->finalize (object);
}
static void
sp_callgraph_profile_class_init (SpCallgraphProfileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_callgraph_profile_finalize;
}
static void
sp_callgraph_profile_init (SpCallgraphProfile *self)
{
self->symbols = g_string_chunk_new (getpagesize ());
self->tags = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
sp_callgraph_profile_set_reader (SpProfile *profile,
SpCaptureReader *reader)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (reader != NULL);
g_clear_pointer (&self->reader, sp_capture_reader_unref);
self->reader = sp_capture_reader_ref (reader);
}
static const gchar *
sp_callgraph_profile_intern_string_take (SpCallgraphProfile *self,
gchar *str)
{
const gchar *ret;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (str != NULL);
ret = g_string_chunk_insert_const (self->symbols, str);
g_free (str);
return ret;
}
static const gchar *
sp_callgraph_profile_intern_string (SpCallgraphProfile *self,
const gchar *str)
{
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (str != NULL);
return g_string_chunk_insert_const (self->symbols, str);
}
static void
sp_callgraph_profile_generate_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
SpCallgraphProfile *self = source_object;
SpCaptureReader *reader = task_data;
g_autoptr(GArray) resolved = NULL;
g_autoptr(GHashTable) maps_by_pid = NULL;
g_autoptr(GHashTable) cmdlines = NULL;
g_autoptr(GPtrArray) resolvers = NULL;
SpCaptureFrameType type;
StackStash *stash;
StackStash *resolved_stash;
gboolean ret = FALSE;
guint j;
g_assert (G_IS_TASK (task));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
maps_by_pid = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sp_map_lookaside_free);
cmdlines = g_hash_table_new (NULL, NULL);
stash = stack_stash_new (NULL);
resolved_stash = stack_stash_new (NULL);
resolvers = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (resolvers, sp_kernel_symbol_resolver_new ());
g_ptr_array_add (resolvers, sp_elf_symbol_resolver_new ());
g_ptr_array_add (resolvers, sp_jitmap_symbol_resolver_new ());
for (j = 0; j < resolvers->len; j++)
{
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
sp_capture_reader_reset (reader);
sp_symbol_resolver_load (resolver, reader);
}
sp_capture_reader_reset (reader);
/*
* The resolved pointer array is where we stash the names for the
* instruction pointers to pass to the stash stack. All the strings
* need to be deduplicated so that pointer comparison works as if we
* did instruction-pointer comparison.
*/
resolved = g_array_new (FALSE, TRUE, sizeof (guint64));
while (sp_capture_reader_peek_type (reader, &type))
{
const SpCaptureProcess *pr;
const gchar *cmdline;
if (type != SP_CAPTURE_FRAME_PROCESS)
{
if (!sp_capture_reader_skip (reader))
goto failure;
continue;
}
if (NULL == (pr = sp_capture_reader_read_process (reader)))
goto failure;
cmdline = g_strdup_printf ("[%s]", pr->cmdline);
g_hash_table_insert (cmdlines,
GINT_TO_POINTER (pr->frame.pid),
(gchar *)sp_callgraph_profile_intern_string (self, cmdline));
}
sp_capture_reader_reset (reader);
/*
* Walk through all of the sample events and resolve instruction-pointers
* to symbol names by loading the particular map and extracting the symbol
* name. If we wanted to support dynamic systems, we'd want to extend this
* to parse information from captured data about the languages jit'd code.
*/
while (sp_capture_reader_peek_type (reader, &type))
{
SpAddressContext last_context = SP_ADDRESS_CONTEXT_NONE;
const SpCaptureSample *sample;
StackNode *node;
StackNode *iter;
const gchar *cmdline;
guint len = 5;
if (type != SP_CAPTURE_FRAME_SAMPLE)
{
if (!sp_capture_reader_skip (reader))
goto failure;
continue;
}
if (NULL == (sample = sp_capture_reader_read_sample (reader)))
goto failure;
cmdline = g_hash_table_lookup (cmdlines, GINT_TO_POINTER (sample->frame.pid));
node = stack_stash_add_trace (stash, sample->addrs, sample->n_addrs, 1);
for (iter = node; iter != NULL; iter = iter->parent)
len++;
if (G_UNLIKELY (resolved->len < len))
g_array_set_size (resolved, len);
len = 0;
for (iter = node; iter != NULL; iter = iter->parent)
{
SpAddressContext context = SP_ADDRESS_CONTEXT_NONE;
SpAddress address = iter->data;
const gchar *symbol = NULL;
if (sp_address_is_context_switch (address, &context))
{
if (last_context)
symbol = sp_address_context_to_string (last_context);
else
symbol = NULL;
last_context = context;
}
else
{
for (j = 0; j < resolvers->len; j++)
{
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
GQuark tag = 0;
gchar *str;
str = sp_symbol_resolver_resolve (resolver,
sample->frame.time,
sample->frame.pid,
address,
&tag);
if (str != NULL)
{
symbol = sp_callgraph_profile_intern_string_take (self, str);
if (tag != 0)
g_hash_table_insert (self->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag));
break;
}
}
}
if (symbol != NULL)
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (symbol);
}
if (last_context && last_context != SP_ADDRESS_CONTEXT_USER)
{
/* Kernel threads do not have a user part, so we end up here
* without ever getting a user context. If this happens,
* add the '- - kernel - - ' name, so that kernel threads
* are properly blamed on the kernel
*/
const gchar *name = sp_address_context_to_string (last_context);
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (name);
}
if (cmdline != NULL)
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 (cmdline);
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]");
stack_stash_add_trace (resolved_stash, (SpAddress *)resolved->data, len, 1);
}
ret = TRUE;
failure:
if (ret == FALSE)
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Sysprof was unable to generate a callgraph from the system capture."));
self->stash = resolved_stash;
stack_stash_unref (stash);
g_task_return_boolean (task, ret);
}
static void
sp_callgraph_profile_generate (SpProfile *profile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
g_autoptr(GTask) task = NULL;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, self->reader, NULL);
g_task_run_in_thread (task, sp_callgraph_profile_generate_worker);
}
static gboolean
sp_callgraph_profile_generate_finish (SpProfile *profile,
GAsyncResult *result,
GError **error)
{
g_assert (SP_IS_CALLGRAPH_PROFILE (profile));
g_assert (G_IS_TASK (result));
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
profile_iface_init (SpProfileInterface *iface)
{
iface->generate = sp_callgraph_profile_generate;
iface->generate_finish = sp_callgraph_profile_generate_finish;
iface->set_reader = sp_callgraph_profile_set_reader;
}
StackStash *
sp_callgraph_profile_get_stash (SpCallgraphProfile *self)
{
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), NULL);
return self->stash;
}
GQuark
sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
const gchar *symbol)
{
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), 0);
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tags, symbol));
}

View File

@ -0,0 +1,36 @@
/* sp-callgraph-profile.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CALLGRAPH_PROFILE_H
#define SP_CALLGRAPH_PROFILE_H
#include "sp-profile.h"
G_BEGIN_DECLS
#define SP_TYPE_CALLGRAPH_PROFILE (sp_callgraph_profile_get_type())
G_DECLARE_FINAL_TYPE (SpCallgraphProfile, sp_callgraph_profile, SP, CALLGRAPH_PROFILE, GObject)
SpProfile *sp_callgraph_profile_new (void);
GQuark sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
const gchar *symbol);
G_END_DECLS
#endif /* SP_CALLGRAPH_PROFILE_H */

881
lib/sp-callgraph-view.c Normal file
View File

@ -0,0 +1,881 @@
/* sp-callgraph-view.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, 2006, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <glib/gi18n.h>
#include "sp-callgraph-profile-private.h"
#include "sp-callgraph-view.h"
#include "util.h"
typedef struct
{
SpCallgraphProfile *profile;
GtkTreeView *callers_view;
GtkTreeView *functions_view;
GtkTreeView *descendants_view;
GtkTreeViewColumn *descendants_name_column;
guint profile_size;
} SpCallgraphViewPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SpCallgraphView, sp_callgraph_view, GTK_TYPE_BIN)
enum {
PROP_0,
PROP_PROFILE,
N_PROPS
};
enum {
COLUMN_NAME,
COLUMN_SELF,
COLUMN_TOTAL,
COLUMN_POINTER,
};
static void sp_callgraph_view_update_descendants (SpCallgraphView *self,
StackNode *node);
static GParamSpec *properties [N_PROPS];
static guint
sp_callgraph_view_get_profile_size (SpCallgraphView *self)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
StackStash *stash;
StackNode *node;
guint size = 0;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
if (priv->profile_size != 0)
return priv->profile_size;
if (priv->profile == NULL)
return 0;
if (NULL == (stash = sp_callgraph_profile_get_stash (priv->profile)))
return 0;
for (node = stack_stash_get_root (stash); node != NULL; node = node->siblings)
size += node->total;
priv->profile_size = size;
return size;
}
static void
build_functions_store (StackNode *node,
gpointer user_data)
{
struct {
GtkListStore *store;
gdouble profile_size;
} *state = user_data;
GtkTreeIter iter;
const StackNode *n;
guint size = 0;
guint total = 0;
g_assert (state != NULL);
g_assert (GTK_IS_LIST_STORE (state->store));
for (n = node; n != NULL; n = n->next)
{
size += n->size;
if (n->toplevel)
total += n->total;
}
gtk_list_store_append (state->store, &iter);
gtk_list_store_set (state->store, &iter,
COLUMN_NAME, (const gchar *)node->data,
COLUMN_SELF, 100.0 * size / state->profile_size,
COLUMN_TOTAL, 100.0 * total / state->profile_size,
COLUMN_POINTER, node,
-1);
}
static void
sp_callgraph_view_load (SpCallgraphView *self,
SpCallgraphProfile *profile)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkListStore *functions;
StackStash *stash;
StackNode *n;
GtkTreeIter iter;
struct {
GtkListStore *store;
gdouble profile_size;
} state = { 0 };
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (SP_IS_CALLGRAPH_PROFILE (profile));
/*
* TODO: This is probably the type of thing we want to do off the main
* thread. We should be able to build the tree models off thread
* and then simply apply them on the main thread.
*
* In the mean time, we should set the state of the widget to
* insensitive and give some indication of loading progress.
*/
g_set_object (&priv->profile, profile);
if (NULL == (stash = sp_callgraph_profile_get_stash (profile)))
return;
for (n = stack_stash_get_root (stash); n; n = n->siblings)
state.profile_size += n->total;
functions = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_POINTER);
state.store = functions;
stack_stash_foreach_by_address (stash, build_functions_store, &state);
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (functions),
COLUMN_TOTAL,
GTK_SORT_DESCENDING);
gtk_tree_view_set_model (priv->functions_view, GTK_TREE_MODEL (functions));
gtk_tree_view_set_model (priv->callers_view, NULL);
gtk_tree_view_set_model (priv->descendants_view, NULL);
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (functions), &iter))
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (priv->functions_view);
gtk_tree_selection_select_iter (selection, &iter);
}
g_clear_object (&functions);
}
static void
sp_callgraph_view_unload (SpCallgraphView *self)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (SP_IS_CALLGRAPH_PROFILE (priv->profile));
g_clear_object (&priv->profile);
priv->profile_size = 0;
}
void
sp_callgraph_view_set_profile (SpCallgraphView *self,
SpCallgraphProfile *profile)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
g_return_if_fail (SP_IS_CALLGRAPH_VIEW (self));
g_return_if_fail (!profile || SP_IS_CALLGRAPH_PROFILE (profile));
if (profile != priv->profile)
{
if (priv->profile)
sp_callgraph_view_unload (self);
if (profile)
sp_callgraph_view_load (self, profile);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILE]);
}
}
static void
sp_callgraph_view_expand_descendants (SpCallgraphView *self)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkTreeModel *model;
GList *all_paths = NULL;
GtkTreePath *first_path;
GtkTreeIter iter;
gdouble top_value = 0;
gint max_rows = 40; /* FIXME */
gint n_rows;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
model = gtk_tree_view_get_model (priv->descendants_view);
first_path = gtk_tree_path_new_first ();
all_paths = g_list_prepend (all_paths, first_path);
n_rows = 1;
gtk_tree_model_get_iter (model, &iter, first_path);
gtk_tree_model_get (model, &iter,
COLUMN_TOTAL, &top_value,
-1);
while ((all_paths != NULL) && (n_rows < max_rows))
{
GtkTreeIter best_iter;
GtkTreePath *best_path = NULL;
GList *list;
gdouble best_value = 0.0;
gint n_children;
gint i;
for (list = all_paths; list != NULL; list = list->next)
{
GtkTreePath *path = list->data;
g_assert (path != NULL);
if (gtk_tree_model_get_iter (model, &iter, path))
{
gdouble value;
gtk_tree_model_get (model, &iter,
COLUMN_TOTAL, &value,
-1);
if (value >= best_value)
{
best_value = value;
best_path = path;
best_iter = iter;
}
}
}
n_children = gtk_tree_model_iter_n_children (model, &best_iter);
if ((n_children > 0) &&
((best_value / top_value) > 0.04) &&
((n_children + gtk_tree_path_get_depth (best_path)) / (gdouble)max_rows) < (best_value / top_value))
{
gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE);
n_rows += n_children;
if (gtk_tree_path_get_depth (best_path) < 4)
{
GtkTreePath *path;
path = gtk_tree_path_copy (best_path);
gtk_tree_path_down (path);
for (i = 0; i < n_children; i++)
{
all_paths = g_list_prepend (all_paths, path);
path = gtk_tree_path_copy (path);
gtk_tree_path_next (path);
}
gtk_tree_path_free (path);
}
}
all_paths = g_list_remove (all_paths, best_path);
/* Always expand at least once */
if ((all_paths == NULL) && (n_rows == 1))
gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE);
gtk_tree_path_free (best_path);
}
g_list_free_full (all_paths, (GDestroyNotify)gtk_tree_path_free);
}
typedef struct
{
StackNode *node;
const gchar *name;
guint self;
guint total;
} Caller;
static Caller *
caller_new (StackNode *node)
{
Caller *c;
c = g_slice_new (Caller);
c->name = U64_TO_POINTER (node->data);
c->self = 0;
c->total = 0;
c->node = node;
return c;
}
static void
caller_free (gpointer data)
{
Caller *c = data;
g_slice_free (Caller, c);
}
static void
sp_callgraph_view_function_selection_changed (SpCallgraphView *self,
GtkTreeSelection *selection)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkTreeModel *model = NULL;
GtkTreeIter iter;
GtkListStore *callers_store;
g_autoptr(GHashTable) callers = NULL;
g_autoptr(GHashTable) processed = NULL;
StackNode *callees = NULL;
StackNode *node;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (GTK_IS_TREE_SELECTION (selection));
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
{
gtk_tree_view_set_model (priv->callers_view, NULL);
gtk_tree_view_set_model (priv->descendants_view, NULL);
return;
}
gtk_tree_model_get (model, &iter,
COLUMN_POINTER, &callees,
-1);
sp_callgraph_view_update_descendants (self, callees);
callers_store = gtk_list_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
callers = g_hash_table_new_full (NULL, NULL, NULL, caller_free);
processed = g_hash_table_new (NULL, NULL);
for (node = callees; node != NULL; node = node->next)
{
Caller *c;
if (!node->parent)
continue;
c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data));
if (c == NULL)
{
c = caller_new (node->parent);
g_hash_table_insert (callers, (gpointer)c->name, c);
}
}
for (node = callees; node != NULL; node = node->next)
{
StackNode *top_caller = node->parent;
StackNode *top_callee = node;
StackNode *n;
Caller *c;
if (!node->parent)
continue;
/*
* We could have a situation where the function was called in a
* reentrant fashion, so we want to take the top-most match in the
* stack.
*/
for (n = node; n && n->parent; n = n->parent)
{
if (n->data == node->data && n->parent->data == node->parent->data)
{
top_caller = n->parent;
top_callee = n;
}
}
c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data));
g_assert (c != NULL);
if (!g_hash_table_lookup (processed, top_caller))
{
c->total += top_callee->total;
g_hash_table_insert (processed, top_caller, top_caller);
}
c->self += node->size;
}
{
GHashTableIter hiter;
gpointer key, value;
guint size = 0;
size = sp_callgraph_view_get_profile_size (self);
g_hash_table_iter_init (&hiter, callers);
while (g_hash_table_iter_next (&hiter, &key, &value))
{
Caller *c = value;
gtk_list_store_append (callers_store, &iter);
gtk_list_store_set (callers_store, &iter,
COLUMN_NAME, c->name,
COLUMN_SELF, c->self * 100.0 / size,
COLUMN_TOTAL, c->total * 100.0 / size,
COLUMN_POINTER, c->node,
-1);
}
}
gtk_tree_view_set_model (priv->callers_view, GTK_TREE_MODEL (callers_store));
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (callers_store),
COLUMN_TOTAL,
GTK_SORT_DESCENDING);
g_clear_object (&callers_store);
}
static void
sp_callgraph_view_set_node (SpCallgraphView *self,
StackNode *node)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkTreeModel *model;
GtkTreeIter iter;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (node != NULL);
model = gtk_tree_view_get_model (priv->functions_view);
if (gtk_tree_model_get_iter_first (model, &iter))
{
do
{
StackNode *item = NULL;
gtk_tree_model_get (model, &iter,
COLUMN_POINTER, &item,
-1);
if (item == node)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (priv->functions_view);
gtk_tree_selection_select_iter (selection, &iter);
break;
}
}
while (gtk_tree_model_iter_next (model, &iter));
}
}
static void
sp_callgraph_view_descendant_activated (SpCallgraphView *self,
GtkTreePath *path,
GtkTreeViewColumn *column,
GtkTreeView *tree_view)
{
GtkTreeModel *model;
StackNode *node = NULL;
GtkTreeIter iter;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (GTK_IS_TREE_VIEW (tree_view));
g_assert (path != NULL);
g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
model = gtk_tree_view_get_model (tree_view);
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (model, &iter,
COLUMN_POINTER, &node,
-1);
if (node != NULL)
sp_callgraph_view_set_node (self, node);
}
static void
sp_callgraph_view_caller_activated (SpCallgraphView *self,
GtkTreePath *path,
GtkTreeViewColumn *column,
GtkTreeView *tree_view)
{
GtkTreeModel *model;
StackNode *node = NULL;
GtkTreeIter iter;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
g_assert (GTK_IS_TREE_VIEW (tree_view));
g_assert (path != NULL);
g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
model = gtk_tree_view_get_model (tree_view);
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (model, &iter,
COLUMN_POINTER, &node,
-1);
if (node != NULL)
sp_callgraph_view_set_node (self, node);
}
static void
sp_callgraph_view_tag_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
SpCallgraphView *self = data;
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
StackNode *node = NULL;
const gchar *str = NULL;
gtk_tree_model_get (model, iter, COLUMN_POINTER, &node, -1);
if (node && node->data)
{
GQuark tag;
tag = sp_callgraph_profile_get_tag (priv->profile, GSIZE_TO_POINTER (node->data));
if (tag != 0)
str = g_quark_to_string (tag);
}
g_object_set (cell, "text", str, NULL);
}
static void
sp_callgraph_view_finalize (GObject *object)
{
SpCallgraphView *self = (SpCallgraphView *)object;
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
g_clear_object (&priv->profile);
G_OBJECT_CLASS (sp_callgraph_view_parent_class)->finalize (object);
}
static void
sp_callgraph_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpCallgraphView *self = SP_CALLGRAPH_VIEW (object);
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
switch (prop_id)
{
case PROP_PROFILE:
g_value_set_object (value, priv->profile);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_callgraph_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpCallgraphView *self = SP_CALLGRAPH_VIEW (object);
switch (prop_id)
{
case PROP_PROFILE:
sp_callgraph_view_set_profile (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_callgraph_view_class_init (SpCallgraphViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = sp_callgraph_view_finalize;
object_class->get_property = sp_callgraph_view_get_property;
object_class->set_property = sp_callgraph_view_set_property;
properties [PROP_PROFILE] =
g_param_spec_object ("profile",
"Profile",
"The callgraph profile to view",
SP_TYPE_CALLGRAPH_PROFILE,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/sysprof/ui/sp-callgraph-view.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpCallgraphView, callers_view);
gtk_widget_class_bind_template_child_private (widget_class, SpCallgraphView, functions_view);
gtk_widget_class_bind_template_child_private (widget_class, SpCallgraphView, descendants_view);
gtk_widget_class_bind_template_child_private (widget_class, SpCallgraphView, descendants_name_column);
}
static void
sp_callgraph_view_init (SpCallgraphView *self)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkTreeSelection *selection;
GtkCellRenderer *cell;
gtk_widget_init_template (GTK_WIDGET (self));
selection = gtk_tree_view_get_selection (priv->functions_view);
g_signal_connect_object (selection,
"changed",
G_CALLBACK (sp_callgraph_view_function_selection_changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->descendants_view,
"row-activated",
G_CALLBACK (sp_callgraph_view_descendant_activated),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->callers_view,
"row-activated",
G_CALLBACK (sp_callgraph_view_caller_activated),
self,
G_CONNECT_SWAPPED);
cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
"foreground", "#666666",
"scale", PANGO_SCALE_SMALL,
"xalign", 1.0f,
NULL);
gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, FALSE);
gtk_tree_view_column_set_cell_data_func (priv->descendants_name_column, cell,
sp_callgraph_view_tag_data_func,
self, NULL);
}
typedef struct _Descendant Descendant;
struct _Descendant
{
const gchar *name;
guint self;
guint cumulative;
Descendant *parent;
Descendant *siblings;
Descendant *children;
};
static void
build_tree_cb (StackLink *trace,
gint size,
gpointer user_data)
{
Descendant **tree = user_data;
Descendant *parent = NULL;
StackLink *link;
g_assert (trace != NULL);
g_assert (tree != NULL);
/* Get last item */
link = trace;
while (link->next)
link = link->next;
for (; link != NULL; link = link->prev)
{
const gchar *address = U64_TO_POINTER (link->data);
Descendant *prev = NULL;
Descendant *match = NULL;
for (match = *tree; match != NULL; match = match->siblings)
{
if (match->name == address)
{
if (prev != NULL)
{
/* Move to front */
prev->siblings = match->siblings;
match->siblings = *tree;
*tree = match;
}
break;
}
}
if (match == NULL)
{
/* Have we seen this object further up the tree? */
for (match = parent; match != NULL; match = match->parent)
{
if (match->name == address)
break;
}
}
if (match == NULL)
{
match = g_slice_new (Descendant);
match->name = address;
match->cumulative = 0;
match->self = 0;
match->children = NULL;
match->parent = parent;
match->siblings = *tree;
*tree = match;
}
tree = &match->children;
parent = match;
}
parent->self += size;
for (; parent != NULL; parent = parent->parent)
parent->cumulative += size;
}
static Descendant *
build_tree (StackNode *node)
{
Descendant *tree = NULL;
for (; node != NULL; node = node->next)
{
if (node->toplevel)
stack_node_foreach_trace (node, build_tree_cb, &tree);
}
return tree;
}
static void
append_to_tree_and_free (SpCallgraphView *self,
StackStash *stash,
GtkTreeStore *store,
Descendant *item,
GtkTreeIter *parent)
{
StackNode *node = NULL;
GtkTreeIter iter;
guint profile_size;
g_assert (GTK_IS_TREE_STORE (store));
g_assert (item != NULL);
profile_size = sp_callgraph_view_get_profile_size (self);
gtk_tree_store_append (store, &iter, parent);
node = stack_stash_find_node (stash, (gpointer)item->name);
gtk_tree_store_set (store, &iter,
COLUMN_NAME, item->name,
COLUMN_SELF, item->self * 100.0 / (gdouble)profile_size,
COLUMN_TOTAL, item->cumulative * 100.0 / (gdouble)profile_size,
COLUMN_POINTER, node,
-1);
if (item->siblings != NULL)
append_to_tree_and_free (self, stash, store, item->siblings, parent);
if (item->children != NULL)
append_to_tree_and_free (self, stash, store, item->children, &iter);
g_slice_free (Descendant, item);
}
static void
sp_callgraph_view_update_descendants (SpCallgraphView *self,
StackNode *node)
{
SpCallgraphViewPrivate *priv = sp_callgraph_view_get_instance_private (self);
GtkTreeStore *store;
g_assert (SP_IS_CALLGRAPH_VIEW (self));
store = gtk_tree_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
if (priv->profile != NULL)
{
StackStash *stash;
stash = sp_callgraph_profile_get_stash (priv->profile);
if (stash != NULL)
{
Descendant *tree;
tree = build_tree (node);
if (tree != NULL)
append_to_tree_and_free (self, stash, store, tree, NULL);
}
}
gtk_tree_view_set_model (priv->descendants_view, GTK_TREE_MODEL (store));
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
COLUMN_TOTAL, GTK_SORT_DESCENDING);
sp_callgraph_view_expand_descendants (self);
g_clear_object (&store);
}

44
lib/sp-callgraph-view.h Normal file
View File

@ -0,0 +1,44 @@
/* sp-callgraph-view.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CALLGRAPH_VIEW_H
#define SP_CALLGRAPH_VIEW_H
#include <gtk/gtk.h>
#include "sp-callgraph-profile.h"
G_BEGIN_DECLS
#define SP_TYPE_CALLGRAPH_VIEW (sp_callgraph_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpCallgraphView, sp_callgraph_view, SP, CALLGRAPH_VIEW, GtkBin)
struct _SpCallgraphViewClass
{
GtkBinClass parent_class;
};
GtkWidget *sp_callgraph_view_new (void);
SpCallgraphProfile *sp_callgraph_view_get_profile (SpCallgraphView *self);
void sp_callgraph_view_set_profile (SpCallgraphView *self,
SpCallgraphProfile *profile);
G_END_DECLS
#endif /* SP_CALLGRAPH_VIEW_H */

686
lib/sp-capture-reader.c Normal file
View File

@ -0,0 +1,686 @@
/* sp-capture-reader.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "sp-capture-reader.h"
#include "sp-capture-writer.h"
struct _SpCaptureReader
{
volatile gint ref_count;
gchar *filename;
guint8 *buf;
gsize bufsz;
gsize len;
gsize pos;
gsize fd_off;
int fd;
gint endian;
SpCaptureFileHeader header;
};
#ifndef SP_DISABLE_GOBJECT
G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader,
sp_capture_reader_ref, sp_capture_reader_unref)
#endif
static gboolean
sp_capture_reader_read_file_header (SpCaptureReader *self,
SpCaptureFileHeader *header,
GError **error)
{
g_assert (self != NULL);
g_assert (header != NULL);
if (sizeof *header != pread (self->fd, header, sizeof *header, 0L))
{
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
if (header->magic != SP_CAPTURE_MAGIC)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_FAILED,
"Capture file magic does not match");
return FALSE;
}
header->capture_time[sizeof header->capture_time - 1] = '\0';
return TRUE;
}
static void
sp_capture_reader_finalize (SpCaptureReader *self)
{
if (self != NULL)
{
close (self->fd);
g_free (self->buf);
g_free (self->filename);
g_free (self);
}
}
const gchar *
sp_capture_reader_get_time (SpCaptureReader *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->header.capture_time;
}
const gchar *
sp_capture_reader_get_filename (SpCaptureReader *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->filename;
}
SpCaptureReader *
sp_capture_reader_new_from_fd (int fd,
GError **error)
{
SpCaptureReader *self;
g_assert (fd > -1);
self = g_new0 (SpCaptureReader, 1);
self->ref_count = 1;
self->bufsz = G_MAXUSHORT * 2;
self->buf = g_malloc (self->bufsz);
self->len = 0;
self->pos = 0;
self->fd = fd;
self->fd_off = sizeof (SpCaptureFileHeader);
if (!sp_capture_reader_read_file_header (self, &self->header, error))
{
sp_capture_reader_finalize (self);
return NULL;
}
if (self->header.little_endian)
self->endian = G_LITTLE_ENDIAN;
else
self->endian = G_BIG_ENDIAN;
return self;
}
SpCaptureReader *
sp_capture_reader_new (const gchar *filename,
GError **error)
{
SpCaptureReader *self;
int fd;
g_assert (filename != NULL);
if (-1 == (fd = open (filename, O_RDONLY, 0)))
{
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return NULL;
}
if (NULL == (self = sp_capture_reader_new_from_fd (fd, error)))
{
close (fd);
return NULL;
}
self->filename = g_strdup (filename);
return self;
}
static inline void
sp_capture_reader_bswap_frame (SpCaptureReader *self,
SpCaptureFrame *frame)
{
g_assert (self != NULL);
g_assert (frame!= NULL);
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
{
frame->len = GUINT16_SWAP_LE_BE (frame->len);
frame->cpu = GUINT16_SWAP_LE_BE (frame->len);
frame->pid = GUINT32_SWAP_LE_BE (frame->len);
frame->time = GUINT64_SWAP_LE_BE (frame->len);
}
}
static inline void
sp_capture_reader_bswap_map (SpCaptureReader *self,
SpCaptureMap *map)
{
g_assert (self != NULL);
g_assert (map != NULL);
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
{
map->start = GUINT64_SWAP_LE_BE (map->start);
map->end = GUINT64_SWAP_LE_BE (map->end);
map->offset = GUINT64_SWAP_LE_BE (map->offset);
map->inode = GUINT64_SWAP_LE_BE (map->inode);
}
}
static inline void
sp_capture_reader_bswap_jitmap (SpCaptureReader *self,
SpCaptureJitmap *jitmap)
{
g_assert (self != NULL);
g_assert (jitmap != NULL);
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
jitmap->n_jitmaps = GUINT64_SWAP_LE_BE (jitmap->n_jitmaps);
}
static gboolean
sp_capture_reader_ensure_space_for (SpCaptureReader *self,
gsize len)
{
g_assert (self != NULL);
g_assert (len > 0);
if ((self->len - self->pos) < len)
{
gssize r;
g_assert (self->len >= self->pos);
memmove (self->buf, &self->buf[self->pos], self->len - self->pos);
self->len -= self->pos;
self->pos = 0;
while ((self->len - self->pos) <= len)
{
g_assert (self->pos + self->len < self->bufsz);
/* Read into our buffer after our current read position */
r = pread (self->fd,
&self->buf[self->len],
self->bufsz - self->len,
self->fd_off);
if (r <= 0)
break;
self->fd_off += r;
self->len += r;
}
}
return (self->len - self->pos) >= len;
}
gboolean
sp_capture_reader_skip (SpCaptureReader *self)
{
SpCaptureFrame *frame;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
if (!sp_capture_reader_ensure_space_for (self, sizeof (SpCaptureFrame)))
return FALSE;
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, frame);
if (frame->len < sizeof (SpCaptureFrame))
return FALSE;
if (!sp_capture_reader_ensure_space_for (self, frame->len))
return FALSE;
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
self->pos += frame->len;
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
return FALSE;
return TRUE;
}
gboolean
sp_capture_reader_peek_type (SpCaptureReader *self,
SpCaptureFrameType *type)
{
SpCaptureFrame *frame;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
g_assert (type != NULL);
*type = 0;
if (!sp_capture_reader_ensure_space_for (self, sizeof *frame))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
*type = frame->type;
return TRUE;
}
static const SpCaptureFrame *
sp_capture_reader_read_basic (SpCaptureReader *self,
SpCaptureFrameType type,
gsize extra)
{
SpCaptureFrame *frame;
gsize len = sizeof *frame + extra;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sp_capture_reader_ensure_space_for (self, len))
return NULL;
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, frame);
if (frame->len < len)
return NULL;
if (frame->type != type)
return NULL;
self->pos += frame->len;
return frame;
}
const SpCaptureTimestamp *
sp_capture_reader_read_timestamp (SpCaptureReader *self)
{
return (SpCaptureTimestamp *)
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_TIMESTAMP, 0);
}
const SpCaptureExit *
sp_capture_reader_read_exit (SpCaptureReader *self)
{
return (SpCaptureExit *)
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_EXIT, 0);
}
const SpCaptureFork *
sp_capture_reader_read_fork (SpCaptureReader *self)
{
SpCaptureFork *fk;
g_assert (self != NULL);
fk = (SpCaptureFork *)
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_FORK, sizeof(guint32));
if (fk != NULL)
{
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
fk->child_pid = GUINT32_SWAP_LE_BE (fk->child_pid);
}
return fk;
}
const SpCaptureMap *
sp_capture_reader_read_map (SpCaptureReader *self)
{
SpCaptureMap *map;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sp_capture_reader_ensure_space_for (self, sizeof *map))
return NULL;
map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, &map->frame);
if (map->frame.type != SP_CAPTURE_FRAME_MAP)
return NULL;
if (map->frame.len < (sizeof *map + 1))
return NULL;
if (!sp_capture_reader_ensure_space_for (self, map->frame.len))
return NULL;
map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
if (self->buf[self->pos + map->frame.len - 1] != '\0')
return NULL;
sp_capture_reader_bswap_map (self, map);
self->pos += map->frame.len;
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
return NULL;
return map;
}
const SpCaptureProcess *
sp_capture_reader_read_process (SpCaptureReader *self)
{
SpCaptureProcess *process;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sp_capture_reader_ensure_space_for (self, sizeof *process))
return NULL;
process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, &process->frame);
if (process->frame.type != SP_CAPTURE_FRAME_PROCESS)
return NULL;
if (process->frame.len < (sizeof *process + 1))
return NULL;
if (!sp_capture_reader_ensure_space_for (self, process->frame.len))
return NULL;
process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
if (self->buf[self->pos + process->frame.len - 1] != '\0')
return NULL;
self->pos += process->frame.len;
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
return NULL;
return process;
}
GHashTable *
sp_capture_reader_read_jitmap (SpCaptureReader *self)
{
g_autoptr(GHashTable) ret = NULL;
SpCaptureJitmap *jitmap;
guint8 *buf;
guint8 *endptr;
guint i;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sp_capture_reader_ensure_space_for (self, sizeof *jitmap))
return NULL;
jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, &jitmap->frame);
if (jitmap->frame.type != SP_CAPTURE_FRAME_JITMAP)
return NULL;
if (jitmap->frame.len < sizeof *jitmap)
return NULL;
if (!sp_capture_reader_ensure_space_for (self, jitmap->frame.len))
return NULL;
jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
ret = g_hash_table_new_full (NULL, NULL, NULL, g_free);
buf = jitmap->data;
endptr = &self->buf[self->pos + jitmap->frame.len];
for (i = 0; i < jitmap->n_jitmaps; i++)
{
SpCaptureAddress addr;
const gchar *str;
if (buf + sizeof addr >= endptr)
return NULL;
memcpy (&addr, buf, sizeof addr);
buf += sizeof addr;
str = (gchar *)buf;
buf = memchr (buf, '\0', (endptr - buf));
if (buf == NULL)
return NULL;
buf++;
g_hash_table_insert (ret, GSIZE_TO_POINTER (addr), g_strdup (str));
}
self->pos += jitmap->frame.len;
return g_steal_pointer (&ret);
}
const SpCaptureSample *
sp_capture_reader_read_sample (SpCaptureReader *self)
{
SpCaptureSample *sample;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sp_capture_reader_ensure_space_for (self, sizeof *sample))
return NULL;
sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
sp_capture_reader_bswap_frame (self, &sample->frame);
if (sample->frame.type != SP_CAPTURE_FRAME_SAMPLE)
return NULL;
if (sample->frame.len < sizeof *sample)
return NULL;
if (self->endian != G_BYTE_ORDER)
sample->n_addrs = GUINT16_SWAP_LE_BE (sample->n_addrs);
if (sample->frame.len < (sizeof *sample + (sizeof(SpCaptureAddress) * sample->n_addrs)))
return NULL;
if (!sp_capture_reader_ensure_space_for (self, sample->frame.len))
return NULL;
sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
if (self->endian != G_BYTE_ORDER)
{
guint i;
for (i = 0; i < sample->n_addrs; i++)
sample->addrs[i] = GUINT64_SWAP_LE_BE (sample->addrs[i]);
}
self->pos += sample->frame.len;
return sample;
}
gboolean
sp_capture_reader_reset (SpCaptureReader *self)
{
g_assert (self != NULL);
self->fd_off = sizeof (SpCaptureFileHeader);
self->pos = 0;
self->len = 0;
return TRUE;
}
SpCaptureReader *
sp_capture_reader_ref (SpCaptureReader *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
g_atomic_int_inc (&self->ref_count);
return self;
}
void
sp_capture_reader_unref (SpCaptureReader *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
sp_capture_reader_finalize (self);
}
gboolean
sp_capture_reader_splice (SpCaptureReader *self,
SpCaptureWriter *dest,
GError **error)
{
g_assert (self != NULL);
g_assert (self->fd != -1);
g_assert (dest != NULL);
/* Flush before writing anything to ensure consistency */
if (!sp_capture_writer_flush (dest))
{
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
/*
* We don't need to track position because writer will
* track the current position to avoid reseting it.
*/
/* Perform the splice */
return _sp_capture_writer_splice_from_fd (dest, self->fd, error);
}
/**
* sp_capture_reader_save_as:
* @self: An #SpCaptureReader
* @filename: the file to save the capture as
* @error: a location for a #GError or %NULL.
*
* This is a convenience function for copying a capture file for which
* you may have already discarded the writer for.
*
* Returns: %TRUE on success; otherwise %FALSE and @error is set.
*/
gboolean
sp_capture_reader_save_as (SpCaptureReader *self,
const gchar *filename,
GError **error)
{
struct stat stbuf;
off_t in_off;
gsize to_write;
int fd = -1;
g_assert (self != NULL);
g_assert (filename != NULL);
if (-1 == (fd = open (filename, O_CREAT | O_WRONLY, 0640)))
goto handle_errno;
if (-1 == fstat (self->fd, &stbuf))
goto handle_errno;
if (-1 == ftruncate (fd, stbuf.st_size))
goto handle_errno;
if ((off_t)-1 == lseek (fd, 0L, SEEK_SET))
goto handle_errno;
in_off = 0;
to_write = stbuf.st_size;
while (to_write > 0)
{
gssize written;
written = sendfile (fd, self->fd, &in_off, to_write);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
g_assert (written <= (gssize)to_write);
to_write -= written;
}
close (fd);
return TRUE;
handle_errno:
if (fd != -1)
close (fd);
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}

65
lib/sp-capture-reader.h Normal file
View File

@ -0,0 +1,65 @@
/* sp-capture-reader.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CAPTURE_READER_H
#define SP_CAPTURE_READER_H
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct _SpCaptureReader SpCaptureReader;
SpCaptureReader *sp_capture_reader_new (const gchar *filename,
GError **error);
SpCaptureReader *sp_capture_reader_new_from_fd (int fd,
GError **error);
SpCaptureReader *sp_capture_reader_ref (SpCaptureReader *self);
void sp_capture_reader_unref (SpCaptureReader *self);
const gchar *sp_capture_reader_get_filename (SpCaptureReader *self);
const gchar *sp_capture_reader_get_time (SpCaptureReader *self);
gboolean sp_capture_reader_skip (SpCaptureReader *self);
gboolean sp_capture_reader_peek_type (SpCaptureReader *self,
SpCaptureFrameType *type);
const SpCaptureMap *sp_capture_reader_read_map (SpCaptureReader *self);
const SpCaptureExit *sp_capture_reader_read_exit (SpCaptureReader *self);
const SpCaptureFork *sp_capture_reader_read_fork (SpCaptureReader *self);
const SpCaptureTimestamp *sp_capture_reader_read_timestamp (SpCaptureReader *self);
const SpCaptureProcess *sp_capture_reader_read_process (SpCaptureReader *self);
const SpCaptureSample *sp_capture_reader_read_sample (SpCaptureReader *self);
GHashTable *sp_capture_reader_read_jitmap (SpCaptureReader *self);
gboolean sp_capture_reader_reset (SpCaptureReader *self);
gboolean sp_capture_reader_splice (SpCaptureReader *self,
SpCaptureWriter *dest,
GError **error);
gboolean sp_capture_reader_save_as (SpCaptureReader *self,
const gchar *filename,
GError **error);
#ifndef SP_DISABLE_GOBJECT
# define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
GType sp_capture_reader_get_type (void);
#endif
#if GLIB_CHECK_VERSION(2, 44, 0)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureReader, sp_capture_reader_unref)
#endif
G_END_DECLS
#endif /* SP_CAPTURE_READER_H */

155
lib/sp-capture-types.h Normal file
View File

@ -0,0 +1,155 @@
/* sp-capture-types.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CAPTURE_FORMAT_H
#define SP_CAPTURE_FORMAT_H
#include <glib.h>
#ifndef SP_DISABLE_GOBJECT
# include <glib-object.h>
#endif
G_BEGIN_DECLS
#define SP_CAPTURE_MAGIC (GUINT32_TO_LE(0xFDCA975E))
#define SP_CAPTURE_ALIGN (sizeof(SpCaptureAddress))
#if __WORDSIZE == 64
# define SP_CAPTURE_JITMAP_MARK G_GUINT64_CONSTANT(0xE000000000000000)
# define SP_CAPTURE_ADDRESS_FORMAT "0x%016lx"
#else
# define SP_CAPTURE_JITMAP_MARK G_GUINT64_CONSTANT(0xE0000000)
# define SP_CAPTURE_ADDRESS_FORMAT "0x%016llx"
#endif
#define SP_CAPTURE_CURRENT_TIME (g_get_monotonic_time() * 1000L)
typedef struct _SpCaptureReader SpCaptureReader;
typedef struct _SpCaptureWriter SpCaptureWriter;
typedef guint64 SpCaptureAddress;
typedef enum
{
SP_CAPTURE_FRAME_TIMESTAMP = 1,
SP_CAPTURE_FRAME_SAMPLE = 2,
SP_CAPTURE_FRAME_MAP = 3,
SP_CAPTURE_FRAME_PROCESS = 4,
SP_CAPTURE_FRAME_FORK = 5,
SP_CAPTURE_FRAME_EXIT = 6,
SP_CAPTURE_FRAME_JITMAP = 7,
} SpCaptureFrameType;
#pragma pack(push, 1)
typedef struct
{
guint32 magic;
guint8 version;
guint32 little_endian : 1;
guint32 padding : 23;
gchar capture_time[64];
gchar suffix[184];
} SpCaptureFileHeader;
typedef struct
{
guint16 len;
gint16 cpu;
gint32 pid;
gint64 time;
guint8 type;
guint64 padding : 56;
guint8 data[0];
} SpCaptureFrame;
typedef struct
{
SpCaptureFrame frame;
guint64 start;
guint64 end;
guint64 offset;
guint64 inode;
gchar filename[0];
} SpCaptureMap;
typedef struct
{
SpCaptureFrame frame;
guint32 n_jitmaps;
guint8 data[0];
} SpCaptureJitmap;
typedef struct
{
SpCaptureFrame frame;
gchar cmdline[0];
} SpCaptureProcess;
typedef struct
{
SpCaptureFrame frame;
guint16 n_addrs;
guint64 padding : 48;
SpCaptureAddress addrs[0];
} SpCaptureSample;
typedef struct
{
SpCaptureFrame frame;
GPid child_pid;
} SpCaptureFork;
typedef struct
{
SpCaptureFrame frame;
} SpCaptureExit;
typedef struct
{
SpCaptureFrame frame;
} SpCaptureTimestamp;
#pragma pack(pop)
G_STATIC_ASSERT (sizeof (SpCaptureFileHeader) == 256);
G_STATIC_ASSERT (sizeof (SpCaptureFrame) == 24);
G_STATIC_ASSERT (sizeof (SpCaptureMap) == 56);
G_STATIC_ASSERT (sizeof (SpCaptureJitmap) == 28);
G_STATIC_ASSERT (sizeof (SpCaptureProcess) == 24);
G_STATIC_ASSERT (sizeof (SpCaptureSample) == 32);
G_STATIC_ASSERT (sizeof (SpCaptureFork) == 28);
G_STATIC_ASSERT (sizeof (SpCaptureExit) == 24);
G_STATIC_ASSERT (sizeof (SpCaptureTimestamp) == 24);
static inline gint
sp_capture_address_compare (SpCaptureAddress a,
SpCaptureAddress b)
{
if (a < b)
return -1;
if (a > b)
return 1;
else
return 0;
}
G_END_DECLS
#endif /* SP_CAPTURE_FORMAT_H */

954
lib/sp-capture-writer.c Normal file
View File

@ -0,0 +1,954 @@
/* sp-capture-writer.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <glib/gstdio.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "sp-capture-reader.h"
#include "sp-capture-writer.h"
#define DEFAULT_BUFFER_SIZE (getpagesize() * 64L)
#define INVALID_ADDRESS (G_GUINT64_CONSTANT(0))
typedef struct
{
/* A pinter into the string buffer */
const gchar *str;
/* The unique address for the string */
guint64 addr;
} SpCaptureJitmapBucket;
struct _SpCaptureWriter
{
/*
* This is our buffer location for incoming strings. This is used
* similarly to GStringChunk except there is only one-page, and after
* it fills, we flush to disk.
*
* This is paired with a closed hash table for deduplication.
*/
gchar addr_buf[4096*4];
/* Our hashtable for deduplication. */
SpCaptureJitmapBucket addr_hash[512];
/* We keep the large fields above so that our allocation will be page
* alinged for the write buffer. This improves the performance of large
* writes to the target file-descriptor.
*/
volatile gint ref_count;
/*
* Our address sequence counter. The value that comes from
* monotonically increasing this is OR'd with JITMAP_MARK to denote
* the address name should come from the JIT map.
*/
gsize addr_seq;
/* Our position in addr_buf. */
gsize addr_buf_pos;
/*
* The number of hash table items in @addr_hash. This is an
* optimization so that we can avoid calculating the number of strings
* when flushing out the jitmap.
*/
guint addr_hash_size;
/* Capture file handle */
int fd;
/* Our write buffer for fd */
guint8 *buf;
gsize pos;
gsize len;
/* Statistics while recording */
SpCaptureStat stat;
};
#ifndef SP_DISABLE_GOBJECT
G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer,
sp_capture_writer_ref, sp_capture_writer_unref)
#endif
static void
sp_capture_writer_finalize (SpCaptureWriter *self)
{
if (self != NULL)
{
sp_capture_writer_flush (self);
close (self->fd);
g_free (self->buf);
g_free (self);
}
}
SpCaptureWriter *
sp_capture_writer_ref (SpCaptureWriter *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
g_atomic_int_inc (&self->ref_count);
return self;
}
void
sp_capture_writer_unref (SpCaptureWriter *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
sp_capture_writer_finalize (self);
}
static gboolean
sp_capture_writer_flush_data (SpCaptureWriter *self)
{
const guint8 *buf;
gssize written;
gsize to_write;
g_assert (self != NULL);
g_assert (self->pos <= self->len);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
buf = self->buf;
to_write = self->pos;
while (to_write > 0)
{
written = write (self->fd, buf, to_write);
if (written < 0)
return FALSE;
if (written == 0 && errno != EAGAIN)
return FALSE;
g_assert (written <= (gssize)to_write);
buf += written;
to_write -= written;
}
self->pos = 0;
return TRUE;
}
static inline void
sp_capture_writer_realign (gsize *pos)
{
*pos = (*pos + SP_CAPTURE_ALIGN - 1) & ~(SP_CAPTURE_ALIGN - 1);
}
static inline gboolean
sp_capture_writer_ensure_space_for (SpCaptureWriter *self,
gsize len)
{
if ((self->len - self->pos) < len)
{
if (!sp_capture_writer_flush_data (self))
return FALSE;
}
return TRUE;
}
static gboolean
sp_capture_writer_flush_jitmap (SpCaptureWriter *self)
{
SpCaptureJitmap jitmap;
gssize r;
gsize len;
g_assert (self != NULL);
if (self->addr_hash_size == 0)
return TRUE;
g_assert (self->addr_buf_pos > 0);
len = sizeof jitmap + self->addr_buf_pos;
sp_capture_writer_realign (&len);
jitmap.frame.len = len;
jitmap.frame.cpu = -1;
jitmap.frame.pid = getpid ();
jitmap.frame.time = g_get_monotonic_time ();
jitmap.frame.type = SP_CAPTURE_FRAME_JITMAP;
jitmap.n_jitmaps = self->addr_hash_size;
if (sizeof jitmap != write (self->fd, &jitmap, sizeof jitmap))
return FALSE;
r = write (self->fd, self->addr_buf, len - sizeof jitmap);
if (r < 0 || (gsize)r != (len - sizeof jitmap))
return FALSE;
self->addr_buf_pos = 0;
self->addr_hash_size = 0;
memset (self->addr_hash, 0, sizeof self->addr_hash);
self->stat.frame_count[SP_CAPTURE_FRAME_JITMAP]++;
return TRUE;
}
static gboolean
sp_capture_writer_lookup_jitmap (SpCaptureWriter *self,
const gchar *name,
SpCaptureAddress *addr)
{
guint hash;
guint i;
g_assert (self != NULL);
g_assert (name != NULL);
g_assert (addr != NULL);
hash = g_str_hash (name) % G_N_ELEMENTS (self->addr_hash);
for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
{
SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (bucket->str == NULL)
return FALSE;
if (strcmp (bucket->str, name) == 0)
{
*addr = bucket->addr;
return TRUE;
}
}
for (i = 0; i < hash; i++)
{
SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (bucket->str == NULL)
return FALSE;
if (strcmp (bucket->str, name) == 0)
{
*addr = bucket->addr;
return TRUE;
}
}
return FALSE;
}
static SpCaptureAddress
sp_capture_writer_insert_jitmap (SpCaptureWriter *self,
const gchar *str)
{
SpCaptureAddress addr;
gchar *dst;
gsize len;
guint hash;
guint i;
g_assert (self != NULL);
g_assert (str != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
len = sizeof addr + strlen (str) + 1;
if ((self->addr_hash_size == G_N_ELEMENTS (self->addr_hash)) ||
((sizeof self->addr_buf - self->addr_buf_pos) < len))
{
if (!sp_capture_writer_flush_jitmap (self))
return INVALID_ADDRESS;
g_assert (self->addr_hash_size == 0);
g_assert (self->addr_buf_pos == 0);
}
g_assert (self->addr_hash_size < G_N_ELEMENTS (self->addr_hash));
g_assert (len > sizeof addr);
/* Allocate the next unique address */
addr = SP_CAPTURE_JITMAP_MARK | ++self->addr_seq;
/* Copy the address into the buffer */
dst = (gchar *)&self->addr_buf[self->addr_buf_pos];
memcpy (dst, &addr, sizeof addr);
/*
* Copy the string into the buffer, keeping dst around for
* when we insert into the hashtable.
*/
dst += sizeof addr;
memcpy (dst, str, len - sizeof addr);
/* Advance our string cache position */
self->addr_buf_pos += len;
g_assert (self->addr_buf_pos <= sizeof self->addr_buf);
/* Now place the address into the hashtable */
hash = g_str_hash (str) % G_N_ELEMENTS (self->addr_hash);
/* Start from the current hash bucket and go forward */
for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
{
SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (G_LIKELY (bucket->str == NULL))
{
bucket->str = dst;
bucket->addr = addr;
self->addr_hash_size++;
return addr;
}
}
/* Wrap around to the beginning */
for (i = 0; i < hash; i++)
{
SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (G_LIKELY (bucket->str == NULL))
{
bucket->str = dst;
bucket->addr = addr;
self->addr_hash_size++;
return addr;
}
}
g_assert_not_reached ();
return INVALID_ADDRESS;
}
SpCaptureWriter *
sp_capture_writer_new_from_fd (int fd,
gsize buffer_size)
{
g_autofree gchar *nowstr = NULL;
SpCaptureWriter *self;
SpCaptureFileHeader *header;
GTimeVal tv;
if (buffer_size == 0)
buffer_size = DEFAULT_BUFFER_SIZE;
g_assert (fd != -1);
g_assert (buffer_size % getpagesize() == 0);
self = g_new0 (SpCaptureWriter, 1);
self->ref_count = 1;
self->fd = fd;
self->buf = (guint8 *)g_malloc (buffer_size);
self->len = buffer_size;
g_get_current_time (&tv);
nowstr = g_time_val_to_iso8601 (&tv);
header = (SpCaptureFileHeader *)(gpointer)self->buf;
header->magic = SP_CAPTURE_MAGIC;
header->version = 1;
#ifdef G_LITTLE_ENDIAN
header->little_endian = TRUE;
#else
header->little_endian = FALSE;
#endif
header->padding = 0;
g_strlcpy (header->capture_time, nowstr, sizeof header->capture_time);
memset (header->suffix, 0, sizeof header->suffix);
self->pos += sizeof *header;
if (!sp_capture_writer_flush_data (self))
{
sp_capture_writer_finalize (self);
return NULL;
}
g_assert (self->pos == 0);
g_assert (self->len > 0);
g_assert (self->len % getpagesize() == 0);
g_assert (self->buf != NULL);
g_assert (self->addr_hash_size == 0);
g_assert (self->fd != -1);
return self;
}
SpCaptureWriter *
sp_capture_writer_new (const gchar *filename,
gsize buffer_size)
{
SpCaptureWriter *self;
int fd;
g_assert (filename != NULL);
g_assert (buffer_size % getpagesize() == 0);
if ((-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640))) ||
(-1 == ftruncate (fd, 0L)))
return NULL;
self = sp_capture_writer_new_from_fd (fd, buffer_size);
if (self == NULL)
close (fd);
return self;
}
gboolean
sp_capture_writer_add_map (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
guint64 start,
guint64 end,
guint64 offset,
guint64 inode,
const gchar *filename)
{
SpCaptureMap *ev;
gsize len;
if (filename == NULL)
filename = "";
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (filename != NULL);
len = sizeof *ev + strlen (filename) + 1;
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_MAP;
ev->frame.padding = 0;
ev->start = start;
ev->end = end;
ev->offset = offset;
ev->inode = inode;
g_strlcpy (ev->filename, filename, len - sizeof *ev);
ev->filename[len - sizeof *ev - 1] = '\0';
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_MAP]++;
return TRUE;
}
SpCaptureAddress
sp_capture_writer_add_jitmap (SpCaptureWriter *self,
const gchar *name)
{
SpCaptureAddress addr = INVALID_ADDRESS;
if (name == NULL)
name = "";
g_assert (self != NULL);
g_assert (name != NULL);
if (!sp_capture_writer_lookup_jitmap (self, name, &addr))
addr = sp_capture_writer_insert_jitmap (self, name);
return addr;
}
gboolean
sp_capture_writer_add_process (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
const gchar *cmdline)
{
SpCaptureProcess *ev;
gsize len;
if (cmdline == NULL)
cmdline = "";
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
g_assert (cmdline != NULL);
len = sizeof *ev + strlen (cmdline) + 1;
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_PROCESS;
ev->frame.padding = 0;
g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
ev->cmdline[len - sizeof *ev - 1] = '\0';
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_PROCESS]++;
return TRUE;
}
gboolean
sp_capture_writer_add_sample (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
const SpCaptureAddress *addrs,
guint n_addrs)
{
SpCaptureSample *ev;
gsize len;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
len = sizeof *ev + (n_addrs * sizeof (SpCaptureAddress));
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_SAMPLE;
ev->frame.padding = 0;
ev->n_addrs = n_addrs;
memcpy (ev->addrs, addrs, (n_addrs * sizeof (SpCaptureAddress)));
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_SAMPLE]++;
return TRUE;
}
gboolean
sp_capture_writer_add_fork (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
GPid child_pid)
{
SpCaptureFork *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureFork *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_FORK;
ev->frame.padding = 0;
ev->child_pid = child_pid;
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_FORK]++;
return TRUE;
}
gboolean
sp_capture_writer_add_exit (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid)
{
SpCaptureExit *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureExit *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_EXIT;
ev->frame.padding = 0;
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_EXIT]++;
return TRUE;
}
gboolean
sp_capture_writer_add_timestamp (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid)
{
SpCaptureTimestamp *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
sp_capture_writer_realign (&len);
if (!sp_capture_writer_ensure_space_for (self, len))
return FALSE;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
ev = (SpCaptureTimestamp *)(gpointer)&self->buf[self->pos];
ev->frame.len = len;
ev->frame.cpu = cpu;
ev->frame.pid = pid;
ev->frame.time = time;
ev->frame.type = SP_CAPTURE_FRAME_TIMESTAMP;
ev->frame.padding = 0;
self->pos += ev->frame.len;
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
self->stat.frame_count[SP_CAPTURE_FRAME_TIMESTAMP]++;
return TRUE;
}
gboolean
sp_capture_writer_flush (SpCaptureWriter *self)
{
g_assert (self != NULL);
return (sp_capture_writer_flush_jitmap (self) &&
sp_capture_writer_flush_data (self));
}
/**
* sp_capture_writer_save_as:
* @self: A #SpCaptureWriter
* @filename: the file to save the capture as
* @error: a location for a #GError or %NULL.
*
* Saves the captured data as the file @filename.
*
* This is primarily useful if the writer was created with a memory-backed
* file-descriptor such as a memfd or tmpfs file on Linux.
*
* Returns: %TRUE if successful, otherwise %FALSE and @error is set.
*/
gboolean
sp_capture_writer_save_as (SpCaptureWriter *self,
const gchar *filename,
GError **error)
{
gsize to_write;
off_t in_off;
off_t pos;
int fd = -1;
g_assert (self != NULL);
g_assert (self->fd != -1);
g_assert (filename != NULL);
if (-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640)))
goto handle_errno;
if (!sp_capture_writer_flush (self))
goto handle_errno;
if (-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
goto handle_errno;
to_write = pos;
in_off = 0;
while (to_write > 0)
{
gssize written;
written = sendfile (fd, self->fd, &in_off, pos);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
g_assert (written <= (gssize)to_write);
to_write -= written;
}
close (fd);
return TRUE;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
if (fd != -1)
{
close (fd);
g_unlink (filename);
}
return FALSE;
}
/**
* _sp_capture_writer_splice_from_fd:
* @self: An #SpCaptureWriter
* @fd: the fd to read from.
* @error: A location for a #GError, or %NULL.
*
* This is internal API for SpCaptureWriter and SpCaptureReader to
* communicate when splicing a reader into a writer.
*
* This should not be used outside of #SpCaptureReader or
* #SpCaptureWriter.
*
* This will not advance the position of @fd.
*
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
*/
gboolean
_sp_capture_writer_splice_from_fd (SpCaptureWriter *self,
int fd,
GError **error)
{
struct stat stbuf;
off_t in_off;
gsize to_write;
g_assert (self != NULL);
g_assert (self->fd != -1);
if (-1 == fstat (fd, &stbuf))
goto handle_errno;
if (stbuf.st_size < 256)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_INVAL,
"Cannot splice, possibly corrupt file.");
return FALSE;
}
in_off = 256;
to_write = stbuf.st_size - in_off;
while (to_write > 0)
{
gssize written;
written = sendfile (self->fd, fd, &in_off, to_write);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
g_assert (written <= (gssize)to_write);
to_write -= written;
}
return TRUE;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
/**
* sp_capture_writer_splice:
* @self: An #SpCaptureWriter
* @dest: An #SpCaptureWriter
* @error: A location for a #GError, or %NULL.
*
* This function will copy the capture @self into the capture @dest. This
* tries to be semi-efficient by using sendfile() to copy the contents between
* the captures. @self and @dest will be flushed before the contents are copied
* into the @dest file-descriptor.
*
* Returns: %TRUE if successful, otherwise %FALSE and and @error is set.
*/
gboolean
sp_capture_writer_splice (SpCaptureWriter *self,
SpCaptureWriter *dest,
GError **error)
{
gboolean ret;
off_t pos;
g_assert (self != NULL);
g_assert (self->fd != -1);
g_assert (dest != NULL);
g_assert (dest->fd != -1);
/* Flush before writing anything to ensure consistency */
if (!sp_capture_writer_flush (self) || !sp_capture_writer_flush (dest))
goto handle_errno;
/* Track our current position so we can reset */
if ((off_t)-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
goto handle_errno;
/* Perform the splice */
ret = _sp_capture_writer_splice_from_fd (dest, self->fd, error);
/* Now reset or file-descriptor position (it should be the same */
if (pos != lseek (self->fd, pos, SEEK_SET))
{
ret = FALSE;
goto handle_errno;
}
return ret;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
/**
* sp_capture_writer_create_reader:
* @self: A #SpCaptureWriter
* @error: a location for a #GError, or %NULL
*
* Creates a new reader for the writer.
*
* Since readers use positioned reads, this uses the same file-descriptor for
* the #SpCaptureReader. Therefore, if you are writing to the capture while
* also consuming from the reader, you could get transient failures unless you
* synchronize the operations.
*
* Returns: (transfer full): A #SpCaptureReader.
*/
SpCaptureReader *
sp_capture_writer_create_reader (SpCaptureWriter *self,
GError **error)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (self->fd != -1, NULL);
return sp_capture_reader_new_from_fd (self->fd, error);
}
/**
* sp_capture_writer_stat:
* @self: A #SpCaptureWriter
* @stat: (out): A location for an #SpCaptureStat
*
* This function will fill @stat with statistics generated while capturing
* the profiler session.
*/
void
sp_capture_writer_stat (SpCaptureWriter *self,
SpCaptureStat *stat)
{
g_return_if_fail (self != NULL);
g_return_if_fail (stat != NULL);
*stat = self->stat;
}

108
lib/sp-capture-writer.h Normal file
View File

@ -0,0 +1,108 @@
/* sp-capture-writer.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This file 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CAPTURE_WRITER_H
#define SP_CAPTURE_WRITER_H
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct _SpCaptureWriter SpCaptureWriter;
typedef struct
{
/*
* The number of frames indexed by SpCaptureFrameType
*/
gsize frame_count[16];
/*
* Padding for future expansion.
*/
gsize padding[48];
} SpCaptureStat;
SpCaptureWriter *sp_capture_writer_new (const gchar *filename,
gsize buffer_size);
SpCaptureWriter *sp_capture_writer_new_from_fd (int fd,
gsize buffer_size);
SpCaptureWriter *sp_capture_writer_ref (SpCaptureWriter *self);
void sp_capture_writer_unref (SpCaptureWriter *self);
void sp_capture_writer_stat (SpCaptureWriter *self,
SpCaptureStat *stat);
gboolean sp_capture_writer_add_map (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
guint64 start,
guint64 end,
guint64 offset,
guint64 inode,
const gchar *filename);
guint64 sp_capture_writer_add_jitmap (SpCaptureWriter *self,
const gchar *name);
gboolean sp_capture_writer_add_process (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
const gchar *cmdline);
gboolean sp_capture_writer_add_sample (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
const SpCaptureAddress *addrs,
guint n_addrs);
gboolean sp_capture_writer_add_fork (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid,
GPid child_pid);
gboolean sp_capture_writer_add_exit (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid);
gboolean sp_capture_writer_add_timestamp (SpCaptureWriter *self,
gint64 time,
gint cpu,
GPid pid);
gboolean sp_capture_writer_flush (SpCaptureWriter *self);
gboolean sp_capture_writer_save_as (SpCaptureWriter *self,
const gchar *filename,
GError **error);
SpCaptureReader *sp_capture_writer_create_reader (SpCaptureWriter *self,
GError **error);
gboolean sp_capture_writer_splice (SpCaptureWriter *self,
SpCaptureWriter *dest,
GError **error);
gboolean _sp_capture_writer_splice_from_fd (SpCaptureWriter *self,
int fd,
GError **error) G_GNUC_INTERNAL;
#ifndef SP_DISABLE_GOBJECT
# define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
GType sp_capture_writer_get_type (void);
#endif
#if GLIB_CHECK_VERSION(2, 44, 0)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureWriter, sp_capture_writer_unref)
#endif
G_END_DECLS
#endif /* SP_CAPTURE_WRITER_H */

View File

@ -0,0 +1,135 @@
/* sp-cell-renderer-percent.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <glib/gi18n.h>
#include "sp-cell-renderer-percent.h"
typedef struct
{
gdouble percent;
} SpCellRendererPercentPrivate;
enum {
PROP_0,
PROP_PERCENT,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (SpCellRendererPercent, sp_cell_renderer_percent, GTK_TYPE_CELL_RENDERER_TEXT)
static GParamSpec *properties [N_PROPS];
static void
sp_cell_renderer_percent_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
switch (prop_id)
{
case PROP_PERCENT:
g_value_set_double (value, sp_cell_renderer_percent_get_percent (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_cell_renderer_percent_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
switch (prop_id)
{
case PROP_PERCENT:
sp_cell_renderer_percent_set_percent (self, g_value_get_double (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_cell_renderer_percent_class_init (SpCellRendererPercentClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = sp_cell_renderer_percent_get_property;
object_class->set_property = sp_cell_renderer_percent_set_property;
properties [PROP_PERCENT] =
g_param_spec_double ("percent",
"Percent",
"Percent",
0.0,
100.0,
0.0,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_cell_renderer_percent_init (SpCellRendererPercent *self)
{
g_object_set (self, "xalign", 1.0f, NULL);
}
gdouble
sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self)
{
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
g_return_val_if_fail (SP_IS_CELL_RENDERER_PERCENT (self), 0.0);
return priv->percent;
}
void
sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
gdouble percent)
{
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
g_return_if_fail (SP_IS_CELL_RENDERER_PERCENT (self));
g_return_if_fail (percent >= 0.0);
g_return_if_fail (percent <= 100.0);
if (percent != priv->percent)
{
gchar text[128];
priv->percent = percent;
g_snprintf (text, sizeof text, "%.2lf<span size='smaller'><span size='smaller'> </span>%%</span>", percent);
text [sizeof text - 1] = '\0';
g_object_set (self, "markup", text, NULL);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PERCENT]);
}
}

View File

@ -0,0 +1,55 @@
/* sp-cell-renderer-percent.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CELL_RENDERER_PERCENT_H
#define SP_CELL_RENDERER_PERCENT_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_CELL_RENDERER_PERCENT (sp_cell_renderer_percent_get_type())
#define SP_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent))
#define SP_CELL_RENDERER_PERCENT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent const))
#define SP_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
#define SP_IS_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CELL_RENDERER_PERCENT))
#define SP_IS_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CELL_RENDERER_PERCENT))
#define SP_CELL_RENDERER_PERCENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
typedef struct _SpCellRendererPercent SpCellRendererPercent;
typedef struct _SpCellRendererPercentClass SpCellRendererPercentClass;
struct _SpCellRendererPercent
{
GtkCellRendererText parent;
};
struct _SpCellRendererPercentClass
{
GtkCellRendererTextClass parent_class;
};
GType sp_cell_renderer_percent_get_type (void);
GtkCellRenderer *sp_cell_renderer_percent_new (void);
gdouble sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self);
void sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
gdouble percent);
G_END_DECLS
#endif /* SP_CELL_RENDERER_PERCENT_H */

51
lib/sp-clock.c Normal file
View File

@ -0,0 +1,51 @@
/* sp-clock.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-clock.h"
gint sp_clock = -1;
void
sp_clock_init (void)
{
static const gint clock_ids[] = {
CLOCK_MONOTONIC_RAW,
CLOCK_MONOTONIC_COARSE,
CLOCK_MONOTONIC,
CLOCK_REALTIME_COARSE,
CLOCK_REALTIME,
};
guint i;
if (sp_clock != -1)
return;
for (i = 0; i < G_N_ELEMENTS (clock_ids); i++)
{
struct timespec ts;
int clock_id = clock_ids [i];
if (0 == clock_gettime (clock_id, &ts))
{
sp_clock = clock_id;
return;
}
}
g_assert_not_reached ();
}

54
lib/sp-clock.h Normal file
View File

@ -0,0 +1,54 @@
/* sp-clock.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CLOCK_H
#define SP_CLOCK_H
#include <glib.h>
#include <time.h>
G_BEGIN_DECLS
typedef gint SpClock;
typedef gint64 SpTimeStamp;
typedef gint32 SpTimeSpan;
extern SpClock sp_clock;
static inline SpTimeStamp
sp_clock_get_current_time (void)
{
struct timespec ts;
clock_gettime (sp_clock, &ts);
return (ts.tv_sec * G_GINT64_CONSTANT (1000000000)) + ts.tv_nsec;
}
static inline SpTimeSpan
sp_clock_get_relative_time (SpTimeStamp epoch)
{
return sp_clock_get_current_time () - epoch;
}
void sp_clock_init (void);
G_END_DECLS
#endif /* SP_CLOCK_H */

View File

@ -0,0 +1,276 @@
/* sp-elf-symbol-resolver.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "binfile.h"
#include "elfparser.h"
#include "sp-map-lookaside.h"
#include "sp-elf-symbol-resolver.h"
struct _SpElfSymbolResolver
{
GObject parent_instance;
GHashTable *lookasides;
GHashTable *bin_files;
GHashTable *tag_cache;
};
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpElfSymbolResolver,
sp_elf_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
sp_elf_symbol_resolver_finalize (GObject *object)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)object;
g_clear_pointer (&self->bin_files, g_hash_table_unref);
g_clear_pointer (&self->lookasides, g_hash_table_unref);
g_clear_pointer (&self->tag_cache, g_hash_table_unref);
G_OBJECT_CLASS (sp_elf_symbol_resolver_parent_class)->finalize (object);
}
static void
sp_elf_symbol_resolver_class_init (SpElfSymbolResolverClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_elf_symbol_resolver_finalize;
}
static void
sp_elf_symbol_resolver_init (SpElfSymbolResolver *self)
{
self->lookasides = g_hash_table_new_full (NULL,
NULL,
NULL,
(GDestroyNotify)sp_map_lookaside_free);
self->bin_files = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)bin_file_free);
self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static void
sp_elf_symbol_resolver_load (SpSymbolResolver *resolver,
SpCaptureReader *reader)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
SpCaptureFrameType type;
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
g_assert (reader != NULL);
sp_capture_reader_reset (reader);
while (sp_capture_reader_peek_type (reader, &type))
{
const SpCaptureMap *ev;
SpMapLookaside *lookaside;
SpMap map;
if (type != SP_CAPTURE_FRAME_MAP)
{
if (!sp_capture_reader_skip (reader))
return;
continue;
}
ev = sp_capture_reader_read_map (reader);
map.start = ev->start;
map.end = ev->end;
map.offset = ev->offset;
map.inode = ev->inode;
map.filename = ev->filename;
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid));
if (lookaside == NULL)
{
lookaside = sp_map_lookaside_new ();
g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside);
}
sp_map_lookaside_insert (lookaside, &map);
}
}
static bin_file_t *
sp_elf_symbol_resolver_get_bin_file (SpElfSymbolResolver *self,
const gchar *filename)
{
bin_file_t *bin_file;
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
bin_file = g_hash_table_lookup (self->bin_files, filename);
if (bin_file == NULL)
{
bin_file = bin_file_new (filename);
g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file);
}
return bin_file;
}
static GQuark
guess_tag (SpElfSymbolResolver *self,
const SpMap *map)
{
g_assert (map != NULL);
g_assert (map->filename != NULL);
if (!g_hash_table_contains (self->tag_cache, map->filename))
{
GQuark tag = 0;
if (strstr (map->filename, "/libgobject-2.0."))
tag = g_quark_from_static_string ("GObject");
else if (strstr (map->filename, "/libglib-2.0."))
tag = g_quark_from_static_string ("GLib");
else if (strstr (map->filename, "/libgio-2.0."))
tag = g_quark_from_static_string ("Gio");
else if (strstr (map->filename, "/libgtk-3."))
tag = g_quark_from_static_string ("Gtk+");
else if (strstr (map->filename, "/libgdk-3."))
tag = g_quark_from_static_string ("Gdk");
else if (strstr (map->filename, "/libpixman-1"))
tag = g_quark_from_static_string ("Pixman");
else if (strstr (map->filename, "/libcairo."))
tag = g_quark_from_static_string ("cairo");
else if (strstr (map->filename, "/libgstreamer-1."))
tag = g_quark_from_static_string ("GStreamer");
else if (strstr (map->filename, "/libX11."))
tag = g_quark_from_static_string ("X11");
else if (strstr (map->filename, "/libpango-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libclutter-"))
tag = g_quark_from_static_string ("Clutter");
else if (strstr (map->filename, "/libcogl.") ||
strstr (map->filename, "/libcogl-"))
tag = g_quark_from_static_string ("Cogl");
else if (strstr (map->filename, "/libffi."))
tag = g_quark_from_static_string ("libffi");
else if (strstr (map->filename, "/libwayland-"))
tag = g_quark_from_static_string ("Wayland");
else if (strstr (map->filename, "/libinput."))
tag = g_quark_from_static_string ("libinput");
else if (strstr (map->filename, "/libgjs."))
tag = g_quark_from_static_string ("Gjs");
else if (strstr (map->filename, "/libmozjs-"))
tag = g_quark_from_static_string ("MozJS");
else if (strstr (map->filename, "/libGL."))
tag = g_quark_from_static_string ("GL");
else if (strstr (map->filename, "/libEGL."))
tag = g_quark_from_static_string ("EGL");
g_hash_table_insert (self->tag_cache,
g_strdup (map->filename),
GSIZE_TO_POINTER (tag));
}
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename));
}
static gchar *
sp_elf_symbol_resolver_resolve (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpCaptureAddress address,
GQuark *tag)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
const bin_symbol_t *bin_sym;
SpMapLookaside *lookaside;
const gchar *bin_sym_name;
const SpMap *map;
bin_file_t *bin_file;
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (pid));
if (lookaside == NULL)
return NULL;
map = sp_map_lookaside_lookup (lookaside, address);
if (map == NULL)
return NULL;
address -= map->start;
address += map->offset;
bin_file = sp_elf_symbol_resolver_get_bin_file (self, map->filename);
g_assert (bin_file != NULL);
if (map->inode && !bin_file_check_inode (bin_file, map->inode))
return g_strdup_printf ("%s: inode mismatch", map->filename);
bin_sym = bin_file_lookup_symbol (bin_file, address);
bin_sym_name = bin_symbol_get_name (bin_file, bin_sym);
if (map->filename)
*tag = guess_tag (self, map);
return elf_demangle (bin_sym_name);
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->load = sp_elf_symbol_resolver_load;
iface->resolve = sp_elf_symbol_resolver_resolve;
}
SpSymbolResolver *
sp_elf_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_ELF_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-elf-symbol-resolver.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_ELF_SYMBOL_RESOLVER_H
#define SP_ELF_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_ELF_SYMBOL_RESOLVER (sp_elf_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpElfSymbolResolver, sp_elf_symbol_resolver, SP, ELF_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_elf_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_ELF_SYMBOL_RESOLVER_H */

41
lib/sp-empty-state-view.c Normal file
View File

@ -0,0 +1,41 @@
/* sp-empty-state-view.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-empty-state-view.h"
G_DEFINE_TYPE (SpEmptyStateView, sp_empty_state_view, GTK_TYPE_BIN)
GtkWidget *
sp_empty_state_view_new (void)
{
return g_object_new (SP_TYPE_EMPTY_STATE_VIEW, NULL);
}
static void
sp_empty_state_view_class_init (SpEmptyStateViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-empty-state-view.ui");
}
static void
sp_empty_state_view_init (SpEmptyStateView *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}

40
lib/sp-empty-state-view.h Normal file
View File

@ -0,0 +1,40 @@
/* sp-empty-state-view.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_EMPTY_STATE_VIEW_H
#define SP_EMPTY_STATE_VIEW_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_EMPTY_STATE_VIEW (sp_empty_state_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpEmptyStateView, sp_empty_state_view, SP, EMPTY_STATE_VIEW, GtkBin)
struct _SpEmptyStateViewClass
{
GtkBinClass parent;
};
GtkWidget *sp_empty_state_view_new (void);
G_END_DECLS
#endif /* SP_EMPTY_STATE_VIEW_H */

30
lib/sp-error.c Normal file
View File

@ -0,0 +1,30 @@
/* sp-error.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-error.h"
GQuark
sp_error_quark (void)
{
static GQuark quark;
if (G_UNLIKELY (!quark))
quark = g_quark_from_static_string ("sp-error-quark");
return quark;
}

32
lib/sp-error.h Normal file
View File

@ -0,0 +1,32 @@
/* sp-error.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_ERROR_H
#define SP_ERROR_H
#include <glib.h>
G_BEGIN_DECLS
#define SP_ERROR (sp_error_quark())
GQuark sp_error_quark (void);
G_END_DECLS
#endif /* SP_ERROR_H */

View File

@ -0,0 +1,42 @@
/* sp-failed-state-view.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-failed-state-view.h"
G_DEFINE_TYPE (SpFailedStateView, sp_failed_state_view, GTK_TYPE_BIN)
GtkWidget *
sp_failed_state_view_new (void)
{
return g_object_new (SP_TYPE_FAILED_STATE_VIEW, NULL);
}
static void
sp_failed_state_view_class_init (SpFailedStateViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/sysprof/ui/sp-failed-state-view.ui");
}
static void
sp_failed_state_view_init (SpFailedStateView *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}

View File

@ -0,0 +1,43 @@
/* sp-failed-state-view.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_FAILED_STATE_VIEW_H
#define SP_FAILED_STATE_VIEW_H
#include <gtk/gtk.h>
#include "sp-profiler.h"
G_BEGIN_DECLS
#define SP_TYPE_FAILED_STATE_VIEW (sp_failed_state_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpFailedStateView, sp_failed_state_view, SP, FAILED_STATE_VIEW, GtkBin)
struct _SpFailedStateViewClass
{
GtkBinClass parent;
};
GtkWidget *sp_failed_state_view_new (void);
void sp_failed_state_view_set_profiler (SpFailedStateView *self,
SpProfiler *profiler);
G_END_DECLS
#endif /* SP_FAILED_STATE_VIEW_H */

245
lib/sp-gjs-source.c Normal file
View File

@ -0,0 +1,245 @@
/* sp-gjs-source.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <string.h>
#include "sp-capture-reader.h"
#include "sp-gjs-source.h"
struct _SpGjsSource
{
GObject parent_instance;
SpCaptureWriter *writer;
GArray *pids;
GArray *enabled;
};
#define ENABLE_PROFILER 0x1
#define DISABLE_PROFILER 0x0
static void source_iface_init (SpSourceInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpGjsSource, sp_gjs_source, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
static void
sp_gjs_source_finalize (GObject *object)
{
SpGjsSource *self = (SpGjsSource *)object;
g_clear_pointer (&self->pids, g_array_unref);
g_clear_pointer (&self->enabled, g_array_unref);
g_clear_pointer (&self->writer, sp_capture_writer_unref);
G_OBJECT_CLASS (sp_gjs_source_parent_class)->finalize (object);
}
static void
sp_gjs_source_class_init (SpGjsSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_gjs_source_finalize;
}
static void
sp_gjs_source_init (SpGjsSource *self)
{
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
self->enabled = g_array_new (FALSE, FALSE, sizeof (GPid));
}
static void
sp_gjs_source_process_capture (SpGjsSource *self,
GPid pid,
const gchar *path)
{
g_autoptr(GError) error = NULL;
g_autoptr(SpCaptureReader) reader = NULL;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (self->writer != NULL);
g_assert (path != NULL);
if (!(reader = sp_capture_reader_new (path, &error)))
{
g_warning ("Failed to load capture: %s", error->message);
return;
}
if (!sp_capture_reader_splice (reader, self->writer, &error))
{
g_warning ("Failed to load capture: %s", error->message);
return;
}
}
static void
sp_gjs_source_process_captures (SpGjsSource *self)
{
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (self->writer != NULL);
for (i = 0; i < self->enabled->len; i++)
{
g_autofree gchar *filename = NULL;
g_autofree gchar *path = NULL;
GPid pid = g_array_index (self->enabled, GPid, i);
filename = g_strdup_printf ("gjs-profile-%u", (guint)pid);
path = g_build_filename (g_get_tmp_dir (), filename, NULL);
sp_gjs_source_process_capture (self, pid, path);
}
}
static void
sp_gjs_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpGjsSource *self = (SpGjsSource *)source;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (writer != NULL);
self->writer = sp_capture_writer_ref (writer);
}
static gboolean
pid_is_profileable (GPid pid)
{
g_autofree gchar *path = NULL;
g_autofree gchar *contents = NULL;
const gchar *libgjs;
gsize len = 0;
g_assert (pid != -1);
/*
* Make sure this process has linked in libgjs. No sense in sending it a
* signal unless we know it can handle it.
*/
path = g_strdup_printf ("/proc/%d/maps", pid);
if (!g_file_get_contents (path, &contents, &len, NULL))
return FALSE;
if (NULL == (libgjs = strstr (contents, "libgjs."G_MODULE_SUFFIX)))
return FALSE;
return TRUE;
}
static void
sp_gjs_source_enable_pid (SpGjsSource *self,
GPid pid)
{
union sigval si;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid != -1);
si.sival_int = ENABLE_PROFILER;
if (0 != sigqueue (pid, SIGUSR2, si))
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
else
g_array_append_val (self->enabled, pid);
}
static void
sp_gjs_source_disable_pid (SpGjsSource *self,
GPid pid)
{
union sigval si;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid != -1);
si.sival_int = DISABLE_PROFILER;
if (0 != sigqueue (pid, SIGUSR2, si))
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
}
static void
sp_gjs_source_start (SpSource *source)
{
SpGjsSource *self = (SpGjsSource *)source;
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
for (i = 0; i < self->pids->len; i++)
{
GPid pid = g_array_index (self->pids, GPid, i);
if (pid_is_profileable (pid))
sp_gjs_source_enable_pid (self, pid);
}
}
static void
sp_gjs_source_stop (SpSource *source)
{
SpGjsSource *self = (SpGjsSource *)source;
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
for (i = 0; i < self->pids->len; i++)
{
GPid pid = g_array_index (self->pids, GPid, i);
if (pid_is_profileable (pid))
sp_gjs_source_disable_pid (self, pid);
}
sp_gjs_source_process_captures (self);
}
static void
sp_gjs_source_add_pid (SpSource *source,
GPid pid)
{
SpGjsSource *self = (SpGjsSource *)source;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid > -1);
g_array_append_val (self->pids, pid);
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->set_writer = sp_gjs_source_set_writer;
iface->start = sp_gjs_source_start;
iface->stop = sp_gjs_source_stop;
iface->add_pid = sp_gjs_source_add_pid;
}
SpSource *
sp_gjs_source_new (void)
{
return g_object_new (SP_TYPE_GJS_SOURCE, NULL);
}

34
lib/sp-gjs-source.h Normal file
View File

@ -0,0 +1,34 @@
/* sp-gjs-source.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_GJS_SOURCE_H
#define SP_GJS_SOURCE_H
#include "sp-source.h"
G_BEGIN_DECLS
#define SP_TYPE_GJS_SOURCE (sp_gjs_source_get_type())
G_DECLARE_FINAL_TYPE (SpGjsSource, sp_gjs_source, SP, GJS_SOURCE, GObject)
SpSource *sp_gjs_source_new (void);
G_END_DECLS
#endif /* SP_GJS_SOURCE_H */

View File

@ -0,0 +1,121 @@
/* sp-jitmap-symbol-resolver.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-kernel-symbol.h"
#include "sp-jitmap-symbol-resolver.h"
struct _SpJitmapSymbolResolver
{
GObject parent_instance;
GHashTable *jitmap;
};
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpJitmapSymbolResolver,
sp_jitmap_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
sp_jitmap_symbol_resolver_finalize (GObject *object)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)object;
g_clear_pointer (&self->jitmap, g_hash_table_unref);
G_OBJECT_CLASS (sp_jitmap_symbol_resolver_parent_class)->finalize (object);
}
static void
sp_jitmap_symbol_resolver_class_init (SpJitmapSymbolResolverClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_jitmap_symbol_resolver_finalize;
}
static void
sp_jitmap_symbol_resolver_init (SpJitmapSymbolResolver *self)
{
self->jitmap = g_hash_table_new_full (NULL, NULL, NULL, g_free);
}
static void
sp_jitmap_symbol_resolver_load (SpSymbolResolver *resolver,
SpCaptureReader *reader)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
SpCaptureFrameType type;
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
g_assert (reader != NULL);
while (sp_capture_reader_peek_type (reader, &type))
{
g_autoptr(GHashTable) jitmap = NULL;
GHashTableIter iter;
SpCaptureAddress addr;
const gchar *str;
if (type != SP_CAPTURE_FRAME_JITMAP)
{
if (!sp_capture_reader_skip (reader))
return;
continue;
}
if (!(jitmap = sp_capture_reader_read_jitmap (reader)))
return;
g_hash_table_iter_init (&iter, jitmap);
while (g_hash_table_iter_next (&iter, (gpointer *)&addr, (gpointer *)&str))
g_hash_table_insert (self->jitmap, GSIZE_TO_POINTER (addr), g_strdup (str));
}
}
static gchar *
sp_jitmap_symbol_resolver_resolve (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpCaptureAddress address,
GQuark *tag)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
*tag = 0;
return g_strdup (g_hash_table_lookup (self->jitmap, GSIZE_TO_POINTER (address)));
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->load = sp_jitmap_symbol_resolver_load;
iface->resolve = sp_jitmap_symbol_resolver_resolve;
}
SpSymbolResolver *
sp_jitmap_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_JITMAP_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-jitmap-symbol-resolver.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_JITMAP_SYMBOL_RESOLVER_H
#define SP_JITMAP_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_JITMAP_SYMBOL_RESOLVER (sp_jitmap_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpJitmapSymbolResolver, sp_jitmap_symbol_resolver, SP, JITMAP_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_jitmap_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_JITMAP_SYMBOL_RESOLVER_H */

View File

@ -0,0 +1,79 @@
/* sp-kernel-symbol-resolver.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-kernel-symbol.h"
#include "sp-kernel-symbol-resolver.h"
struct _SpKernelSymbolResolver
{
GObject parent_instance;
};
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpKernelSymbolResolver,
sp_kernel_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static GQuark linux_quark;
static void
sp_kernel_symbol_resolver_class_init (SpKernelSymbolResolverClass *klass)
{
linux_quark = g_quark_from_static_string ("Linux");
}
static void
sp_kernel_symbol_resolver_init (SpKernelSymbolResolver *skernel)
{
}
static gchar *
sp_kernel_symbol_resolver_resolve (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpCaptureAddress address,
GQuark *tag)
{
const SpKernelSymbol *sym;
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
if (NULL != (sym = sp_kernel_symbol_from_address (address)))
{
*tag = linux_quark;
return g_strdup (sym->name);
}
return NULL;
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->resolve = sp_kernel_symbol_resolver_resolve;
}
SpSymbolResolver *
sp_kernel_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_KERNEL_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-kernel-symbol-resolver.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_KERNEL_SYMBOL_RESOLVER_H
#define SP_KERNEL_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_KERNEL_SYMBOL_RESOLVER (sp_kernel_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpKernelSymbolResolver, sp_kernel_symbol_resolver, SP, KERNEL_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_kernel_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_KERNEL_SYMBOL_RESOLVER_H */

205
lib/sp-kernel-symbol.c Normal file
View File

@ -0,0 +1,205 @@
/* sp-kernel-symbol.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-line-reader.h"
#include "sp-kernel-symbol.h"
static GArray *kernel_symbols;
static const gchar *kernel_symbols_skip[] = {
/* IRQ stack */
"common_interrupt",
"apic_timer_interrupt",
"smp_apic_timer_interrupt",
"hrtimer_interrupt",
"__run_hrtimer",
"perf_swevent_hrtimer",
"perf_event_overflow",
"__perf_event_overflow",
"perf_prepare_sample",
"perf_callchain",
"perf_swcounter_hrtimer",
"perf_counter_overflow",
"__perf_counter_overflow",
"perf_counter_output",
/* NMI stack */
"nmi_stack_correct",
"do_nmi",
"notify_die",
"atomic_notifier_call_chain",
"notifier_call_chain",
"perf_event_nmi_handler",
"perf_counter_nmi_handler",
"intel_pmu_handle_irq",
"perf_event_overflow",
"perf_counter_overflow",
"__perf_event_overflow",
"perf_prepare_sample",
"perf_callchain",
NULL
};
static gint
sp_kernel_symbol_compare (gconstpointer a,
gconstpointer b)
{
const SpKernelSymbol *syma = a;
const SpKernelSymbol *symb = b;
if (syma->address > symb->address)
return 1;
else if (syma->address == symb->address)
return 0;
else
return -1;
}
static gboolean
sp_kernel_symbol_load (void)
{
g_autofree gchar *contents = NULL;
g_autoptr(GArray) ar = NULL;
g_autoptr(GHashTable) skip = NULL;
g_autoptr(SpLineReader) reader = NULL;
const gchar *line;
gsize len;
guint i;
skip = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0; kernel_symbols_skip [i]; i++)
g_hash_table_insert (skip, (gchar *)kernel_symbols_skip [i], NULL);
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
if (!g_file_get_contents ("/proc/kallsyms", &contents, &len, NULL))
{
g_warning ("/proc/kallsyms is missing, kernel symbols will not be available");
return FALSE;
}
reader = sp_line_reader_new (contents, len);
while (NULL != (line = sp_line_reader_next (reader, &len)))
{
gchar **tokens;
((gchar *)line) [len] = '\0';
tokens = g_strsplit_set (line, " \t", -1);
if (tokens [0] && tokens [1] && tokens [2])
{
SpCaptureAddress address;
gchar *endptr;
if (g_hash_table_contains (skip, tokens [2]))
continue;
address = g_ascii_strtoull (tokens [0], &endptr, 16);
if (*endptr == '\0' &&
(g_str_equal (tokens [1], "T") || g_str_equal (tokens [1], "t")))
{
SpKernelSymbol sym;
sym.address = address;
sym.name = g_steal_pointer (&tokens [2]);
g_array_append_val (ar, sym);
}
}
g_strfreev (tokens);
}
if (ar->len == 0)
return FALSE;
g_array_sort (ar, sp_kernel_symbol_compare);
kernel_symbols = g_steal_pointer (&ar);
return TRUE;
}
static const SpKernelSymbol *
sp_kernel_symbol_lookup (SpKernelSymbol *symbols,
SpCaptureAddress address,
guint first,
guint last)
{
if (address >= symbols [last].address)
{
return &symbols [last];
}
else if (last - first < 3)
{
while (last >= first)
{
if (address >= symbols[last].address)
return &symbols [last];
last--;
}
return NULL;
}
else
{
int mid = (first + last) / 2;
if (symbols [mid].address > address)
return sp_kernel_symbol_lookup (symbols, address, first, mid);
else
return sp_kernel_symbol_lookup (symbols, address, mid, last);
}
}
/**
* sp_kernel_symbol_from_address:
* @address: the address of the instruction pointer
*
* Locates the kernel symbol that contains @address.
*
* Returns: (transfer none): An #SpKernelSymbol or %NULL.
*/
const SpKernelSymbol *
sp_kernel_symbol_from_address (SpCaptureAddress address)
{
const SpKernelSymbol *first;
if (G_UNLIKELY (kernel_symbols == NULL))
{
if (!sp_kernel_symbol_load ())
return NULL;
}
g_assert (kernel_symbols != NULL);
g_assert (kernel_symbols->len > 0);
/* Short circuit if this is out of range */
first = &g_array_index (kernel_symbols, SpKernelSymbol, 0);
if (address < first->address)
return NULL;
return sp_kernel_symbol_lookup ((SpKernelSymbol *)kernel_symbols->data,
address,
0,
kernel_symbols->len - 1);
}

36
lib/sp-kernel-symbol.h Normal file
View File

@ -0,0 +1,36 @@
/* sp-kernel-symbol.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_KERNEL_SYMBOL_H
#define SP_KERNEL_SYMBOL_H
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct
{
SpCaptureAddress address;
const gchar *name;
} SpKernelSymbol;
const SpKernelSymbol *sp_kernel_symbol_from_address (SpCaptureAddress address);
G_END_DECLS
#endif /* SP_KERNEL_SYMBOL_H */

124
lib/sp-line-reader.c Normal file
View File

@ -0,0 +1,124 @@
/* sp-line-reader.c
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "sp-line-reader.h"
struct _SpLineReader
{
const gchar *contents;
gsize length;
gsize pos;
};
void
sp_line_reader_free (SpLineReader *self)
{
g_slice_free (SpLineReader, self);
}
/**
* sp_line_reader_new:
* @contents: The buffer to read lines from
* @length: the length of @buffer in bytes
*
* Creates a new #SpLineReader for the contents provided. @contents are not
* copied and therefore it is a programming error to free contents before
* freeing the #SpLineReader structure.
*
* Use sp_line_reader_next() to read through the lines of the buffer.
*
* Returns: (transfer full): A new #SpLineReader that should be freed with
* sp_line_reader_free() when no longer in use.
*/
SpLineReader *
sp_line_reader_new (const gchar *contents,
gssize length)
{
SpLineReader *self;
g_return_val_if_fail (contents != NULL, NULL);
self = g_slice_new (SpLineReader);
if (length < 0)
length = strlen (contents);
if (contents != NULL)
{
self->contents = contents;
self->length = length;
self->pos = 0;
}
else
{
self->contents = NULL;
self->length = 0;
self->pos = 0;
}
return self;
}
/**
* sp_line_reader_next:
* @self: the #SpLineReader
* @length: a location for the length of the line in bytes
*
* Moves forward to the beginning of the next line in the buffer. No changes to
* the buffer are made, and the result is a pointer within the string passed as
* @contents in sp_line_reader_init(). Since the line most likely will not be
* terminated with a NULL byte, you must provide @length to determine the
* length of the line.
*
* Using "line[length]" will place you on the \n that was found for the line.
* However, to perform this safely, you need to know that your string was
* either \0 terminated to begin with, or that your buffer provides enough space
* to guarantee you can dereference past the last "textual content" of the
* buffer.
*
* Returns: (nullable) (transfer none): The beginning of the line within the buffer
*/
const gchar *
sp_line_reader_next (SpLineReader *self,
gsize *length)
{
const gchar *ret;
const gchar *endptr;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (length != NULL, NULL);
if ((self->contents == NULL) || (self->pos >= self->length))
{
*length = 0;
return NULL;
}
ret = &self->contents [self->pos];
endptr = memchr (ret, '\n', self->length - self->pos);
if (G_UNLIKELY (endptr == NULL))
endptr = &self->contents [self->length];
*length = (endptr - ret);
self->pos += *length + 1;
return ret;
}

38
lib/sp-line-reader.h Normal file
View File

@ -0,0 +1,38 @@
/* sp-line-reader.h
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_LINE_READER_H
#define SP_LINE_READER_H
#include <glib.h>
G_BEGIN_DECLS
typedef struct _SpLineReader SpLineReader;
SpLineReader *sp_line_reader_new (const gchar *contents,
gssize length);
void sp_line_reader_free (SpLineReader *self);
const gchar *sp_line_reader_next (SpLineReader *self,
gsize *length);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpLineReader, sp_line_reader_free)
G_END_DECLS
#endif /* SP_LINE_READER_H */

119
lib/sp-map-lookaside.c Normal file
View File

@ -0,0 +1,119 @@
/* sp-map-lookaside.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-map-lookaside.h"
struct _SpMapLookaside
{
GSequence *seq;
GStringChunk *chunk;
};
static gint
sp_map_compare (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
const SpMap *map_a = a;
const SpMap *map_b = b;
return sp_capture_address_compare (map_a->start, map_b->start);
}
static gint
sp_map_compare_in_range (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
const SpMap *map_a = a;
const SpMap *map_b = b;
/*
* map_b is the needle for the search.
* Only map_b->start is set.
*/
if ((map_b->start >= map_a->start) && (map_b->start < map_a->end))
return 0;
return sp_capture_address_compare (map_a->start, map_b->start);
}
static void
sp_map_free (gpointer data)
{
SpMap *map = data;
g_slice_free (SpMap, map);
}
SpMapLookaside *
sp_map_lookaside_new (void)
{
SpMapLookaside *ret;
ret = g_slice_new (SpMapLookaside);
ret->seq = g_sequence_new (sp_map_free);
ret->chunk = g_string_chunk_new (4096);
return ret;
}
void
sp_map_lookaside_free (SpMapLookaside *self)
{
g_sequence_free (self->seq);
g_string_chunk_free (self->chunk);
g_slice_free (SpMapLookaside, self);
}
void
sp_map_lookaside_insert (SpMapLookaside *self,
const SpMap *map)
{
SpMap *copy;
g_assert (self != NULL);
g_assert (map != NULL);
copy = g_slice_new (SpMap);
copy->start = map->start;
copy->end = map->end;
copy->offset = map->offset;
copy->inode = map->inode;
copy->filename = g_string_chunk_insert_const (self->chunk, map->filename);
g_sequence_insert_sorted (self->seq, copy, sp_map_compare, NULL);
}
const SpMap *
sp_map_lookaside_lookup (SpMapLookaside *self,
SpCaptureAddress address)
{
SpMap map = { address };
GSequenceIter *iter;
g_assert (self != NULL);
iter = g_sequence_lookup (self->seq, &map, sp_map_compare_in_range, NULL);
if (iter != NULL)
return g_sequence_get (iter);
return NULL;
}

50
lib/sp-map-lookaside.h Normal file
View File

@ -0,0 +1,50 @@
/* sp-map-lookaside.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_MAP_LOOKASIDE_H
#define SP_MAP_LOOKASIDE_H
#include <glib.h>
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct _SpMapLookaside SpMapLookaside;
typedef struct
{
SpCaptureAddress start;
SpCaptureAddress end;
off_t offset;
ino_t inode;
const gchar *filename;
} SpMap;
SpMapLookaside *sp_map_lookaside_new (void);
void sp_map_lookaside_insert (SpMapLookaside *self,
const SpMap *map);
const SpMap *sp_map_lookaside_lookup (SpMapLookaside *self,
SpCaptureAddress address);
void sp_map_lookaside_free (SpMapLookaside *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpMapLookaside, sp_map_lookaside_free)
G_END_DECLS
#endif /* SP_MAP_LOOKASIDE_H */

472
lib/sp-model-filter.c Normal file
View File

@ -0,0 +1,472 @@
/* sp-model-filter.c
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "sp-model-filter.h"
/*
* This is a simple model filter for GListModel.
*
* Let me start by saying how it works, and then how I wish it worked.
*
* This uses 2 GSequence (Treaps) to track the filters. One matches the
* underlying listmodel. One matches the filtered set. We hold onto our
* own reference to the object in the child list model, and update things
* as necessary when ::items-changed is emitted.
*
* I'd rather see this solved in one of two ways.
*
* 1) Add filtering support to GListStore
*
* or
*
* 2) Create a multi-tree data-structure that contains two tree nodes
* in each element. One tree contains all items, one tree contains
* the visible items (a subset of the other tree). The nodes might
* look something like:
*
* Item {
* TreeNode all_tree;
* TreeNode visible_tree;
* GObject *item;
* }
*
* But either way, this gets the job done for now. I'd venture a guess
* that in many cases (small lists), this is actually slower than just
* rechecking a simple GPtrArray, but let's see how it goes.
*
* -- Christian
*/
typedef struct
{
GListModel *child_model;
GSequence *seq;
GSequence *visible_seq;
SpModelFilterFunc filter_func;
gpointer filter_func_data;
GDestroyNotify filter_func_data_destroy;
guint needs_rebuild : 1;
} SpModelFilterPrivate;
typedef struct
{
GSequenceIter *iter;
GObject *object;
} Element;
static void list_model_iface_init (GListModelInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpModelFilter, sp_model_filter, G_TYPE_OBJECT, 0,
G_ADD_PRIVATE (SpModelFilter)
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
list_model_iface_init))
enum {
PROP_0,
PROP_CHILD_MODEL,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
element_free (gpointer data)
{
Element *e = data;
g_clear_object (&e->object);
g_slice_free (Element, e);
}
static gboolean
sp_model_filter_default_filter_func (GObject *item,
gpointer user_data)
{
return TRUE;
}
static void
sp_model_filter_child_model_items_changed (SpModelFilter *self,
guint position,
guint n_removed,
guint n_added,
GListModel *child_model)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
GSequenceIter *insert_before = NULL;
GSequenceIter *insert_iter;
GSequenceIter *lower;
GSequenceIter *upper;
guint i;
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (G_IS_LIST_MODEL (child_model));
for (i = 0; i < n_removed; i++)
{
GSequenceIter *iter;
Element *ele;
iter = g_sequence_get_iter_at_pos (priv->seq, position);
ele = g_sequence_get (iter);
if (ele->iter)
{
guint visible_position = g_sequence_iter_get_position (ele->iter);
insert_before = g_sequence_iter_next (ele->iter);
g_sequence_remove (ele->iter);
g_list_model_items_changed (G_LIST_MODEL (self), visible_position, 1, 0);
}
g_sequence_remove (iter);
}
insert_iter = g_sequence_get_iter_at_pos (priv->seq, position + 1);
if (insert_before != NULL)
goto add_items;
#if GLIB_CHECK_VERSION(2, 48, 0)
if (g_sequence_is_empty (priv->visible_seq))
#else
if (g_sequence_get_begin_iter (priv->visible_seq) == g_sequence_get_end_iter (priv->visible_seq))
#endif
{
insert_before = g_sequence_get_end_iter (priv->visible_seq);
goto add_items;
}
lower = g_sequence_get_begin_iter (priv->visible_seq);
upper = g_sequence_get_end_iter (priv->visible_seq);
while (lower != upper)
{
GSequenceIter *mid;
GSequenceIter *iter;
guint mid_pos;
mid = g_sequence_range_get_midpoint (lower, upper);
iter = g_sequence_get (mid);
mid_pos = g_sequence_iter_get_position (iter);
if (mid_pos < position)
lower = g_sequence_iter_next (mid);
else if (mid_pos > position)
upper = g_sequence_iter_prev (mid);
else
upper = lower = mid;
}
if (upper == g_sequence_get_end_iter (priv->visible_seq))
insert_before = upper;
else
insert_before =
((guint)g_sequence_iter_get_position (g_sequence_get (upper)) <= position)
? upper : g_sequence_iter_next (upper);
add_items:
for (i = 0; i < n_added; i++)
{
GSequenceIter *iter;
Element *ele;
ele = g_slice_new (Element);
ele->object = g_list_model_get_item (priv->child_model, position + i);
ele->iter = NULL;
iter = g_sequence_insert_before (insert_iter, ele);
if (priv->filter_func (ele->object, priv->filter_func_data))
{
ele->iter = g_sequence_insert_before (insert_before, iter);
g_list_model_items_changed (G_LIST_MODEL (self),
g_sequence_iter_get_position (ele->iter),
0, 1);
}
}
}
static void
sp_model_filter_finalize (GObject *object)
{
SpModelFilter *self = (SpModelFilter *)object;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_clear_pointer (&priv->seq, g_sequence_free);
g_clear_pointer (&priv->visible_seq, g_sequence_free);
if (priv->filter_func_data_destroy)
{
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
priv->filter_func_data_destroy = NULL;
}
g_clear_object (&priv->child_model);
G_OBJECT_CLASS (sp_model_filter_parent_class)->finalize (object);
}
static void
sp_model_filter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpModelFilter *self = SP_MODEL_FILTER (object);
switch (prop_id)
{
case PROP_CHILD_MODEL:
g_value_set_object (value, sp_model_filter_get_child_model (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_model_filter_class_init (SpModelFilterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_model_filter_finalize;
object_class->get_property = sp_model_filter_get_property;
properties [PROP_CHILD_MODEL] =
g_param_spec_object ("child-model",
"Child Model",
"The child model being filtered.",
G_TYPE_LIST_MODEL,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_model_filter_init (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
priv->filter_func = sp_model_filter_default_filter_func;
priv->seq = g_sequence_new (element_free);
priv->visible_seq = g_sequence_new (NULL);
priv->needs_rebuild = TRUE;
}
static void
sp_model_filter_rebuild (SpModelFilter *self,
gboolean no_emit)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
guint new_n_items = 0;
guint old_n_items;
guint n_items;
guint i;
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (priv->needs_rebuild);
old_n_items = g_sequence_get_length (priv->visible_seq);
g_clear_pointer (&priv->seq, g_sequence_free);
g_clear_pointer (&priv->visible_seq, g_sequence_free);
priv->seq = g_sequence_new (element_free);
priv->visible_seq = g_sequence_new (NULL);
n_items = g_list_model_get_n_items (priv->child_model);
for (i = 0; i < n_items; i++)
{
GSequenceIter *iter;
Element *ele;
ele = g_slice_new (Element);
ele->object = g_list_model_get_item (priv->child_model, i);
ele->iter = NULL;
iter = g_sequence_append (priv->seq, ele);
if (priv->filter_func (ele->object, priv->filter_func_data))
{
ele->iter = g_sequence_append (priv->visible_seq, iter);
new_n_items++;
}
}
if (!no_emit)
g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items);
priv->needs_rebuild = FALSE;
}
static GType
sp_model_filter_get_item_type (GListModel *model)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_assert (SP_IS_MODEL_FILTER (self));
return g_list_model_get_item_type (priv->child_model);
}
static guint
sp_model_filter_get_n_items (GListModel *model)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_assert (SP_IS_MODEL_FILTER (self));
if (priv->needs_rebuild)
sp_model_filter_rebuild (self, TRUE);
return g_sequence_get_length (priv->visible_seq);
}
static gpointer
sp_model_filter_get_item (GListModel *model,
guint position)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
GSequenceIter *iter;
Element *ele;
g_assert (SP_IS_MODEL_FILTER (self));
if (priv->needs_rebuild)
sp_model_filter_rebuild (self, TRUE);
iter = g_sequence_get_iter_at_pos (priv->visible_seq, position);
if (!iter || g_sequence_iter_is_end (iter))
{
g_warning ("invalid position for filter, filter is corrupt");
return NULL;
}
iter = g_sequence_get (iter);
if (!iter || g_sequence_iter_is_end (iter))
{
g_warning ("invalid position for filter, filter is corrupt");
return NULL;
}
ele = g_sequence_get (iter);
return g_object_ref (ele->object);
}
static void
list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = sp_model_filter_get_item_type;
iface->get_n_items = sp_model_filter_get_n_items;
iface->get_item = sp_model_filter_get_item;
}
SpModelFilter *
sp_model_filter_new (GListModel *child_model)
{
SpModelFilter *ret;
SpModelFilterPrivate *priv;
g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL);
ret = g_object_new (SP_TYPE_MODEL_FILTER, NULL);
priv = sp_model_filter_get_instance_private (ret);
priv->child_model = g_object_ref (child_model);
g_signal_connect_object (child_model,
"items-changed",
G_CALLBACK (sp_model_filter_child_model_items_changed),
ret,
G_CONNECT_SWAPPED);
return ret;
}
/**
* sp_model_filter_get_child_model:
* @self: A #SpModelFilter
*
* Gets the child model that is being filtered.
*
* Returns: (transfer none): A #GListModel.
*/
GListModel *
sp_model_filter_get_child_model (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_return_val_if_fail (SP_IS_MODEL_FILTER (self), NULL);
return priv->child_model;
}
void
sp_model_filter_invalidate (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_return_if_fail (SP_IS_MODEL_FILTER (self));
priv->needs_rebuild = TRUE;
sp_model_filter_rebuild (self, FALSE);
}
void
sp_model_filter_set_filter_func (SpModelFilter *self,
SpModelFilterFunc filter_func,
gpointer filter_func_data,
GDestroyNotify filter_func_data_destroy)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_return_if_fail (SP_IS_MODEL_FILTER (self));
g_return_if_fail (filter_func || (!filter_func_data && !filter_func_data_destroy));
if (priv->filter_func_data_destroy)
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
if (filter_func != NULL)
{
priv->filter_func = filter_func;
priv->filter_func_data = filter_func_data;
priv->filter_func_data_destroy = filter_func_data_destroy;
}
else
{
priv->filter_func = sp_model_filter_default_filter_func;
priv->filter_func_data = NULL;
priv->filter_func_data_destroy = NULL;
}
sp_model_filter_invalidate (self);
}

48
lib/sp-model-filter.h Normal file
View File

@ -0,0 +1,48 @@
/* sp-model-filter.h
*
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_MODEL_FILTER_H
#define SP_MODEL_FILTER_H
#include <gio/gio.h>
G_BEGIN_DECLS
#define SP_TYPE_MODEL_FILTER (sp_model_filter_get_type())
typedef gboolean (*SpModelFilterFunc) (GObject *object,
gpointer user_data);
G_DECLARE_DERIVABLE_TYPE (SpModelFilter, sp_model_filter, SP, MODEL_FILTER, GObject)
struct _SpModelFilterClass
{
GObjectClass parent_class;
};
SpModelFilter *sp_model_filter_new (GListModel *child_model);
GListModel *sp_model_filter_get_child_model (SpModelFilter *self);
void sp_model_filter_invalidate (SpModelFilter *self);
void sp_model_filter_set_filter_func (SpModelFilter *self,
SpModelFilterFunc filter_func,
gpointer filter_func_data,
GDestroyNotify filter_func_data_destroy);
G_END_DECLS
#endif /* SP_MODEL_FILTER_H */

671
lib/sp-perf-counter.c Normal file
View File

@ -0,0 +1,671 @@
/* sp-perf-counter.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <errno.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <polkit/polkit.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "sp-perf-counter.h"
#include "util.h"
/*
* Number of pages to map for the ring buffer. We map one additional buffer
* at the beginning for header information to communicate with perf.
*/
#define N_PAGES 32
/*
* This represents a stream coming to us from perf. All SpPerfCounterInfo
* share a single GSource used for watching incoming G_IO_IN requests.
* The map is the mmap() zone we are using as a ring buffer for communicating
* with perf. The rest is for managing the ring buffer.
*/
typedef struct
{
gint fd;
gpointer fdtag;
struct perf_event_mmap_page *map;
guint8 *data;
guint64 tail;
gint cpu;
guint in_callback : 1;
} SpPerfCounterInfo;
struct _SpPerfCounter
{
volatile gint ref_count;
/*
* If we are should currently be enabled. We allow calling
* multiple times and disabling when we reach zero.
*/
guint enabled;
/*
* Our main context and source for delivering callbacks.
*/
GMainContext *context;
GSource *source;
/*
* An array of SpPerfCounterInfo, indicating all of our open
* perf stream.s
*/
GPtrArray *info;
/*
* The callback to execute for every discovered perf event.
*/
SpPerfCounterCallback callback;
gpointer callback_data;
GDestroyNotify callback_data_destroy;
/*
* The number of samples we've recorded.
*/
guint64 n_samples;
};
typedef struct
{
GSource source;
SpPerfCounter *counter;
} PerfGSource;
G_DEFINE_BOXED_TYPE (SpPerfCounter,
sp_perf_counter,
(GBoxedCopyFunc)sp_perf_counter_ref,
(GBoxedFreeFunc)sp_perf_counter_unref)
static gboolean
perf_gsource_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
return callback ? callback (user_data) : G_SOURCE_CONTINUE;
}
static GSourceFuncs source_funcs = {
NULL, NULL, perf_gsource_dispatch, NULL
};
static void
sp_perf_counter_info_free (SpPerfCounterInfo *info)
{
if (info->map)
{
gsize map_size;
map_size = N_PAGES * getpagesize () + getpagesize ();
munmap (info->map, map_size);
info->map = NULL;
info->data = NULL;
}
if (info->fd != -1)
{
close (info->fd);
info->fd = 0;
}
g_slice_free (SpPerfCounterInfo, info);
}
static void
sp_perf_counter_finalize (SpPerfCounter *self)
{
guint i;
g_assert (self != NULL);
g_assert (self->ref_count == 0);
for (i = 0; i < self->info->len; i++)
{
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
if (info->fdtag)
g_source_remove_unix_fd (self->source, info->fdtag);
sp_perf_counter_info_free (info);
}
if (self->callback_data_destroy)
self->callback_data_destroy (self->callback_data);
g_clear_pointer (&self->source, g_source_destroy);
g_clear_pointer (&self->info, g_ptr_array_free);
g_clear_pointer (&self->context, g_main_context_unref);
g_slice_free (SpPerfCounter, self);
}
void
sp_perf_counter_unref (SpPerfCounter *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
sp_perf_counter_finalize (self);
}
SpPerfCounter *
sp_perf_counter_ref (SpPerfCounter *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (self->ref_count > 0, NULL);
g_atomic_int_inc (&self->ref_count);
return self;
}
static void
sp_perf_counter_flush (SpPerfCounter *self,
SpPerfCounterInfo *info)
{
guint64 head;
guint64 tail;
guint64 n_bytes = N_PAGES * getpagesize ();
guint64 mask = n_bytes - 1;
g_assert (self != NULL);
g_assert (info != NULL);
tail = info->tail;
head = info->map->data_head;
read_barrier ();
if (head < tail)
tail = head;
while ((head - tail) >= sizeof (struct perf_event_header))
{
g_autofree guint8 *free_me = NULL;
struct perf_event_header *header;
guint8 buffer[4096];
/* Note that:
*
* - perf events are a multiple of 64 bits
* - the perf event header is 64 bits
* - the data area is a multiple of 64 bits
*
* which means there will always be space for one header, which means we
* can safely dereference the size field.
*/
header = (struct perf_event_header *)(info->data + (tail & mask));
if (header->size > head - tail)
{
/* The kernel did not generate a complete event.
* I don't think that can happen, but we may as well
* be paranoid.
*/
break;
}
if (info->data + (tail & mask) + header->size > info->data + n_bytes)
{
gint n_before;
gint n_after;
guint8 *b;
if (header->size > sizeof buffer)
free_me = b = g_malloc (header->size);
else
b = buffer;
n_after = (tail & mask) + header->size - n_bytes;
n_before = header->size - n_after;
memcpy (b, info->data + (tail & mask), n_before);
memcpy (b + n_before, info->data, n_after);
header = (struct perf_event_header *)b;
}
if (header->type == PERF_RECORD_SAMPLE)
self->n_samples++;
if (self->callback != NULL)
{
info->in_callback = TRUE;
self->callback ((SpPerfCounterEvent *)header, info->cpu, self->callback_data);
info->in_callback = FALSE;
}
tail += header->size;
}
info->tail = tail;
info->map->data_tail = tail;
}
static gboolean
sp_perf_counter_dispatch (gpointer user_data)
{
SpPerfCounter *self = user_data;
guint i;
g_assert (self != NULL);
g_assert (self->info != NULL);
for (i = 0; i < self->info->len; i++)
{
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
sp_perf_counter_flush (self, info);
}
return G_SOURCE_CONTINUE;
}
static void
sp_perf_counter_enable_info (SpPerfCounter *self,
SpPerfCounterInfo *info)
{
g_assert (self != NULL);
g_assert (info != NULL);
if (0 != ioctl (info->fd, PERF_EVENT_IOC_ENABLE))
g_warning ("Failed to enable counters");
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_IN);
}
SpPerfCounter *
sp_perf_counter_new (GMainContext *context)
{
SpPerfCounter *ret;
if (context == NULL)
context = g_main_context_default ();
ret = g_slice_new0 (SpPerfCounter);
ret->ref_count = 1;
ret->info = g_ptr_array_new ();
ret->context = g_main_context_ref (context);
ret->source = g_source_new (&source_funcs, sizeof (PerfGSource));
((PerfGSource *)ret->source)->counter = ret;
g_source_set_callback (ret->source, sp_perf_counter_dispatch, ret, NULL);
g_source_set_name (ret->source, "[perf]");
g_source_attach (ret->source, context);
return ret;
}
void
sp_perf_counter_close (SpPerfCounter *self,
gint fd)
{
guint i;
g_return_if_fail (self != NULL);
g_return_if_fail (fd != -1);
for (i = 0; i < self->info->len; i++)
{
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
if (info->fd == fd)
{
g_ptr_array_remove_index (self->info, i);
if (self->source)
g_source_remove_unix_fd (self->source, info->fdtag);
sp_perf_counter_info_free (info);
return;
}
}
}
static void
sp_perf_counter_add_info (SpPerfCounter *self,
int fd,
int cpu)
{
SpPerfCounterInfo *info;
guint8 *map;
gsize map_size;
g_assert (self != NULL);
g_assert (fd != -1);
map_size = N_PAGES * getpagesize () + getpagesize ();
map = mmap (NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED)
{
close (fd);
return;
}
info = g_slice_new0 (SpPerfCounterInfo);
info->fd = fd;
info->map = (gpointer)map;
info->data = map + getpagesize ();
info->tail = 0;
info->cpu = cpu;
g_ptr_array_add (self->info, info);
info->fdtag = g_source_add_unix_fd (self->source, info->fd, G_IO_ERR);
if (self->enabled)
sp_perf_counter_enable_info (self, info);
}
void
sp_perf_counter_take_fd (SpPerfCounter *self,
int fd)
{
g_return_if_fail (self != NULL);
g_return_if_fail (fd > -1);
sp_perf_counter_add_info (self, fd, -1);
}
#ifdef ENABLE_SYSPROFD
static GDBusProxy *
get_proxy (void)
{
static GDBusProxy *proxy;
GDBusConnection *bus = NULL;
if (proxy != NULL)
return g_object_ref (proxy);
bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
if (bus == NULL)
return NULL;
proxy = g_dbus_proxy_new_sync (bus,
(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION),
NULL,
"org.gnome.Sysprof2",
"/org/gnome/Sysprof2",
"org.gnome.Sysprof2",
NULL, NULL);
if (proxy != NULL)
{
g_object_add_weak_pointer (G_OBJECT (proxy), (gpointer *)&proxy);
return g_object_ref (proxy);
}
return NULL;
}
static gboolean
authorize_proxy (GDBusProxy *proxy)
{
PolkitSubject *subject = NULL;
GPermission *permission = NULL;
GDBusConnection *conn;
const gchar *name;
g_assert (G_IS_DBUS_PROXY (proxy));
conn = g_dbus_proxy_get_connection (proxy);
if (conn == NULL)
goto failure;
name = g_dbus_connection_get_unique_name (conn);
if (name == NULL)
goto failure;
subject = polkit_system_bus_name_new (name);
if (subject == NULL)
goto failure;
permission = polkit_permission_new_sync ("org.gnome.sysprof2.perf-event-open", subject, NULL, NULL);
if (permission == NULL)
goto failure;
if (!g_permission_acquire (permission, NULL, NULL))
goto failure;
return TRUE;
failure:
g_clear_object (&subject);
g_clear_object (&permission);
return FALSE;
}
static GDBusProxy *
get_authorized_proxy (void)
{
g_autoptr(GDBusProxy) proxy = NULL;
proxy = get_proxy ();
if (proxy != NULL && authorize_proxy (proxy))
return g_steal_pointer (&proxy);
return NULL;
}
#endif
gint
sp_perf_counter_open (SpPerfCounter *self,
struct perf_event_attr *attr,
GPid pid,
gint cpu,
gint group_fd,
gulong flags)
{
#ifdef ENABLE_SYSPROFD
g_autoptr(GError) error = NULL;
g_autoptr(GDBusProxy) proxy = NULL;
g_autoptr(GUnixFDList) fdlist = NULL;
g_autoptr(GVariant) res = NULL;
g_autoptr(GVariant) params = NULL;
gint handle = -1;
#endif
gint ret = -1;
g_return_val_if_fail (self != NULL, -1);
g_return_val_if_fail (attr != NULL, -1);
/*
* First, we try to run the syscall locally, since we should avoid the
* polkit request unless we have to use it for elevated privileges.
*/
if (-1 != (ret = syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags)))
{
sp_perf_counter_take_fd (self, ret);
return ret;
}
#ifdef ENABLE_SYSPROFD
params = g_variant_new_parsed (
"("
"["
"{'comm', <%b>},"
"{'clockid', <%i>},"
"{'config', <%t>},"
"{'disabled', <%b>},"
"{'exclude_idle', <%b>},"
"{'mmap', <%b>},"
"{'wakeup_events', <%u>},"
"{'sample_period', <%t>},"
"{'sample_type', <%t>},"
"{'task', <%b>},"
"{'type', <%u>},"
"{'use_clockid', <%b>}"
"],"
"%i,"
"%i,"
"%t"
")",
(gboolean)!!attr->comm,
(gint32)attr->clockid,
(guint64)attr->config,
(gboolean)!!attr->disabled,
(gboolean)!!attr->exclude_idle,
(gboolean)!!attr->mmap,
(guint32)attr->wakeup_events,
(guint64)attr->sample_period,
(guint64)attr->sample_type,
(gboolean)!!attr->task,
(guint32)attr->type,
(gboolean)!!attr->use_clockid,
(gint32)pid,
(gint32)cpu,
(guint64)flags);
params = g_variant_ref_sink (params);
if (NULL == (proxy = get_authorized_proxy ()))
{
errno = EPERM;
return -1;
}
res = g_dbus_proxy_call_with_unix_fd_list_sync (proxy,
"PerfEventOpen",
params,
G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
60000,
NULL,
&fdlist,
NULL,
&error);
if (res == NULL)
{
g_autofree gchar *str = g_variant_print (params, TRUE);
g_warning ("PerfEventOpen: %s: %s", error->message, str);
return -1;
}
if (!g_variant_is_of_type (res, (const GVariantType *)"(h)"))
{
g_warning ("Received something other than a handle");
return -1;
}
if (fdlist == NULL)
{
g_warning ("Failed to receive fdlist");
return -1;
}
g_variant_get (res, "(h)", &handle);
if (-1 == (ret = g_unix_fd_list_get (fdlist, handle, &error)))
{
g_warning ("%s", error->message);
return -1;
}
sp_perf_counter_take_fd (self, ret);
#endif
return ret;
}
void
sp_perf_counter_set_callback (SpPerfCounter *self,
SpPerfCounterCallback callback,
gpointer callback_data,
GDestroyNotify callback_data_destroy)
{
g_return_if_fail (self != NULL);
if (self->callback_data_destroy)
self->callback_data_destroy (self->callback_data);
self->callback = callback;
self->callback_data = callback_data;
self->callback_data_destroy = callback_data_destroy;
}
void
sp_perf_counter_enable (SpPerfCounter *self)
{
g_return_if_fail (self != NULL);
if (g_atomic_int_add (&self->enabled, 1) == 0)
{
guint i;
for (i = 0; i < self->info->len; i++)
{
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
sp_perf_counter_enable_info (self, info);
}
}
}
void
sp_perf_counter_disable (SpPerfCounter *self)
{
g_return_if_fail (self != NULL);
if (g_atomic_int_dec_and_test (&self->enabled))
{
guint i;
for (i = 0; i < self->info->len; i++)
{
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
if (0 != ioctl (info->fd, PERF_EVENT_IOC_DISABLE))
g_warning ("Failed to disable counters");
if (!info->in_callback)
sp_perf_counter_flush (self, info);
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_ERR);
}
}
}

138
lib/sp-perf-counter.h Normal file
View File

@ -0,0 +1,138 @@
/* sp-perf-counter.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_PERF_COUNTER_H
#define SP_PERF_COUNTER_H
#include <glib-object.h>
#include <linux/perf_event.h>
G_BEGIN_DECLS
#define SP_TYPE_PERF_COUNTER (sp_perf_counter_get_type())
typedef struct _SpPerfCounter SpPerfCounter;
#pragma pack(push, 1)
typedef struct
{
/*
* These fields are available as the suffix only because we have specified
* them when creating attributes. Be careful about using them.
* Ideally, we would probably switch from using structures overlaid with
* casts to a reader design, which knows about the attributes.
*/
guint32 pid, tid;
guint64 time;
} SpPerfCounterSuffix;
typedef struct
{
struct perf_event_header header;
guint32 pid;
guint32 ppid;
guint32 tid;
guint32 ptid;
guint64 time;
} SpPerfCounterEventFork;
typedef struct
{
struct perf_event_header header;
guint32 pid;
guint32 tid;
gchar comm[0];
} SpPerfCounterEventComm;
typedef struct
{
struct perf_event_header header;
guint32 pid;
guint32 ppid;
guint32 tid;
guint32 ptid;
guint64 time;
} SpPerfCounterEventExit;
typedef struct
{
struct perf_event_header header;
guint32 pid;
guint32 tid;
guint64 addr;
guint64 len;
guint64 pgoff;
char filename[0];
} SpPerfCounterEventMmap;
typedef struct
{
struct perf_event_header header;
guint64 ip;
guint32 pid;
guint32 tid;
guint64 time;
guint64 n_ips;
guint64 ips[0];
} SpPerfCounterEventSample;
typedef union
{
struct perf_event_header header;
guint8 raw[0];
SpPerfCounterEventFork fork;
SpPerfCounterEventComm comm;
SpPerfCounterEventExit exit;
SpPerfCounterEventMmap mmap;
SpPerfCounterEventSample sample;
} SpPerfCounterEvent;
#pragma pack(pop)
typedef void (*SpPerfCounterCallback) (SpPerfCounterEvent *event,
guint cpu,
gpointer user_data);
GType sp_perf_counter_get_type (void);
SpPerfCounter *sp_perf_counter_new (GMainContext *context);
void sp_perf_counter_set_callback (SpPerfCounter *self,
SpPerfCounterCallback callback,
gpointer callback_data,
GDestroyNotify callback_data_destroy);
void sp_perf_counter_add_pid (SpPerfCounter *self,
GPid pid);
gint sp_perf_counter_open (SpPerfCounter *self,
struct perf_event_attr *attr,
GPid pid,
gint cpu,
gint group_fd,
gulong flags);
void sp_perf_counter_take_fd (SpPerfCounter *self,
int fd);
void sp_perf_counter_enable (SpPerfCounter *self);
void sp_perf_counter_disable (SpPerfCounter *self);
void sp_perf_counter_close (SpPerfCounter *self,
gint fd);
SpPerfCounter *sp_perf_counter_ref (SpPerfCounter *self);
void sp_perf_counter_unref (SpPerfCounter *self);
G_END_DECLS
#endif /* SP_PERF_COUNTER_H */

459
lib/sp-perf-source.c Normal file
View File

@ -0,0 +1,459 @@
/* sp-perf-source.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <errno.h>
#include <string.h>
#include "sp-clock.h"
#include "sp-perf-counter.h"
#include "sp-perf-source.h"
#define N_WAKEUP_EVENTS 149
struct _SpPerfSource
{
GObject parent_instance;
SpCaptureWriter *writer;
SpPerfCounter *counter;
GHashTable *pids;
guint running : 1;
};
static void source_iface_init (SpSourceInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpPerfSource, sp_perf_source, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
enum {
TARGET_EXITED,
N_SIGNALS
};
static guint signals [N_SIGNALS];
static void
sp_perf_source_real_target_exited (SpPerfSource *self)
{
g_assert (SP_IS_PERF_SOURCE (self));
sp_source_emit_finished (SP_SOURCE (self));
}
static void
sp_perf_source_finalize (GObject *object)
{
SpPerfSource *self = (SpPerfSource *)object;
g_clear_pointer (&self->writer, sp_capture_writer_unref);
g_clear_pointer (&self->counter, sp_perf_counter_unref);
g_clear_pointer (&self->pids, g_hash_table_unref);
G_OBJECT_CLASS (sp_perf_source_parent_class)->finalize (object);
}
static void
sp_perf_source_class_init (SpPerfSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_perf_source_finalize;
signals [TARGET_EXITED] =
g_signal_new_class_handler ("target-exited",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_CALLBACK (sp_perf_source_real_target_exited),
NULL, NULL, NULL, G_TYPE_NONE, 0);
}
static void
sp_perf_source_init (SpPerfSource *self)
{
self->pids = g_hash_table_new (NULL, NULL);
}
static gboolean
do_emit_exited (gpointer data)
{
g_autoptr(SpPerfSource) self = data;
g_signal_emit (self, signals [TARGET_EXITED], 0);
return G_SOURCE_REMOVE;
}
static void
sp_perf_source_handle_sample (SpPerfSource *self,
gint cpu,
const SpPerfCounterEventSample *sample)
{
const guint64 *ips;
gint n_ips;
guint64 trace[3];
g_assert (SP_IS_PERF_SOURCE (self));
g_assert (sample != NULL);
ips = sample->ips;
n_ips = sample->n_ips;
if (n_ips == 0)
{
if (sample->header.misc & PERF_RECORD_MISC_KERNEL)
{
trace[0] = PERF_CONTEXT_KERNEL;
trace[1] = sample->ip;
trace[2] = PERF_CONTEXT_USER;
ips = trace;
n_ips = 3;
}
else
{
trace[0] = PERF_CONTEXT_USER;
trace[1] = sample->ip;
ips = trace;
n_ips = 2;
}
}
sp_capture_writer_add_sample (self->writer,
sample->time,
cpu,
sample->pid,
ips,
n_ips);
}
static void
sp_perf_source_handle_event (SpPerfCounterEvent *event,
guint cpu,
gpointer user_data)
{
SpPerfSource *self = user_data;
SpPerfCounterSuffix *suffix;
g_assert (SP_IS_PERF_SOURCE (self));
g_assert (event != NULL);
switch (event->header.type)
{
case PERF_RECORD_COMM:
suffix = (SpPerfCounterSuffix *)event->raw
+ G_STRUCT_OFFSET (SpPerfCounterEventComm, comm)
+ strlen (event->comm.comm)
+ 1;
sp_capture_writer_add_process (self->writer,
suffix->time,
cpu,
event->comm.pid,
event->comm.comm);
break;
case PERF_RECORD_EXIT:
sp_capture_writer_add_exit (self->writer,
event->exit.time,
cpu,
event->exit.pid);
if (g_hash_table_contains (self->pids, GINT_TO_POINTER (event->exit.pid)))
{
g_hash_table_remove (self->pids, GINT_TO_POINTER (event->exit.pid));
if (self->running && (g_hash_table_size (self->pids) > 0))
{
self->running = FALSE;
sp_perf_counter_disable (self->counter);
g_timeout_add (0, do_emit_exited, g_object_ref (self));
}
}
break;
case PERF_RECORD_FORK:
sp_capture_writer_add_fork (self->writer,
event->fork.time,
cpu,
event->fork.ppid,
event->fork.pid);
/*
* TODO: We should add support for "follow fork" of the GPid if we are
* targetting it.
*/
break;
case PERF_RECORD_LOST:
break;
case PERF_RECORD_MMAP:
suffix = (SpPerfCounterSuffix *)event->raw
+ G_STRUCT_OFFSET (SpPerfCounterEventMmap, filename)
+ strlen (event->mmap.filename)
+ 1;
sp_capture_writer_add_map (self->writer,
suffix->time,
cpu,
event->mmap.pid,
event->mmap.addr,
event->mmap.addr + event->mmap.len,
event->mmap.pgoff,
0,
event->mmap.filename);
break;
case PERF_RECORD_READ:
break;
case PERF_RECORD_SAMPLE:
sp_perf_source_handle_sample (self, cpu, &event->sample);
break;
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
default:
break;
}
}
static gboolean
sp_perf_source_start_pid (SpPerfSource *self,
GPid pid,
GError **error)
{
struct perf_event_attr attr = { 0 };
gulong flags = 0;
gint ncpu = g_get_num_processors ();
gint cpu = 0;
gint fd;
g_assert (SP_IS_PERF_SOURCE (self));
attr.sample_type = PERF_SAMPLE_IP
| PERF_SAMPLE_TID
| PERF_SAMPLE_CALLCHAIN
| PERF_SAMPLE_TIME;
attr.wakeup_events = N_WAKEUP_EVENTS;
attr.disabled = TRUE;
attr.mmap = 1;
attr.comm = 1;
attr.task = 1;
attr.exclude_idle = 1;
attr.size = sizeof attr;
attr.clockid = sp_clock;
attr.use_clockid = 1;
if (pid != -1)
{
ncpu = 0;
cpu = -1;
}
for (; cpu < ncpu; cpu++)
{
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CPU_CYCLES;
attr.sample_period = 1200000;
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
if (fd == -1)
{
/*
* We might just not have access to hardware counters, so try to
* gracefully fallback to software counters.
*/
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_CPU_CLOCK;
attr.sample_period = 1000000;
errno = 0;
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
if (fd == -1)
{
if (errno == EPERM || errno == EACCES)
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_PERMISSION_DENIED,
_("Sysprof requires authorization to access your computers performance counters."));
else
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("An error occurred while attempting to access performance counters: %s"),
g_strerror (errno));
sp_source_stop (SP_SOURCE (self));
return FALSE;
}
}
}
return TRUE;
}
static void
sp_perf_source_start (SpSource *source)
{
SpPerfSource *self = (SpPerfSource *)source;
g_autoptr(GError) error = NULL;
g_assert (SP_IS_PERF_SOURCE (self));
self->counter = sp_perf_counter_new (NULL);
sp_perf_counter_set_callback (self->counter,
sp_perf_source_handle_event,
self, NULL);
if (g_hash_table_size (self->pids) > 0)
{
GHashTableIter iter;
gpointer key;
g_hash_table_iter_init (&iter, self->pids);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
GPid pid = GPOINTER_TO_INT (key);
if (!sp_perf_source_start_pid (self, pid, &error))
{
sp_source_emit_failed (source, error);
return;
}
}
}
else
{
if (!sp_perf_source_start_pid (self, -1, &error))
{
sp_source_emit_failed (source, error);
return;
}
}
self->running = TRUE;
sp_perf_counter_enable (self->counter);
sp_source_emit_ready (source);
}
static void
sp_perf_source_stop (SpSource *source)
{
SpPerfSource *self = (SpPerfSource *)source;
g_assert (SP_IS_PERF_SOURCE (self));
if (self->running)
{
self->running = FALSE;
sp_perf_counter_disable (self->counter);
}
g_clear_pointer (&self->counter, sp_perf_counter_unref);
sp_source_emit_finished (source);
}
static void
sp_perf_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpPerfSource *self = (SpPerfSource *)source;
g_assert (SP_IS_PERF_SOURCE (self));
g_assert (writer != NULL);
self->writer = sp_capture_writer_ref (writer);
}
static void
sp_perf_source_add_pid (SpSource *source,
GPid pid)
{
SpPerfSource *self = (SpPerfSource *)source;
g_return_if_fail (SP_IS_PERF_SOURCE (self));
g_return_if_fail (pid >= -1);
g_return_if_fail (self->writer == NULL);
g_hash_table_add (self->pids, GINT_TO_POINTER (pid));
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->start = sp_perf_source_start;
iface->stop = sp_perf_source_stop;
iface->set_writer = sp_perf_source_set_writer;
iface->add_pid = sp_perf_source_add_pid;
}
SpSource *
sp_perf_source_new (void)
{
return g_object_new (SP_TYPE_PERF_SOURCE, NULL);
}
void
sp_perf_source_set_target_pid (SpPerfSource *self,
GPid pid)
{
g_return_if_fail (SP_IS_PERF_SOURCE (self));
g_return_if_fail (pid >= -1);
if (pid == -1)
g_hash_table_remove_all (self->pids);
else
sp_perf_source_add_pid (SP_SOURCE (self), pid);
}

36
lib/sp-perf-source.h Normal file
View File

@ -0,0 +1,36 @@
/* sp-perf-source.h
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_PERF_SOURCE_H
#define SP_PERF_SOURCE_H
#include "sp-source.h"
G_BEGIN_DECLS
#define SP_TYPE_PERF_SOURCE (sp_perf_source_get_type())
G_DECLARE_FINAL_TYPE (SpPerfSource, sp_perf_source, SP, PERF_SOURCE, GObject)
SpSource *sp_perf_source_new (void);
void sp_perf_source_set_target_pid (SpPerfSource *self,
GPid pid);
G_END_DECLS
#endif /* SP_PERF_SOURCE_H */

347
lib/sp-proc-source.c Normal file
View File

@ -0,0 +1,347 @@
/* sp-proc-source.c
*
* Copyright (C) 2016 Christian Hergert <chergert@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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sp-proc-source.h"
struct _SpProcSource
{
GObject parent_instance;
SpCaptureWriter *writer;
GArray *pids;
};
static void source_iface_init (SpSourceInterface *iface);
static gchar **proc_readlines (const gchar *format,
...)
G_GNUC_PRINTF (1, 2);
G_DEFINE_TYPE_EXTENDED (SpProcSource, sp_proc_source, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
static gchar **
proc_readlines (const gchar *format,
...)
{
gchar **ret = NULL;
gchar *filename = NULL;
gchar *contents = NULL;
va_list args;
gsize len;
g_assert (format != NULL);
va_start (args, format);
filename = g_strdup_vprintf (format, args);
va_end (args);
if (g_file_get_contents (filename, &contents, &len, NULL))
ret = g_strsplit (contents, "\n", 0);
g_free (contents);
g_free (filename);
return ret;
}
gchar *
sp_proc_source_get_command_line (GPid pid,
gboolean *is_kernel)
{
gchar *ret;
gchar **lines;
if (is_kernel)
*is_kernel = FALSE;
/*
* Get the full command line from /proc/pid/cmdline.
*/
if (NULL != (lines = proc_readlines ("/proc/%d/cmdline", pid)))
{
if (lines [0] && lines [0][0])
{
ret = g_strdup (lines [0]);
g_strfreev (lines);
return ret;
}
g_strfreev (lines);
}
/*
* We are guessing this is a kernel process based on cmdline being null.
*/
if (is_kernel)
*is_kernel = TRUE;
/*
* Check the first line of /proc/pid/status for Name: foo
*/
if (NULL != (lines = proc_readlines ("/proc/%d/status", pid)))
{
if (lines [0] && g_str_has_prefix (lines [0], "Name:"))
{
ret = g_strstrip (g_strdup (lines [0] + 5));
g_strfreev (lines);
return ret;
}
g_strfreev (lines);
}
return NULL;
}
static void
sp_proc_source_populate_process (SpProcSource *self,
GPid pid)
{
gchar *cmdline;
g_assert (SP_IS_PROC_SOURCE (self));
g_assert (pid > 0);
if (NULL != (cmdline = sp_proc_source_get_command_line (pid, NULL)))
{
sp_capture_writer_add_process (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
pid,
cmdline);
g_free (cmdline);
}
}
static void
sp_proc_source_populate_maps (SpProcSource *self,
GPid pid)
{
g_auto(GStrv) lines = NULL;
guint i;
g_assert (SP_IS_PROC_SOURCE (self));
g_assert (pid > 0);
if (NULL == (lines = proc_readlines ("/proc/%d/maps", pid)))
return;
for (i = 0; lines [i] != NULL; i++)
{
gchar file[256];
gulong start;
gulong end;
gulong offset;
gulong inode;
gint r;
r = sscanf (lines [i],
"%lx-%lx %*15s %lx %*x:%*x %lu %255s",
&start, &end, &offset, &inode, file);
file [sizeof file - 1] = '\0';
if (r != 5)
continue;
if (strcmp ("[vdso]", file) == 0)
{
/*
* Søren Sandmann Pedersen says:
*
* For the vdso, the kernel reports 'offset' as the
* the same as the mapping addres. This doesn't make
* any sense to me, so we just zero it here. There
* is code in binfile.c (read_inode) that returns 0
* for [vdso].
*/
offset = 0;
inode = 0;
}
sp_capture_writer_add_map (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
pid,
start,
end,
offset,
inode,
file);
}
}
static void
sp_proc_source_populate (SpProcSource *self)
{
const gchar *name;
GDir *dir;
g_assert (SP_IS_PROC_SOURCE (self));
if (self->pids->len > 0)
{
guint i;
for (i = 0; i < self->pids->len; i++)
{
GPid pid = g_array_index (self->pids, GPid, i);
sp_proc_source_populate_process (self, pid);
sp_proc_source_populate_maps (self, pid);
}
return;
}
if (NULL == (dir = g_dir_open ("/proc", 0, NULL)))
return;
while (NULL != (name = g_dir_read_name (dir)))
{
GPid pid;
char *end;
pid = strtol (name, &end, 10);
if (pid <= 0 || *end != '\0')
continue;
sp_proc_source_populate_process (self, pid);
sp_proc_source_populate_maps (self, pid);
}
g_dir_close (dir);
}
static void
sp_proc_source_start (SpSource *source)
{
SpProcSource *self = (SpProcSource *)source;
g_assert (SP_IS_PROC_SOURCE (self));
g_assert (self->writer != NULL);
sp_proc_source_populate (self);
sp_source_emit_finished (source);
}
static void
sp_proc_source_stop (SpSource *source)
{
SpProcSource *self = (SpProcSource *)source;
g_assert (SP_IS_PROC_SOURCE (self));
g_clear_pointer (&self->writer, sp_capture_writer_unref);
}
static void
sp_proc_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpProcSource *self = (SpProcSource *)source;
g_assert (SP_IS_PROC_SOURCE (self));
g_assert (writer != NULL);
self->writer = sp_capture_writer_ref (writer);
}
static void
sp_proc_source_add_pid (SpSource *source,
GPid pid)
{
SpProcSource *self = (SpProcSource *)source;
guint i;
g_assert (SP_IS_PROC_SOURCE (self));
g_assert (pid > -1);
for (i = 0; i < self->pids->len; i++)
{
GPid ele = g_array_index (self->pids, GPid, i);
if (ele == pid)
return;
}
g_array_append_val (self->pids, pid);
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->set_writer = sp_proc_source_set_writer;
iface->start = sp_proc_source_start;
iface->stop = sp_proc_source_stop;
iface->add_pid = sp_proc_source_add_pid;
}
static void
sp_proc_source_finalize (GObject *object)
{
SpProcSource *self = (SpProcSource *)object;
g_clear_pointer (&self->writer, sp_capture_writer_unref);
g_clear_pointer (&self->pids, g_array_unref);
G_OBJECT_CLASS (sp_proc_source_parent_class)->finalize (object);
}
static void
sp_proc_source_class_init (SpProcSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_proc_source_finalize;
}
static void
sp_proc_source_init (SpProcSource *self)
{
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
}
SpSource *
sp_proc_source_new (void)
{
return g_object_new (SP_TYPE_PROC_SOURCE, NULL);
}

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