mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
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:
4
AUTHORS
4
AUTHORS
@ -1 +1,3 @@
|
||||
Søren Sandmann (sandmann@redhat.com)
|
||||
# Generated by Makefile. Do not edit.
|
||||
|
||||
Christian Hergert
|
||||
|
||||
340
COPYING.gpl-2
Normal file
340
COPYING.gpl-2
Normal 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
27
DESIGN.md
Normal 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.
|
||||
|
||||
147
Makefile.am
147
Makefile.am
@ -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
7
NEWS
@ -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
25
TODO
Normal 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.
|
||||
|
||||
40
autogen.sh
40
autogen.sh
@ -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
5
build-aux/tap-test
Executable 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
148
build-aux/test-driver
Executable 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='[0;31m' # Red.
|
||||
grn='[0;32m' # Green.
|
||||
lgn='[1;32m' # Light green.
|
||||
blu='[1;34m' # Blue.
|
||||
mgn='[0;35m' # Magenta.
|
||||
std='[m' # 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:
|
||||
188
configure.ac
188
configure.ac
@ -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
14
daemon/Makefile.am
Normal 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
188
daemon/sd-bus-helper.c
Normal 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
36
daemon/sd-bus-helper.h
Normal 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
347
daemon/sysprofd.c
Normal 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
86
data/Makefile.am
Normal 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
29
data/icons/Makefile.am
Normal 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
|
||||
16
data/org.gnome.Sysprof2.conf.in
Normal file
16
data/org.gnome.Sysprof2.conf.in
Normal 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>
|
||||
13
data/org.gnome.Sysprof2.desktop
Normal file
13
data/org.gnome.Sysprof2.desktop
Normal 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;
|
||||
5
data/org.gnome.Sysprof2.service.in
Normal file
5
data/org.gnome.Sysprof2.service.in
Normal file
@ -0,0 +1,5 @@
|
||||
[D-BUS Service]
|
||||
Name=org.gnome.Sysprof2
|
||||
Exec=@sysprofdprivdir@/sysprofd
|
||||
User=root
|
||||
SystemdService=sysprof2.service
|
||||
24
data/org.gnome.Sysprof2.xml
Normal file
24
data/org.gnome.Sysprof2.xml
Normal 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>
|
||||
34
data/org.gnome.sysprof2.gschema.xml
Normal file
34
data/org.gnome.sysprof2.gschema.xml
Normal 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>
|
||||
22
data/org.gnome.sysprof2.policy.in
Normal file
22
data/org.gnome.sysprof2.policy.in
Normal 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
7
data/sysprof-mime.xml
Normal 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
11
data/sysprof.pc.in
Normal 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
8
data/sysprof2.service.in
Normal file
@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Sysprof Daemon
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.gnome.Sysprof2
|
||||
ExecStart=@sysprofdprivdir@/sysprofd
|
||||
|
||||
333
git.mk
Normal file
333
git.mk
Normal 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
62
gresources.mk
Normal 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
18
help/C/index.page
Normal 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
57
help/C/introduction.page
Normal 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
12
help/C/legal.xml
Normal 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
140
help/C/profiling.page
Normal 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
18
help/Makefile.am
Normal 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
128
lib/Makefile.am
Normal 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
|
||||
530
lib/binfile.c
530
lib/binfile.c
@ -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);
|
||||
}
|
||||
}
|
||||
806
lib/collector.c
806
lib/collector.c
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
9595
lib/demangle.c
9595
lib/demangle.c
File diff suppressed because it is too large
Load Diff
814
lib/elfparser.c
814
lib/elfparser.c
@ -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
|
||||
*/
|
||||
@ -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;
|
||||
}
|
||||
@ -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__ */
|
||||
3363
lib/footreestore.c
3363
lib/footreestore.c
File diff suppressed because it is too large
Load Diff
@ -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__ */
|
||||
605
lib/profile.c
605
lib/profile.c
@ -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;
|
||||
}
|
||||
@ -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 */
|
||||
11
lib/resources/libsysprof.gresource.xml
Normal file
11
lib/resources/libsysprof.gresource.xml
Normal 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>
|
||||
198
lib/resources/ui/sp-callgraph-view.ui
Normal file
198
lib/resources/ui/sp-callgraph-view.ui
Normal 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>
|
||||
64
lib/resources/ui/sp-empty-state-view.ui
Normal file
64
lib/resources/ui/sp-empty-state-view.ui
Normal 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 <b>Record</b> 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>
|
||||
64
lib/resources/ui/sp-failed-state-view.ui
Normal file
64
lib/resources/ui/sp-failed-state-view.ui
Normal 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>
|
||||
41
lib/resources/ui/sp-process-model-row.ui
Normal file
41
lib/resources/ui/sp-process-model-row.ui
Normal 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>
|
||||
221
lib/resources/ui/sp-profiler-menu-button.ui
Normal file
221
lib/resources/ui/sp-profiler-menu-button.ui
Normal 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>
|
||||
64
lib/resources/ui/sp-recording-state-view.ui
Normal file
64
lib/resources/ui/sp-recording-state-view.ui
Normal 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 <a href="help:sysprof">sysprof-cli</a> 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>
|
||||
1180
lib/sfile.c
1180
lib/sfile.c
File diff suppressed because it is too large
Load Diff
169
lib/sfile.h
169
lib/sfile.h
@ -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);
|
||||
646
lib/sformat.c
646
lib/sformat.c
@ -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
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
@ -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
91
lib/sp-address.c
Normal 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
59
lib/sp-address.h
Normal 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 */
|
||||
31
lib/sp-callgraph-profile-private.h
Normal file
31
lib/sp-callgraph-profile-private.h
Normal 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
376
lib/sp-callgraph-profile.c
Normal 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));
|
||||
}
|
||||
36
lib/sp-callgraph-profile.h
Normal file
36
lib/sp-callgraph-profile.h
Normal 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
881
lib/sp-callgraph-view.c
Normal 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
44
lib/sp-callgraph-view.h
Normal 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
686
lib/sp-capture-reader.c
Normal 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
65
lib/sp-capture-reader.h
Normal 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
155
lib/sp-capture-types.h
Normal 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
954
lib/sp-capture-writer.c
Normal 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
108
lib/sp-capture-writer.h
Normal 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 */
|
||||
135
lib/sp-cell-renderer-percent.c
Normal file
135
lib/sp-cell-renderer-percent.c
Normal 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]);
|
||||
}
|
||||
}
|
||||
55
lib/sp-cell-renderer-percent.h
Normal file
55
lib/sp-cell-renderer-percent.h
Normal 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
51
lib/sp-clock.c
Normal 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
54
lib/sp-clock.h
Normal 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 */
|
||||
|
||||
276
lib/sp-elf-symbol-resolver.c
Normal file
276
lib/sp-elf-symbol-resolver.c
Normal 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);
|
||||
}
|
||||
34
lib/sp-elf-symbol-resolver.h
Normal file
34
lib/sp-elf-symbol-resolver.h
Normal 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
41
lib/sp-empty-state-view.c
Normal 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
40
lib/sp-empty-state-view.h
Normal 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
30
lib/sp-error.c
Normal 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
32
lib/sp-error.h
Normal 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 */
|
||||
42
lib/sp-failed-state-view.c
Normal file
42
lib/sp-failed-state-view.c
Normal 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));
|
||||
}
|
||||
43
lib/sp-failed-state-view.h
Normal file
43
lib/sp-failed-state-view.h
Normal 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
245
lib/sp-gjs-source.c
Normal 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
34
lib/sp-gjs-source.h
Normal 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 */
|
||||
121
lib/sp-jitmap-symbol-resolver.c
Normal file
121
lib/sp-jitmap-symbol-resolver.c
Normal 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);
|
||||
}
|
||||
34
lib/sp-jitmap-symbol-resolver.h
Normal file
34
lib/sp-jitmap-symbol-resolver.h
Normal 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 */
|
||||
79
lib/sp-kernel-symbol-resolver.c
Normal file
79
lib/sp-kernel-symbol-resolver.c
Normal 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);
|
||||
}
|
||||
34
lib/sp-kernel-symbol-resolver.h
Normal file
34
lib/sp-kernel-symbol-resolver.h
Normal 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
205
lib/sp-kernel-symbol.c
Normal 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
36
lib/sp-kernel-symbol.h
Normal 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
124
lib/sp-line-reader.c
Normal 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
38
lib/sp-line-reader.h
Normal 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
119
lib/sp-map-lookaside.c
Normal 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
50
lib/sp-map-lookaside.h
Normal 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
472
lib/sp-model-filter.c
Normal 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
48
lib/sp-model-filter.h
Normal 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
671
lib/sp-perf-counter.c
Normal 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
138
lib/sp-perf-counter.h
Normal 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
459
lib/sp-perf-source.c
Normal 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
36
lib/sp-perf-source.h
Normal 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
347
lib/sp-proc-source.c
Normal 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
Reference in New Issue
Block a user