diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | bd9e6617827818fd043452c08c606f07b78014a0 (patch) | |
tree | 425bb4c3168f9c02f10150f235d2cb998dcc6108 /kcachegrind | |
download | tdesdk-bd9e6617827818fd043452c08c606f07b78014a0.tar.gz tdesdk-bd9e6617827818fd043452c08c606f07b78014a0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdesdk@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kcachegrind')
125 files changed, 38941 insertions, 0 deletions
diff --git a/kcachegrind/AUTHORS b/kcachegrind/AUTHORS new file mode 100644 index 00000000..ded6005d --- /dev/null +++ b/kcachegrind/AUTHORS @@ -0,0 +1 @@ +Josef Weidendorfer <Josef.Weidendorfer@gmx.de> diff --git a/kcachegrind/COPYING b/kcachegrind/COPYING new file mode 100644 index 00000000..5b6e7c66 --- /dev/null +++ b/kcachegrind/COPYING @@ -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. diff --git a/kcachegrind/ChangeLog b/kcachegrind/ChangeLog new file mode 100644 index 00000000..b19b0fb8 --- /dev/null +++ b/kcachegrind/ChangeLog @@ -0,0 +1,89 @@ +2004/06/30 + * Leak fixes + * Crash fixes on reload (make setData() synchroneous) + * Some update fixes in the data model (tracedata.cpp) + * Fix update problems in Function Profile + * Reselect active function on refresh in function profile + with grouping on + +2004/04/28 + * toplevel.h/cpp, kcachegrindui.rc + - Switching Layouts + * multiview.cpp: Removed some qDebug's + * Same term fixes + +2004/04/26 + * cachegrindloader.cpp, fixcost.cpp: + - Allow Ranges in Subposition Spec, currently not used + - Correctly parse "Desc: Trigger:" + - Allow Event Spec (Long Name, Formula) with "event:" + * listutils.cpp: + - make level meters for costs only 1 bar + (2 with upper from 0..50%, lower 50%..100% is really confusing) + - Besides from Call graph and Tree maps, truncate bars to + only use needed size (removes lots of empty rectangles) + * CallGraphView: + - some fixes when no data is loaded + * functionselection.cpp (Function Profile) + - activation on mouse release to allow for context menu + * tracedata.cpp + - more robust parsing of events lists + - Introduction of Ranges (not currently used) + * utils.cpp: + - more robust parsing functions + +2004/04/05 + * CallGraphView: + - Add Context menu item "Export as Image" + - Hide Birdseye-View if call-graph fits into widget + - Error messages in Canvas when something goes wrong + * Some Fixes, qDebug->kdDebug + +2004/04/02 + * In most views columns for 2nd Event Type added + * Context menus modified to allow quick change of 2nd Event Type + * Toolbar simplified (only most used actions) + * Terminology fixes ("cost type"->"event type", + "trace data"->"profile data", long names of Ir,Dr,...) + * Sorting costs in lists is always descending now + * New File menu item: "Add..." other profile data to current window + * Detect Cachegrind format by "events:" content, not file name + Allows for arbitrary names of profile data files. + +2004/03/25 + * New Class Addr as wrapper for memory addresses. Use 64bit + to allow loading of data produced on 64bit architectures + +2004/03/17 + + * costtypeview.cpp, tracedata.h/cpp: + Fixed deletion of custom types + * cachegrindloader.cpp, tracedata.h/cpp: + Moved String compression handling in Cachegrind files + to CachegrindLoader + * Do not show inclusive cost column in FunctionSelection + side bar if not available + * Remove "isPartOfTrace" from Loader interface + (we allow parts from multiple experiments for comp.) + * partview.cpp, partlistitem.h/cpp: + Remove Column Callees, add Trigger + +2003/05/10 + + * Status progress on loading and cycle calculation + * Corrected order of trace parts (PID/PartNo/ThreadID) + * Allow adding traces (BUGGY...) + +2003/02/06 + + * Version 0.3a + * Bugfixes: + - Compiles with KDE 3.0.x + - Always select a first cost type + - Loading from another directory + + +2002/11/28 + + * Version 0.3 + diff --git a/kcachegrind/INSTALL b/kcachegrind/INSTALL new file mode 100644 index 00000000..02a4a074 --- /dev/null +++ b/kcachegrind/INSTALL @@ -0,0 +1,167 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Type `make install' to install the programs and any data files and + documentation. + + 4. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/kcachegrind/Makefile.am b/kcachegrind/Makefile.am new file mode 100644 index 00000000..595471f5 --- /dev/null +++ b/kcachegrind/Makefile.am @@ -0,0 +1,6 @@ +SUBDIRS = kcachegrind pics converters + +EXTRA_DIST = \ + AUTHORS COPYING NEWS ChangeLog INSTALL README TODO \ + kcachegrind.lsm kcachegrind.spec version.h + diff --git a/kcachegrind/NEWS b/kcachegrind/NEWS new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/kcachegrind/NEWS diff --git a/kcachegrind/README b/kcachegrind/README new file mode 100644 index 00000000..30fee51e --- /dev/null +++ b/kcachegrind/README @@ -0,0 +1,62 @@ +KCachegrind +=========== + + +What is all this about ? +------------------------- + +Profiling, i.e. determinating most time consuming execution parts, +is an important last step when developing applications. +KCachegrind visualizes traces, generated by profiling, in various ways; +most notable is the TreeMap visualization of the calls happening +and a condensed version of it, the Coverage analysis. +KCachegrind is designed to allow fast browsing and to provide a quick +overview of very large programs, such as KDE applications (but not +limited to!). + +At the moment, it uses Cachegrind as profiling backend, which is using +the excellent CPU simulator in Valgrind. Thus, profiling does not +need any preparation, can cope with shared libraries and plugin +architectures, and allows for profile runs to not influence the measuring +by the profile itself (all in contrast to e.g. GProf). Disadvantage is +slower profile runs, unfortunately. + +For Cachegrind to provide call tree information, a patch is provided. +This enables the most interesting visualization features of KCachegrind. + + +Requirements +------------ + +A call-tree version of Cachegrind: + - X86 Linux + - Valgrind 1.0.x with call-tree patch from KCachegrind Website + - Valgrind 2.0.x with call-tree skin installed + +Cachegrind runs on x86 platforms, KCachegrind on all KDE enabled +platforms (KDE 3.0.x). + + +Compilation and Installation +---------------------------- + +Simple do the command sequence + + ./configure --prefix=<KDE base directory> + make + make install + + + +KCachegrind features +-------------------- + +Most important: TreeMap calltree visualisation. +For the rest, see the detailed "What's this?" help for +each part of KCachegrind and the quick starter on the +WWW page ( http://kcachegrind.sourceforge.net/cgi-bin/show.cgi ) + + + +Happy Profiling, + Josef Weidendorfer diff --git a/kcachegrind/TODO b/kcachegrind/TODO new file mode 100644 index 00000000..1eca67ed --- /dev/null +++ b/kcachegrind/TODO @@ -0,0 +1,100 @@ +TODO/Wishlist Items +=================== + + +KCachegrind +----------- + +All cost Lists: +* Show up to a number of items, not down to a threadshold. + If more, add a "..." with number of items not shown, and context option + to show more +* "Copy from Top" converts lists into ASCII, puts into clipboard + + +Configuration: + Source dirs per ELF object + +Layout: +* 1/2/3/4 vertical/horizontal FunctionInfos + with Shift/Wraparound selection mode +* Inside each FunctionInfo different Layouts + - tabbed layout + - top: info, bottom left: calls/coverage, bottom right: graph/source +* Long/short info tab + +General: +* Selected Item can be a object/file/class/function/line +* Configuration Dlg + - Local config (?) + - Cost Types + - function colors + - Try to reload source after config. +* Session Management + + + +Annotation Views: + + BUGS: + * Draw problem with multiple srcs to one target + * REP case... + + TODO: + * Selectable Jumps (Arrows) + * Tooltip for Jumps (Kind, from/to, jump count) + * Show direction (arrows) on jump lines + + Source view TODO: + * Implicit jumps (green) [needs support from the tool?] + + + +Callgraph: +* Fix Arrows for back-arcs +* Less "Jumps" for minimap +* Correct Keyboard navigation (how?) + +Types: +* Ratios +* Automatic subtypes + +WISHS: +* Support for Data tracing + Which variables are touched how often from which function? + - Some graphical visualisation... + +* GCC -pg (gmon.out) as Profiling Backend +* Demangler (use c++filt) +* Calculation of call weights (if not given) +* OProfile, DynaProf + +Support for KCachegrind in Calltree +----------------------------------- + +WISHS: +- store more details of calltree + - for every function call: executed from shared lib + (Not needed, if function names are unique in whole app) + - adaptive call chain context (Really needed ? MUCH Data!) +- dump at + - breakpoints + - watchpoints (with data tracing!) + - every xxx BBs (DONE) +- dump around + - function invocation + - KAction event + - DCOP event + +- data accesses from (instr address/count) + stack: -> (function, stackframe-offset) + dynamic: -> (mem region start, [type], offset) + type can be get when a constructor is called for region + static: -> (mem region start, type, offset) + +* Generate full instr/data access trace for offline analysis. + +* Appending mode + + + diff --git a/kcachegrind/configure.in.in b/kcachegrind/configure.in.in new file mode 100644 index 00000000..22701fc8 --- /dev/null +++ b/kcachegrind/configure.in.in @@ -0,0 +1,8 @@ +KCACHEGRIND_VERSION=0.4.6kde +AC_SUBST(KCACHEGRIND_VERSION) + +AC_FUNC_MMAP + +dnl AC_OUTPUT( kcachegrind/version.h ) +dnl AC_OUTPUT( kcachegrind/kcachegrind.spec ) +dnl AC_OUTPUT( kcachegrind/kcachegrind.lsm ) diff --git a/kcachegrind/converters/Makefile.am b/kcachegrind/converters/Makefile.am new file mode 100644 index 00000000..08b3696b --- /dev/null +++ b/kcachegrind/converters/Makefile.am @@ -0,0 +1,2 @@ +bin_SCRIPTS = hotshot2calltree op2calltree pprof2calltree dprof2calltree \ + memprof2calltree diff --git a/kcachegrind/converters/README b/kcachegrind/converters/README new file mode 100644 index 00000000..6a1c46c5 --- /dev/null +++ b/kcachegrind/converters/README @@ -0,0 +1,24 @@ +This directory contains some scripts to convert output of different +profiling tools into the format which can be loaded by KCachegrind. +See the comment at start of every script for details. + +In the long run, these should be replaced by import filters in +KCachegrind directly, but I can't promise anything. Partly, this +is because some scripts are provided as contribution from others. + +hotshot2calltree Converter from Python Hotshot Profiler. +op2calltree Converter from OProfile sampling data. +dprof2calltree Converter from PERL::DProf Profiler. +pprof2calltree Converter from APD PHP Profiler. + +Thanks go to +* George Schlossnagle <george@omniti.com> for + dprof2calltree and pprof2calltree, +* Jrg Beyer <job@webde-ag.de> for + hotshot2calltree + +If you want to write a converter, have a look at the calltree format +description on the web site (kcachegrind.sf.net). + +Josef + diff --git a/kcachegrind/converters/dprof2calltree b/kcachegrind/converters/dprof2calltree new file mode 100644 index 00000000..940457c8 --- /dev/null +++ b/kcachegrind/converters/dprof2calltree @@ -0,0 +1,199 @@ +#!/usr/bin/perl +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# - All advertising materials mentioning features or use of this software +# must display the following acknowledgement: This product includes software +# developed by OmniTI Computer Consulting. +# +# - Neither name of the company nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Copyright (c) 2004 OmniTI Computer Consulting +# All rights reserved +# The following code was written by George Schlossnagle <george@omniti.com> +# and is provided completely free and without any warranty. +# + +# +# This script is designed to convert the tmon.out output emitted +# from Perl's Devel::DProf profiling package. To use this: +# +# 1) Run your perl script as +# > perl -d:DProf yoursript.pl +# This will create a file called tmon.out. If you want to +# inspect it on the command line, look at the man page +# for dprofp for details. +# +# 2) Run +# > dprof2calltree -f tmon.out +# or +# > dprof2calltree -f tmon.out -o cachegrind.out.foo +# +# This creates a cachegrind-style file called cachgrind.out.tmon.out or +# cachegrind.out.foo, respecitvely. +# +# 3) Run kcachegrind cachegrind.out.foo +# +# 4) Enjoy! + +use strict; +use Config; +use Getopt::Std; +use IO::File; + +my @callstack; +my %function_info; +my $tree = {}; +my $total_cost = 0; +my %opts; + +getopt('f:o:', \%opts); + +my $infd; +usage() unless ($opts{'f'} && ($infd = IO::File->new($opts{'f'}, "r"))); + +my $outfd; +my $outfile = $opts{'o'}; +unless($outfile) { + $opts{'f'} =~ m!([^/]+)$!; + $outfile = "cachegrind.out.$1"; +} +$outfd = new IO::File $outfile, "w"; +usage() unless defined $outfd; + +while(<$infd>) { + last if /^PART2/; +} +while(<$infd>) { + chomp; + my @args = split; + if($args[0] eq '@') { + # record timing event + my $call_element = pop @callstack; + if($call_element) { + $call_element->{'cost'} += $args[3]; + $call_element->{'cumm_cost'} += $args[3]; + $total_cost += $args[3]; + push @callstack, $call_element; + } + } + elsif($args[0] eq '&') { + # declare function + $function_info{$args[1]}->{'package'} = $args[2]; + if($args[2] ne 'main') { + $function_info{$args[1]}->{'name'} = $args[2]."::".$args[3]; + } else { + $function_info{$args[1]}->{'name'} = $args[3]; + } + } + elsif($args[0] eq '+') { + # push myself onto the stack + my $call_element = { 'specifier' => $args[1], 'cost' => 0 }; + push @callstack, $call_element; + } + elsif($args[0] eq '-') { + my $called = pop @callstack; + my $called_id = $called->{'specifier'}; + my $caller = pop @callstack; + if (exists $tree->{$called_id}) { + $tree->{$called_id}->{'cost'} += $called->{'cost'}; + } + else { + $tree->{$called_id} = $called; + } + if($caller) { + $caller->{'child_calls'}++; + my $caller_id = $caller->{'specifier'}; + if(! exists $tree->{$caller_id} ) { + $tree->{$caller_id} = { 'specifier' => $caller_id, 'cost' => 0 }; +# $tree->{$caller_id} = $caller; + } + $caller->{'cumm_cost'} += $called->{'cumm_cost'}; + $tree->{$caller_id}->{'called_funcs'}->[$tree->{$caller_id}->{'call_counter'}++]->{$called_id} += $called->{'cumm_cost'}; + push @callstack, $caller; + } + } + elsif($args[0] eq '*') { + # goto &func + # replace last caller with self + my $call_element = pop @callstack; + $call_element->{'specifier'} = $args[1]; + push @callstack, $call_element; + } + else {print STDERR "Unexpected line: $_\n";} +} + +# +# Generate output +# +my $output = ''; +$output .= "events: Tick\n"; +$output .= "summary: $total_cost\n"; +$output .= "cmd: your script\n\n"; +foreach my $specifier ( keys %$tree ) { + my $caller_package = $function_info{$specifier}->{'package'} || '???'; + my $caller_name = $function_info{$specifier}->{'name'} || '???'; + my $include = find_include($caller_package); + $output .= "ob=\n"; + $output .= sprintf "fl=%s\n", find_include($caller_package); + $output .= sprintf "fn=%s\n", $caller_name; + $output .= sprintf "1 %d\n", $tree->{$specifier}->{'cost'}; + if(exists $tree->{$specifier}->{'called_funcs'}) { + foreach my $items (@{$tree->{$specifier}->{'called_funcs'}}) { + while(my ($child_specifier, $costs) = each %$items) { + $output .= sprintf "cfn=%s\n", $function_info{$child_specifier}->{'name'}; + $output .= sprintf "cfi=%s\n", find_include($function_info{$child_specifier}->{'package'}); + $output .= "calls=1\n"; + $output .= sprintf "1 %d\n", $costs; + } + } + } + $output .= "\n"; +} +print STDERR "Writing kcachegrind output to $outfile\n"; +$outfd->print($output); + + + +sub find_include { + my $module = shift; + $module =~ s!::!/!g; + for (@INC) { + if ( -f "$_/$module.pm" ) { + return "$_/$module.pm"; + } + if ( -f "$_/$module.so" ) { + return "$_/$module.so"; + } + } + return "???"; +} + +sub usage() { + print STDERR "dprof2calltree -f <tmon.out> [-o outfile]\n"; + exit -1; +} + + +# vim: set sts=2 ts=2 bs ai expandtab : diff --git a/kcachegrind/converters/hotshot2calltree b/kcachegrind/converters/hotshot2calltree new file mode 100644 index 00000000..176f82f3 --- /dev/null +++ b/kcachegrind/converters/hotshot2calltree @@ -0,0 +1,394 @@ +#!/usr/bin/env python +# _*_ coding: latin1 _*_ + +# +# Copyright (c) 2003 by WEB.DE, Karlsruhe +# Autor: Jrg Beyer <job@webde-ag.de> +# +# hotshot2cachegrind is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# 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; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# +# +# This script transforms the pstat output of the hotshot +# python profiler into the input of kcachegrind. +# +# example usage: +# modify you python script to run this code: +# +# import hotshot +# filename = "pythongrind.prof" +# prof = hotshot.Profile(filename, lineevents=1) +# prof.runcall(run) # assuming that "run" should be called. +# prof.close() +# +# it will run the "run"-method under profiling and write +# the results in a file, called "pythongrind.prof". +# +# then call this script: +# hotshot2cachegrind -o <output> <input> +# or here: +# hotshot2cachegrind cachegrind.out.0 pythongrind.prof +# +# then call kcachegrind: +# kcachegrind cachegrind.out.0 +# +# TODO: +# * es gibt Probleme mit rekursiven (direkt und indirekt) Aufrufen - dann +# stimmen die Kosten nicht. +# +# * einige Funktionen werden mit "?" als Name angezeigt. Evtl sind +# das nur die C/C++ extensions. +# +# * es fehlt noch ein Funktionsnamen Mangling, dass die Filenamen bercksichtigt, +# zZ sind alle __init__'s und alle run's schwer unterscheidbar :-( +# +version = "$Revision$" +progname = "hotshot2cachegrind" + +import os, sys +from hotshot import stats,log +import os.path + +file_limit=0 + +what2text = { + log.WHAT_ADD_INFO : "ADD_INFO", + log.WHAT_DEFINE_FUNC : "DEFINE_FUNC", + log.WHAT_DEFINE_FILE : "DEFINE_FILE", + log.WHAT_LINENO : "LINENO", + log.WHAT_EXIT : "EXIT", + log.WHAT_ENTER : "ENTER"} + +# a pseudo caller on the caller stack. This represents +# the Python interpreter that executes the given python +# code. +root_caller = ("PythonInterpreter",0,"execute") + +class CallStack: + """A tiny Stack implementation, based on python lists""" + def __init__(self): + self.stack = [] + self.recursion_counter = {} + def push(self, elem): + """put something on the stack""" + self.stack.append(elem) + rc = self.recursion_counter.get(elem, 0) + self.recursion_counter[elem] = rc + 1 + + def pop(self): + """get the head element of the stack and remove it from teh stack""" + elem = self.stack[-1:][0] + rc = self.recursion_counter.get(elem) - 1 + if rc>0: + self.recursion_counter[elem] = rc + else: + del self.recursion_counter[elem] + return self.stack.pop() + + def top(self): + """get the head element of the stack, stack is unchanged.""" + return self.stack[-1:][0] + def handleLineCost(self, tdelta): + p, c = self.stack.pop() + self.stack.append( (p,c + tdelta) ) + def size(self): + """ return how many elements the stack has""" + return len(self.stack) + + def __str__(self): + return "[stack: %s]" % self.stack + + def recursion(self, pos): + return self.recursion_counter.get(pos, 0) + #return self.recursion_dict.has_key((entry[0][0], entry[0][2])) + +def return_from_call(caller_stack, call_dict, cost_now): + """return from a function call + remove the function from the caller stack, + add the costs to the calling function. + """ + called, cost_at_enter = caller_stack.pop() + caller, caller_cost = caller_stack.top() + + #print "return_from_call: %s ruft %s" % (caller, called,) + + per_file_dict = call_dict.get(called[0], {}) + per_caller_dict = per_file_dict.get(called[2], {}) + cost_so_far, call_counter = per_caller_dict.get(caller, (0, 0)) + + if caller_stack.recursion(called): + per_caller_dict[caller] = (cost_so_far, call_counter + 1) + else: + per_caller_dict[caller] = (cost_so_far + cost_now - cost_at_enter, call_counter + 1) + + per_file_dict[called[2]] = per_caller_dict + call_dict[called[0]] = per_file_dict + + +def updateStatus(filecount): + sys.stdout.write("reading File #%d \r" % filecount) + sys.stdout.flush() +def convertProfFiles(output, inputfilenames): + """convert all the given input files into one kcachegrind + input file. + """ + call_dict = {} + cost_per_pos = {} + cost_per_function = {} + caller_stack = CallStack() + caller_stack.push((root_caller, 0)) + + total_cost = 0 + filecount = 1 + number_of_files = len(inputfilenames) + for inputfilename in inputfilenames: + updateStatus(filecount) + cost, filecount = convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount) + total_cost += cost + if (file_limit > 0) and (filecount > file_limit): + break + + print + print "total_cost: % d Ticks",total_cost + dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function) + +def convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount): + updateStatus(filecount) + if not ((file_limit > 0) and (filecount > file_limit)): + if os.path.isdir(inputfilename): + cost, filecount = convertProfDir(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount) + elif os.path.isfile(inputfilename): + cost = convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function) + filecount += 1 + else: + sys.stderr.write("warn: ignoring '%s', is no file and no directory\n" % inputfilename) + cost = 0 + return (cost, filecount) + +def convertProfDir(start, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount): + cost = 0 + filenames = os.listdir(start) + for f in filenames: + if (file_limit > 0) and (filecount > file_limit): + break + full = os.path.join(start, f) + c, filecount = convertHandleFilename(full, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount) + cost += c; + return (cost, filecount) + +def handleCostPerPos(cost_per_pos, pos, current_cost): + """ + the cost per source position are managed in a dict in a dict. + + the cost are handled per file and there per function. + so, the per-file-dict contains some per-function-dicts + which sum up the cost per line (in this function and in + this file). + """ + filename = pos[0] + lineno = pos[1] + funcname = pos[2] + file_dict = cost_per_pos.get(filename, {}) + func_dict = file_dict.get(funcname, {}) + func_dict.setdefault(lineno, 0) + func_dict[lineno] += current_cost + file_dict[funcname] = func_dict + cost_per_pos[filename] = file_dict + +def convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function): + """convert a single input file into one kcachegrind + data. + + this is the most expensive function in this python source :-) + """ + + total_cost = 0 + try: + logreader = log.LogReader(inputfilename) + current_cost = 0 + hc = handleCostPerPos # shortcut + for item in logreader: + what, pos ,tdelta = item + (file, lineno, func) = pos + #line = "%s %s %d %s %d" % (what2text[what], file, lineno, func, tdelta) + #print line + # most common cases first + if what == log.WHAT_LINENO: + # add the current cost to the current function + hc(cost_per_pos, pos, tdelta) + total_cost += tdelta + elif what == log.WHAT_ENTER: + caller_stack.push((pos, total_cost)) + hc(cost_per_pos, pos, tdelta) + total_cost += tdelta + elif what == log.WHAT_EXIT: + hc(cost_per_pos, pos, tdelta) + total_cost += tdelta + return_from_call(caller_stack, call_dict, total_cost) + else: + assert 0, "duh: %d" % what + + + # I have no idea, why sometimes the stack is not empty - we + # have to rewind the stack to get 100% for the root_caller + while caller_stack.size() > 1: + return_from_call(caller_stack, call_dict, total_cost) + + except IOError: + print "could not open inputfile '%s', ignore this." % inputfilename + except EOFError, m: + print "EOF: %s" % (m,) + return total_cost + +def pretty_name(file, function): + #pfile = os.path.splitext(os.path.basename(file)) [0] + #return "%s_[%s]" % (function, file) + return "%s" % function + #return "%s::%s" % (file, function) + #return "%s_%s" % (pfile, function) + +class TagWriter: + def __init__(self, output): + self.output = output + self.last_values = {} + + def clearTag(self, tag): + if self.last_values.has_key(tag): + del self.last_values[ tag ] + def clear(self): + self.last_values = {} + + def write(self, tag, value): + self.output.write("%s=%s\n" % (tag, value)) + #if (not self.last_values.has_key(tag)) or self.last_values[tag] != value: + # self.last_values[ tag ] = value + # self.output.write("%s=%s\n" % (tag, value)) + +def dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function): + """write the collected results in the format kcachegrind + could read. + """ + # the intro + output.write("events: Tick\n") + output.write("summary: %d\n" % total_cost) + output.write("cmd: your python script\n") + output.write("\n") + tagwriter = TagWriter(output) + + # now the costs per line + for file in cost_per_pos.keys(): + func_dict = cost_per_pos[file] + for func in func_dict.keys(): + line_dict = func_dict[func] + tagwriter.write("ob", file) + tagwriter.write("fn", func)# pretty_name(file, func)) ; output.write("# ^--- 2\n") + tagwriter.write("fl", file) + for line in line_dict: + output.write("%d %d\n" %( line, line_dict[line] )) + + output.write("\n\n") + # now the function calls. For each caller all the called + # functions and their costs are written. + for file in call_dict.keys(): + per_file_dict = call_dict[file] + #print "file %s -> %s" % (file, per_file_dict) + for called_x in per_file_dict.keys(): + #print "called_x:",called_x + per_caller_dict = per_file_dict[called_x] + #print "called_x %s wird gerufen von: %s" % (called_x, per_caller_dict) + for caller_x in per_caller_dict.keys(): + tagwriter.write("ob", caller_x[0]) + tagwriter.write("fn", caller_x[2])# pretty_name(caller_x[2], caller_x[0])) ; output.write("# ^--- 1\n") + tagwriter.write("fl", caller_x[0]) + tagwriter.write("cob", file) + tagwriter.write("cfn", called_x) #pretty_name(file, called_x)) + tagwriter.write("cfl", file) + cost, count = per_caller_dict[caller_x] + #print "called_x:",called_x + output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost)) + tagwriter.clear() + #tagwriter.clearTag("cob") + # is it a bug in kcachegrind, that the "cob=xxx" line has + # to be rewritten after a calls entry with costline ? + #assert cost <= total_cost, "caller_x: %s, per_caller_dict: %s " % (caller_x, per_caller_dict, ) + #output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost)) + output.write("\n") + +def run_without_optparse(): + """parse the options without optparse, use sys.argv""" + if len(sys.argv) < 4 or sys.argv[1] != "-o" : + print "usage: hotshot2cachegrind -o outputfile in1 [in2 [in3 [...]]]" + return + outputfilename = sys.argv[2] + try: + output = file(outputfilename, "w") + args = sys.argv[3:] + convertProfFiles(output, args) + output.close() + except IOError: + print "could not open '%s' for writing." % outputfilename + +def run_with_optparse(): + """parse the options with optparse""" + + global file_limit + + versiontext = "%s version: %s" % ( progname, version.split()[1], ) + parser = OptionParser(version=versiontext) + parser.add_option("-o", "--output", + action="store", type="string", dest="outputfilename", + help="write output into FILE") + parser.add_option("--file-limit", + action="store", dest="file_limit", default=0, + help="stop after given number of input files") + output = sys.stdout + close_output = 0 + (options, args) = parser.parse_args() + file_limit = int(options.file_limit) + try: + if options.outputfilename and options.outputfilename != "-": + output = file(options.outputfilename, "w") + close_output = 1 + except IOError: + print "could not open '%s' for writing." % options.outputfilename + if output: + convertProfFiles(output, args) + if close_output: + output.close() + + +def profile_myself(): + import hotshot + filename = "self.prof" + if not os.path.exists(filename): + prof = hotshot.Profile(filename, lineevents=1) + prof.runcall(run) + prof.close() + else: + print "not profiling myself, since '%s' exists, running normal" % filename + run() + +# check if optparse is available. +try: + from optparse import OptionParser + run = run_with_optparse +except ImportError: + run = run_without_optparse + +if __name__ == "__main__": + try: + run() + #profile_myself() + except KeyboardInterrupt: + sys.exit(1) diff --git a/kcachegrind/converters/memprof2calltree b/kcachegrind/converters/memprof2calltree new file mode 100755 index 00000000..e82d6e85 --- /dev/null +++ b/kcachegrind/converters/memprof2calltree @@ -0,0 +1,38 @@ +#!/usr/bin/perl +# +# Convert the memory profiles of memprof to calltree format, +# loadable with KCachegrind +# +# (C) 2004, Josef Weidendorfer + +print "events: Allocated\n"; + +while(<>) { + if (/^(\S.*)$/) { + $next = 0; + print "\nfn=$1\n"; + next; + } + if (/^ children:/) { + $next = 1; #children + next; + } + if (/^ inherited:/) { + $next = 2; #inherited + next; + } + if (/^ total:/) { + # ignore, is calculated + next; + } + if (/^ self:\s*(\d+)/) { + if ($1 ne "0") { + print "0 $1\n"; + } + next; + } + if (/^\s+(\S.*?):\s*(\d+)$/) { + if ($next < 2) { next; } + print "cfn=$1\ncalls=0 0\n0 $2\n"; + } +} diff --git a/kcachegrind/converters/op2calltree b/kcachegrind/converters/op2calltree new file mode 100755 index 00000000..ff755390 --- /dev/null +++ b/kcachegrind/converters/op2calltree @@ -0,0 +1,238 @@ +#!/usr/bin/perl +# +# Copyright (c) 2004 +# Author: Josef Weidendorfer <Josef.Weidendorfer@gmx.de> +# +# op2calltree is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# 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; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# +# +# Converter from OProfile's output of "opreport -gdf" (v 0.8) +# into callgrind format. +# +# Generate a OProfile report with opreport and flags -gdf +# and pipe this as standard input into this script. +# This will generate separate cachegrind files for every application. +# + + +# parse symbol line. example (with 1 event type, $has_image==0): +# 308 0.1491 /path/source.c:6 /path/app main +sub parseSymSpec { + $e = 0; + while($e < $eventCount) { + ($line) = ($line =~ /\d+\s+\S+\s+(.*)/); + $e++; + } + if ($line =~ s/^\(no location information\)\s+//) { + $file = "???"; + $linenr = 0; + } + else { + ($file,$linenr) = ($line =~ s/(\S+?):(\d+)\s+//); + } + if ($has_image) { + if ($line =~ s/^(\S+)\s+//) { $img = $1; } + } + if ($has_app) { + if ($line =~ s/^(\S+)\s+//) { $app = $1; } + if (!$has_image) { $img = $app; } + } + $sym = $line; + + $app =~ s/^.*\///; + if ($sym eq "(no symbols)") { $sym = "???"; } + $file{$sym} = $file; + $linenr{$sym} = $linenr; + $app{$sym} = $app; + $img{$app,$sym} = $img; + $syms{$app}++; + + if ($app ne $oldApp) { + $oldApp = $app; + print "\n\nApp $app\n"; + } + print " Symbol $sym (Image $img)\n"; +} + + + +$eventCount = 0; +$descCount = 0; +$lnr = 0; +$has_image = 0; +$has_app = 0; +$app = "unnamed"; +$img = "???"; + +# first loop till first symbol specification +while(<>) { + $lnr++; + chomp; + if (/^CPU:/) { + $desc[$descCount++] = $_; + next; + } + if (/^Counted\s*(\S+)/) { + $desc[$descCount++] = $_; + $eventCount++; + $events[$eventCount] = $1; + next; + } + if (/^(Profiling through timer.*)/) { + $desc[$descCount++] = $_; + $eventCount++; + $events[$eventCount] = "Timer"; + next; + } + if (/^vma/) { + # title row: adapt to separation options of OProfile + if (/image/) { $has_image = 1; } + if (/app/) { $has_app = 1; } + next; + } + if (/^([0-9a-fA-F]+)\s*(.*)$/) { + $vmaSym = $1; + $line = $2; + last; + } +} + +if ($eventCount == 0) { + die "No Events found"; +} + +print "Description:\n"; +foreach $d (@desc) { print " $d\n"; } +print "\n"; + +print "Events:"; +foreach $e (@events) { print " $e"; } +print "\n"; + +parseSymSpec; + +while(<>) { + $lnr++; + if (/^([0-9a-fA-F]+)\s*(.*)$/) { + $vmaSym = $1; + $line = $2; + + parseSymSpec; + next; + } + if (/^\s+([0-9a-fA-F]+)\s*(.*)$/) { + + $sampleCount{$app,$sym}++; + $sc = $sampleCount{$app,$sym}; + + $vma{$app,$sym,$sc} = $1; + $line = $2; + + $e = 1; + while($e <= $eventCount) { + ($cost, $line) = ($line =~ /(\d+)\s+\S+\s+(.*)/); + $summary{$app,$e} += $cost; + $cost{"$app,$sym,$sc,$e"} = $cost; + $e++; + } + if ($line =~ /\(no location information\)/) { + $file = "???"; + $linenr = 0; + } + else { + ($file,$linenr) = ($line =~ /(\S+?):(\d+)/); + } + $sFile{$app,$sym,$sc} = $file; + $linenr{$app,$sym,$sc} = $linenr; + + $file =~ s/^.*\///; + print " Sample $sc: $vma{$app,$sym,$sc} ($file:$linenr):"; + foreach $e (1 .. $eventCount) { $c = $cost{"$app,$sym,$sc,$e"} ; print " $c"; } + print "\n"; + next; + } + die "ERROR: Reading line $lnr '$_'\n"; +} + +foreach $app (keys %syms) { + if ($app eq "") { next; } + print "Generating dump for App '$app'...\n"; + + $out = "# Generated by op2cg, using OProfile with opreport -gdf\n"; + $out .= "positions: instr line\n"; + + $out .= "events:"; + foreach $e (@events) { $out .= " $e"; } + $out .= "\n"; + + $out .= "summary:"; + foreach $e (1 .. $eventCount) { $out .= " $summary{$app,$e}"; } + $out .= "\n\n"; + + %fileNum = (); + $fileNum = 1; + $sf = ""; + + $img = ""; + + foreach $sym (keys %file) { + if ($sampleCount{$app,$sym} eq "") { next; } + + if ($img{$app,$sym} ne $img) { + $img = $img{$app,$sym}; + $out .= "ob=$img\n"; + } + + $file = $file{$sym}; + if ($sf ne $file) { + if ($fileNum{$file} eq "") { + $fileNum{$file} = $fileNum; + $out .= "fl=($fileNum) $file\n"; + $fileNum++; + } + else { + $out .= "fl=($fileNum{$file})\n"; + } + $sf = $file; + } + + $out .= "fn=$sym\n"; + foreach $sc (1 .. $sampleCount{$app,$sym}) { + if ($sf ne $sFile{$app,$sym,$sc}) { + $sf = $sFile{$app,$sym,$sc}; + if ($sf eq $file) { + $out .= "fe=($fileNum{$file})\n"; + } + else { + if ($fileNum{$sf} eq "") { + $fileNum{$sf} = $fileNum; + $out .= "fi=($fileNum) $sf\n"; + $fileNum++; + } + else { + $out .= "fi=($fileNum{$sf})\n"; + } + } + } + $out .= "0x$vma{$app,$sym,$sc} $linenr{$app,$sym,$sc}"; + foreach $e (1 .. $eventCount) { $c = $cost{"$app,$sym,$sc,$e"} ; $out .= " $c"; } + $out .= "\n"; + } + } + + open OUT, ">oprof.out.$app"; + print OUT $out; + close OUT; +} diff --git a/kcachegrind/converters/pprof2calltree b/kcachegrind/converters/pprof2calltree new file mode 100644 index 00000000..59f8770d --- /dev/null +++ b/kcachegrind/converters/pprof2calltree @@ -0,0 +1,218 @@ +#!/usr/bin/env php +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# - All advertising materials mentioning features or use of this software +# must display the following acknowledgement: This product includes software +# developed by OmniTI Computer Consulting. +# +# - Neither name of the company nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Copyright (c) 2004 OmniTI Computer Consulting +# All rights reserved +# The following code was written by George Schlossnagle <george@omniti.com> +# and is provided completely free and without any warranty. +# +# This script is designed to convert the pprof output from +# APD (http://pecl.php.net/apd/) to one readable by kcachegrind. To use +# this script: +# +# 1) Install APD. +# 2) Profile your script with APD accordingto the directions in it's +# README file. +# 3) Take the pprof trace file for your script (pprof.XXXXX.Y) and run it +# through this script as follows: +# > pprof2calltree -f pprof.12345.1 +# This creates a new file cachegrind.out.12345.1 +# 4) View your trace with pprof2calltree cachegrind.out.12345.1 + +<?php + +require "Console/Getopt.php"; + +$con = new Console_Getopt; +$args = $con->readPHPArgv(); +array_shift($args); +$shortoptions = 'f:'; +$retval = $con->getopt( $args, $shortoptions); +if(is_object($retval)) { + usage(); +} +foreach ($retval[0] as $kv_array) { + $opt[$kv_array[0]] = $kv_array[1]; +} +if(!$opt['f']) { + usage(); +} +if(!file_exists($opt['f'])) { + print "Trace file ${opt['f']} does not exist\n"; + exit; +} +$IN = fopen($opt['f'], "r"); +if(!$IN) { + print "Trace file ${opt['f']} could not be opened\n"; + exit; +} + +$path_parts = pathinfo($opt['f']); +$outfile = "cachegrind.out.".$path_parts['basename']; +$OUT = fopen($outfile, "w"); +if(!$OUT) { + print "Destination file $outfile could not be opened.\n"; + exit; +} + +while(($line = fgets($IN)) !== false) { + $line = rtrim($line); + if($line == "END_HEADER") { + break; + } +} +$tree = array(); +$callstack = array(); +while(($line = fgets($IN)) !== false) { + $line = rtrim($line); + $args = explode(" ", $line); + if($args[0] == '!') { + $file_lookup[$args[1]] = $args[2]; + } + else if($args[0] == '&') { + $function_lookup[$args[1]] = $args[2]; + $function_type[$args[1]] = ($args[3] == 2)?"USER":"INTERNAL"; + } + else if($args[0] == '+') { + $val = array(function_id => $args[1], + file_id => $args[2], + line => $args[3], + cost => 0); + array_push($callstack, $val); + } + else if($args[0] == '-') { + // retrieve $called to discard + $called = array_pop($callstack); + // retrieve $caller for reference + $caller = array_pop($callstack); + $called_id = $called['function_id']; + + // Set meta data if not already set' + if(!array_key_exists($called_id, $tree)) { + $tree[$called_id] = $called; + // initialize these to 0 + $tree[$called_id]['cost_per_line'] = array(); + } + if($caller !== null) { + $caller['child_calls']++; + $caller_id = $caller['function_id']; + if(!array_key_exists($caller_id, $tree)) { + $tree[$caller_id] = $caller; + } + $caller['cost'] += $called['cost']; + $tree[$caller_id]['called_funcs'][$tree[$caller_id]['call_counter']++][$called_id][$called['file_id']][$called['line']] += $called['cost']; + array_push($callstack, $caller); + } + if(is_array($called['cost_per_line'])) { + foreach($called[cost_per_line] as $file => $lines) { + foreach($lines as $line => $cost) { + $tree[$called_id]['cost_per_line'][$file][$line] += $cost; + } + } + } + } + else if($args[0] == '@') { + $called = array_pop($callstack); + switch(count($args)) { + // support new and old-style pprof data + case 6: + $file = $args[1]; + $line = $args[2]; + $real_tm = $args[5]; + break; + case 4: + $file = $called['file_id']; + $line = $called['line']; + $real_tm = $args[3]; + break; + + } + $called['cost_per_line'][$file][$line] += $real_tm; + $called['cost'] += $real_tm; + $total_cost += $real_tm; + array_push($callstack, $called); + } +} + +ob_start(); +print "events: Tick\n"; +print "summary: $total_cost\n"; +printf("cmd: %s\n", $file_lookup[1]); +print "\n"; + +foreach($tree as $caller => $data) { + $filename = $file_lookup[$data['file_id']]?$file_lookup[$data['file_id']]:"???"; + printf("ob=%s\n", $function_type[$caller]); + printf("fl=%s\n", $filename); + printf("fn=%s\n", $function_lookup[$caller]); + if(is_array($data['cost_per_line'])) { + foreach($data['cost_per_line'] as $file => $lines) { + foreach($lines as $line => $cost) { + print "$line $cost\n"; + } + } + } + else if ($data['cost']) { + printf("COST %s %s\n", $items['line'], $items['cost']); + } + else { + print_r($items); + } + if(is_array($data['called_funcs'])) { + foreach($data['called_funcs'] as $counter => $items) { + foreach($items as $called_id => $costs) { + if(is_array($costs)) { + printf("cfn=%s\n", $function_lookup[$called_id]); + foreach($costs as $file => $lines) { + printf("cfi=%s\ncalls=1\n", $file_lookup[$file]); + foreach($lines as $line => $cost) { + print "$line $cost\n"; + } + } + } + } + } + } + print "\n"; +} +print "\ntotals=$total_cost\n"; +$buffer = ob_get_clean(); +print "Writing kcachegrind compatible output to $outfile\n"; +fwrite($OUT, $buffer); + +function usage() +{ + print <<<EOD +pprof2calltree -f <tracefile> + +EOD; + exit(1); +} +?> diff --git a/kcachegrind/kcachegrind.lsm.in b/kcachegrind/kcachegrind.lsm.in new file mode 100644 index 00000000..fba32141 --- /dev/null +++ b/kcachegrind/kcachegrind.lsm.in @@ -0,0 +1,11 @@ +Begin3 +Title: kcachegrind +Version: @KCACHEGRIND_VERSION@ +Description: KDE Profiling Visualisation Tool +Keywords: Profiling, Performance Analysis, Visualisation, Development +Author: Josef Weidendorfer <Josef.Weidendorfer@gmx.de> +Maintained-by: Josef Weidendorfer <Josef.Weidendorfer@gmx.de> +Home-page: http://kcachegrind.sourceforge.net +Platforms: Linux and other Unices +Copying-policy: GNU Public License +End diff --git a/kcachegrind/kcachegrind.spec.in b/kcachegrind/kcachegrind.spec.in new file mode 100644 index 00000000..060dd28e --- /dev/null +++ b/kcachegrind/kcachegrind.spec.in @@ -0,0 +1,55 @@ +Summary: KDE Profiling Visualisation Tool +Name: kcachegrind +Version: @KCACHEGRIND_VERSION@ +Release: 1 +Copyright: GPL +Group: Development/Tools +Vendor: (none) +URL: http://kcachegrind.sourceforge.net +Packager: Josef Weidendorfer <Josef.Weidendorfer@gmx.de> +Source: kcachegrind-@KCACHEGRIND_VERSION@.tar.gz +BuildRoot: /var/tmp/build + +%description +KCachegrind is a GPL'd tool for quick browsing in and visualisation +of performance data of an application run. This data is produced by +profiling tools and typically includes distribution of cost events +to source code ranges (instructions, source lines, functions, C++ classes) +and call relationship of functions. +KCachegrind has a list of functions sorted according to different cost +types, and can provide various performance views for a function like +direct/indirect callers/callees, TreeMap visualisation of cost distribution +among callees, call graph sectors centered around the function and +annotated source/assembler. +Currently, KCachegrind depends on data delivered by the profiling tool +calltree, powered by the Valgrind runtime instrumentation framework. + +%prep +%setup +CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" ./configure \ + \ + $LOCALFLAGS +%build +# Setup for parallel builds +numprocs=`egrep -c ^cpu[0-9]+ /proc/stat || :` +if [ "$numprocs" = "0" ]; then + numprocs=1 +fi + +make -j$numprocs + +%install +make install-strip DESTDIR=$RPM_BUILD_ROOT + +cd $RPM_BUILD_ROOT +find . -type d | sed '1,2d;s,^\.,\%attr(-\,root\,root) \%dir ,' > $RPM_BUILD_DIR/file.list.kcachegrind +find . -type f | sed 's,^\.,\%attr(-\,root\,root) ,' >> $RPM_BUILD_DIR/file.list.kcachegrind +find . -type l | sed 's,^\.,\%attr(-\,root\,root) ,' >> $RPM_BUILD_DIR/file.list.kcachegrind + +%clean +rm -rf $RPM_BUILD_ROOT/* +rm -rf $RPM_BUILD_DIR/kcachegrind +rm -rf ../file.list.kcachegrind + + +%files -f ../file.list.kcachegrind diff --git a/kcachegrind/kcachegrind/Doxyfile b/kcachegrind/kcachegrind/Doxyfile new file mode 100644 index 00000000..72db41bc --- /dev/null +++ b/kcachegrind/kcachegrind/Doxyfile @@ -0,0 +1,157 @@ +# Doxygen configuration generated by Doxywizard version 0.1 +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = kcachegrind +PROJECT_NUMBER = +OUTPUT_DIRECTORY = +OUTPUT_LANGUAGE = English +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +HIDE_UNDOC_MEMBERS = +HIDE_UNDOC_CLASSES = +BRIEF_MEMBER_DESC = +REPEAT_BRIEF = +ALWAYS_DETAILED_SEC = +FULL_PATH_NAMES = +STRIP_FROM_PATH = +INTERNAL_DOCS = +CLASS_DIAGRAMS = +SOURCE_BROWSER = +INLINE_SOURCES = +STRIP_CODE_COMMENTS = +CASE_SENSE_NAMES = +SHORT_NAMES = +HIDE_SCOPE_NAMES = +VERBATIM_HEADERS = +SHOW_INCLUDE_FILES = +JAVADOC_AUTOBRIEF = +INHERIT_DOCS = +INLINE_INFO = +SORT_MEMBER_DOCS = +DISTRIBUTE_GROUP_DOC = +TAB_SIZE = +ENABLED_SECTIONS = +GENERATE_TODOLIST = +GENERATE_TESTLIST = +GENERATE_BUGLIST = +ALIASES = +MAX_INITIALIZER_LINES = +OPTIMIZE_OUTPUT_FOR_C = +SHOW_USED_FILES = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = +WARNINGS = +WARN_IF_UNDOCUMENTED = +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +FILE_PATTERNS = *.cpp \ + *.h +RECURSIVE = no +EXCLUDE = +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = +COLS_IN_ALPHA_INDEX = +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = +HTML_OUTPUT = html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = +GENERATE_HTMLHELP = +GENERATE_CHI = +BINARY_TOC = +TOC_EXPAND = +DISABLE_INDEX = +ENUM_VALUES_PER_LINE = +GENERATE_TREEVIEW = +TREEVIEW_WIDTH = +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +COMPACT_LATEX = +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = +USE_PDFLATEX = +LATEX_BATCHMODE = +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = +RTF_HYPERLINKS = +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = +MACRO_EXPANSION = +EXPAND_ONLY_PREDEF = +SEARCH_INCLUDES = +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +HAVE_DOT = +CLASS_GRAPH = +COLLABORATION_GRAPH = +INCLUDE_GRAPH = +INCLUDED_BY_GRAPH = +GRAPHICAL_HIERARCHY = +DOT_PATH = +MAX_DOT_GRAPH_WIDTH = +MAX_DOT_GRAPH_HEIGHT = +GENERATE_LEGEND = +DOT_CLEANUP = +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = +CGI_NAME = search.cgi +CGI_URL = +DOC_URL = +DOC_ABSPATH = +BIN_ABSPATH = /usr/local/bin/ +EXT_DOC_PATHS = diff --git a/kcachegrind/kcachegrind/Makefile.am b/kcachegrind/kcachegrind/Makefile.am new file mode 100644 index 00000000..3b4871aa --- /dev/null +++ b/kcachegrind/kcachegrind/Makefile.am @@ -0,0 +1,62 @@ +bin_PROGRAMS = kcachegrind + +kcachegrind_SOURCES = \ + functionselectionbase.ui \ + stackselectionbase.ui \ + partselectionbase.ui \ + configdlgbase.ui \ + loader.cpp cachegrindloader.cpp treemap.cpp pool.cpp \ + main.cpp configuration.cpp \ + functionselection.cpp coverage.cpp partgraph.cpp \ + toplevel.cpp stackselection.cpp stackbrowser.cpp \ + subcost.cpp tracedata.cpp partselection.cpp configdlg.cpp \ + utils.cpp fixcost.cpp \ + traceitemview.cpp instrview.cpp tabview.cpp \ + sourceview.cpp callmapview.cpp callview.cpp \ + coverageview.cpp costtypeview.cpp partview.cpp \ + listutils.cpp costtypeitem.cpp multiview.cpp \ + callitem.cpp coverageitem.cpp sourceitem.cpp \ + costlistitem.cpp partlistitem.cpp functionitem.cpp \ + instritem.cpp stackitem.cpp callgraphview.cpp + +kcachegrind_COMPILE_FIRST = ../version.h + +kcachegrind_LDADD = $(LIB_KIO) + +KDE_ICON = AUTO + +xdg_apps_DATA = kcachegrind.desktop + +mimeapplicationdir = $(kde_mimedir)/application +mimeapplication_DATA = x-kcachegrind.desktop + +EXTRA_DIST = \ + kcachegrind.desktop \ + x-kcachegrind.desktop \ + hi32-app-kcachegrind.png \ + hi48-app-kcachegrind.png \ + Doxyfile \ + kcachegrindui.rc + +# set the include path for X, qt and KDE +INCLUDES= $(all_includes) + +METASOURCES = AUTO + +# the library search path. +kcachegrind_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +rcdir = $(kde_datadir)/kcachegrind +rc_DATA = kcachegrindui.rc + +tipdir = $(kde_datadir)/kcachegrind +tip_DATA = tips + +messages: rc.cpp + $(PREPARETIPS) > tips.cpp + LIST=`find . -name \*.h -o -name \*.cpp`; \ + if test -n "$$LIST"; then \ + $(XGETTEXT) $$LIST -o $(podir)/kcachegrind.pot; \ + fi + rm -f tips.cpp + diff --git a/kcachegrind/kcachegrind/cachegrindloader.cpp b/kcachegrind/kcachegrind/cachegrindloader.cpp new file mode 100644 index 00000000..912b3bf6 --- /dev/null +++ b/kcachegrind/kcachegrind/cachegrindloader.cpp @@ -0,0 +1,1323 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <errno.h> + +#include <qfile.h> +#include <qcstring.h> + +#include <klocale.h> +#include <kdebug.h> + +#include "loader.h" +#include "tracedata.h" +#include "utils.h" +#include "fixcost.h" + + +#define TRACE_LOADER 0 + +/* + * Loader for Callgrind Profile data (format based on Cachegrind format). + * See Callgrind documentation for the file format. + */ + +class CachegrindLoader: public Loader +{ +public: + CachegrindLoader(); + + bool canLoadTrace(QFile* file); + bool loadTrace(TracePart*); + bool isPartOfTrace(QString file, TraceData*); + +private: + bool loadTraceInternal(TracePart*); + + enum lineType { SelfCost, CallCost, BoringJump, CondJump }; + + bool parsePosition(FixString& s, PositionSpec& newPos); + + // position setters + void clearPosition(); + void ensureObject(); + void ensureFile(); + void ensureFunction(); + void setObject(const QString&); + void setCalledObject(const QString&); + void setFile(const QString&); + void setCalledFile(const QString&); + void setFunction(const QString&); + void setCalledFunction(const QString&); + + QString _emptyString; + + // current line in file to read in + QString _filename; + int _lineNo; + + TraceSubMapping* subMapping; + TraceData* _data; + TracePart* _part; + + // current position + lineType nextLineType; + bool hasLineInfo, hasAddrInfo; + PositionSpec currentPos; + + // current function/line + TraceObject* currentObject; + TracePartObject* currentPartObject; + TraceFile* currentFile; + TracePartFile* currentPartFile; + TraceFunction* currentFunction; + TracePartFunction* currentPartFunction; + TraceFunctionSource* currentFunctionSource; + TraceInstr* currentInstr; + TracePartInstr* currentPartInstr; + TraceLine* currentLine; + TracePartLine* currentPartLine; + + // current call + TraceObject* currentCalledObject; + TracePartObject* currentCalledPartObject; + TraceFile* currentCalledFile; + TracePartFile* currentCalledPartFile; + TraceFunction* currentCalledFunction; + TracePartFunction* currentCalledPartFunction; + SubCost currentCallCount; + + // current jump + TraceFile* currentJumpToFile; + TraceFunction* currentJumpToFunction; + PositionSpec targetPos; + SubCost jumpsFollowed, jumpsExecuted; + + /** Support for compressed string format + * This uses the following string compression model + * for objects, files, functions: + * If the name matches + * "(<Integer>) Name": this is a compression specification, + * mapping the integer number to Name and using Name. + * "(<Integer>)" : this is a compression reference. + * Assumes previous compression specification of the + * integer number to a name, uses this name. + * "Name" : Regular name + */ + void clearCompression(); + const QString& checkUnknown(const QString& n); + TraceObject* compressedObject(const QString& name); + TraceFile* compressedFile(const QString& name); + TraceFunction* compressedFunction(const QString& name, + TraceFile*, TraceObject*); + + QPtrVector<TraceCostItem> _objectVector, _fileVector, _functionVector; +}; + + + +/********************************************************** + * Loader + */ + + +CachegrindLoader::CachegrindLoader() + : Loader("Callgrind", + i18n( "Import filter for Cachegrind/Callgrind generated profile data files") ) +{ + _emptyString = QString(""); +} + +bool CachegrindLoader::canLoadTrace(QFile* file) +{ + if (!file) return false; + + if (!file->isOpen()) { + if (!file->open( IO_ReadOnly ) ) { + kdDebug() << QFile::encodeName(_filename) << ": " + << strerror( errno ) << endl; + return false; + } + } + + /* + * We recognize this as cachegrind/callgrind format if in the first + * 2047 bytes we see the string "\nevents:" + */ + char buf[2048]; + int read = file->readBlock(buf,2047); + if (read < 0) + return false; + buf[read] = 0; + + QCString s; + s.setRawData(buf, read+1); + int pos = s.find("events:"); + if (pos>0 && buf[pos-1] != '\n') pos = -1; + s.resetRawData(buf, read+1); + return (pos>=0); +} + +bool CachegrindLoader::loadTrace(TracePart* p) +{ + /* do the loading in a new object so parallel load + * operations do not interfere each other. + */ + CachegrindLoader l; + + /* emit progress signals via the singleton loader */ + connect(&l, SIGNAL(updateStatus(QString, int)), + this, SIGNAL(updateStatus(QString, int))); + + return l.loadTraceInternal(p); +} + +Loader* createCachegrindLoader() +{ + return new CachegrindLoader(); +} + + + +/** + * Return false if this is no position specification + */ +bool CachegrindLoader::parsePosition(FixString& line, + PositionSpec& newPos) +{ + char c; + uint diff; + + if (hasAddrInfo) { + + if (!line.first(c)) return false; + + if (c == '*') { + // nothing changed + line.stripFirst(c); + newPos.fromAddr = currentPos.fromAddr; + newPos.toAddr = currentPos.toAddr; + } + else if (c == '+') { + line.stripFirst(c); + line.stripUInt(diff, false); + newPos.fromAddr = currentPos.fromAddr + diff; + newPos.toAddr = newPos.fromAddr; + } + else if (c == '-') { + line.stripFirst(c); + line.stripUInt(diff, false); + newPos.fromAddr = currentPos.fromAddr - diff; + newPos.toAddr = newPos.fromAddr; + } + else if (c >= '0') { + uint64 v; + line.stripUInt64(v, false); + newPos.fromAddr = Addr(v); + newPos.toAddr = newPos.fromAddr; + } + else return false; + + // Range specification + if (line.first(c)) { + if (c == '+') { + line.stripFirst(c); + line.stripUInt(diff); + newPos.toAddr = newPos.fromAddr + diff; + } + else if ((c == '-') || (c == ':')) { + line.stripFirst(c); + uint64 v; + line.stripUInt64(v); + newPos.toAddr = Addr(v); + } + } + line.stripSpaces(); + +#if TRACE_LOADER + if (newPos.fromAddr == newPos.toAddr) + kdDebug() << " Got Addr " << newPos.fromAddr.toString() << endl; + else + kdDebug() << " Got AddrRange " << newPos.fromAddr.toString() + << ":" << newPos.toAddr.toString() << endl; +#endif + + } + + if (hasLineInfo) { + + if (!line.first(c)) return false; + + if (c > '9') return false; + else if (c == '*') { + // nothing changed + line.stripFirst(c); + newPos.fromLine = currentPos.fromLine; + newPos.toLine = currentPos.toLine; + } + else if (c == '+') { + line.stripFirst(c); + line.stripUInt(diff, false); + newPos.fromLine = currentPos.fromLine + diff; + newPos.toLine = newPos.fromLine; + } + else if (c == '-') { + line.stripFirst(c); + line.stripUInt(diff, false); + if (currentPos.fromLine < diff) { + kdError() << _filename << ":" << _lineNo + << " - Negative line number " + << (int)currentPos.fromLine - (int)diff << endl; + diff = currentPos.fromLine; + } + newPos.fromLine = currentPos.fromLine - diff; + newPos.toLine = newPos.fromLine; + } + else if (c >= '0') { + line.stripUInt(newPos.fromLine, false); + newPos.toLine = newPos.fromLine; + } + else return false; + + // Range specification + if (line.first(c)) { + if (c == '+') { + line.stripFirst(c); + line.stripUInt(diff); + newPos.toLine = newPos.fromLine + diff; + } + else if ((c == '-') || (c == ':')) { + line.stripFirst(c); + line.stripUInt(newPos.toLine); + } + } + line.stripSpaces(); + +#if TRACE_LOADER + if (newPos.fromLine == newPos.toLine) + kdDebug() << " Got Line " << newPos.fromLine << endl; + else + kdDebug() << " Got LineRange " << newPos.fromLine + << ":" << newPos.toLine << endl; +#endif + + } + + return true; +} + +// Support for compressed strings +void CachegrindLoader::clearCompression() +{ + // this doesn't delete previous contained objects + _objectVector.clear(); + _fileVector.clear(); + _functionVector.clear(); + + // reset to reasonable init size. We double lengths if needed. + _objectVector.resize(100); + _fileVector.resize(1000); + _functionVector.resize(10000); +} + +const QString& CachegrindLoader::checkUnknown(const QString& n) +{ + if (n == "???") return _emptyString; + return n; +} + +TraceObject* CachegrindLoader::compressedObject(const QString& name) +{ + if ((name[0] != '(') || !name[1].isDigit()) return _data->object(checkUnknown(name)); + + // compressed format using _objectVector + int p = name.find(')'); + if (p<2) { + kdError() << _filename << ":" << _lineNo + << " - Invalid compressed ELF object ('" + << name << "')" << endl; + return 0; + } + unsigned index = name.mid(1, p-1).toInt(); + TraceObject* o = 0; + p++; + if ((int)name.length()>p) { + while(name.at(p).isSpace()) p++; + + if (_objectVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + kdDebug() << " CachegrindLoader: objectVector enlarged to " + << newSize << endl; +#endif + _objectVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + o = (TraceObject*) _objectVector.at(index); + if (o && (o->name() != realName)) { + kdError() << _filename << ":" << _lineNo + << " - Redefinition of compressed ELF object index " << index + << " (was '" << o->name() + << "') to '" << realName << "'" << endl; + } + + o = _data->object(realName); + _objectVector.insert(index, o); + } + else { + if ((_objectVector.size() <= index) || + ( (o=(TraceObject*)_objectVector.at(index)) == 0)) { + kdError() << _filename << ":" << _lineNo + << " - Undefined compressed ELF object index " << index << endl; + return 0; + } + } + + return o; +} + + +// Note: Callgrind sometimes gives different IDs for same file +// (when references to same source file come from different ELF objects) +TraceFile* CachegrindLoader::compressedFile(const QString& name) +{ + if ((name[0] != '(') || !name[1].isDigit()) return _data->file(checkUnknown(name)); + + // compressed format using _fileVector + int p = name.find(')'); + if (p<2) { + kdError() << _filename << ":" << _lineNo + << " - Invalid compressed file ('" + << name << "')" << endl; + return 0; + } + unsigned int index = name.mid(1, p-1).toUInt(); + TraceFile* f = 0; + p++; + if ((int)name.length()>p) { + while(name.at(p).isSpace()) p++; + + if (_fileVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + kdDebug() << " CachegrindLoader::fileVector enlarged to " + << newSize << endl; +#endif + _fileVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + f = (TraceFile*) _fileVector.at(index); + if (f && (f->name() != realName)) { + kdError() << _filename << ":" << _lineNo + << " - Redefinition of compressed file index " << index + << " (was '" << f->name() + << "') to '" << realName << "'" << endl; + } + + f = _data->file(realName); + _fileVector.insert(index, f); + } + else { + if ((_fileVector.size() <= index) || + ( (f=(TraceFile*)_fileVector.at(index)) == 0)) { + kdError() << _filename << ":" << _lineNo + << " - Undefined compressed file index " << index << endl; + return 0; + } + } + + return f; +} + +// Note: Callgrind gives different IDs even for same function +// when parts of the function are from different source files. +// Thus, it is no error when multiple indexes map to same function. +TraceFunction* CachegrindLoader::compressedFunction(const QString& name, + TraceFile* file, + TraceObject* object) +{ + if ((name[0] != '(') || !name[1].isDigit()) + return _data->function(checkUnknown(name), file, object); + + // compressed format using _functionVector + int p = name.find(')'); + if (p<2) { + kdError() << _filename << ":" << _lineNo + << " - Invalid compressed function ('" + << name << "')" << endl; + return 0; + } + + + unsigned int index = name.mid(1, p-1).toUInt(); + TraceFunction* f = 0; + p++; + if ((int)name.length()>p) { + while(name.at(p).isSpace()) p++; + + if (_functionVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + kdDebug() << " CachegrindLoader::functionVector enlarged to " + << newSize << endl; +#endif + _functionVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + f = (TraceFunction*) _functionVector.at(index); + if (f && (f->name() != realName)) { + kdError() << _filename << ":" << _lineNo + << " - Redefinition of compressed function index " << index + << " (was '" << f->name() + << "') to '" << realName << "'" << endl; + } + + f = _data->function(realName, file, object); + _functionVector.insert(index, f); + +#if TRACE_LOADER + kdDebug() << "compressedFunction: Inserted at Index " << index + << "\n " << f->fullName() + << "\n in " << f->cls()->fullName() + << "\n in " << f->file()->fullName() + << "\n in " << f->object()->fullName() << endl; +#endif + } + else { + if ((_functionVector.size() <= index) || + ( (f=(TraceFunction*)_functionVector.at(index)) == 0)) { + kdError() << _filename << ":" << _lineNo + << " - Undefined compressed function index " + << index << endl; + return 0; + } + + // there was a check if the used function (returned from KCachegrinds + // model) has the same object and file as here given to us, but that was wrong: + // that holds only if we make this assumption on the model... + } + + return f; +} + + +// make sure that a valid object is set, at least dummy with empty name +void CachegrindLoader::ensureObject() +{ + if (currentObject) return; + + currentObject = _data->object(_emptyString); + currentPartObject = currentObject->partObject(_part); +} + +void CachegrindLoader::setObject(const QString& name) +{ + currentObject = compressedObject(name); + if (!currentObject) { + kdError() << _filename << ":" << _lineNo + << " - Invalid object specification, setting to unknown" << endl; + + currentObject = _data->object(_emptyString); + } + + currentPartObject = currentObject->partObject(_part); + currentFunction = 0; + currentPartFunction = 0; +} + +void CachegrindLoader::setCalledObject(const QString& name) +{ + currentCalledObject = compressedObject(name); + + if (!currentCalledObject) { + kdError() << _filename << ":" << _lineNo + << " - Invalid called specification, setting to unknown" << endl; + + currentCalledObject = _data->object(_emptyString); + } + + currentCalledPartObject = currentCalledObject->partObject(_part); +} + + +// make sure that a valid file is set, at least dummy with empty name +void CachegrindLoader::ensureFile() +{ + if (currentFile) return; + + currentFile = _data->file(_emptyString); + currentPartFile = currentFile->partFile(_part); +} + +void CachegrindLoader::setFile(const QString& name) +{ + currentFile = compressedFile(name); + + if (!currentFile) { + kdWarning() << _filename << ":" << _lineNo + << " - Invalid file specification, setting to unknown" << endl; + + currentFile = _data->file(_emptyString); + } + + currentPartFile = currentFile->partFile(_part); + currentLine = 0; + currentPartLine = 0; +} + +void CachegrindLoader::setCalledFile(const QString& name) +{ + currentCalledFile = compressedFile(name); + + if (!currentCalledFile) { + kdError() << _filename << ":" << _lineNo + << " - Invalid called file specification, setting to unknown" << endl; + + currentCalledFile = _data->file(_emptyString); + } + + currentCalledPartFile = currentCalledFile->partFile(_part); +} + +// make sure that a valid function is set, at least dummy with empty name +void CachegrindLoader::ensureFunction() +{ + if (currentFunction) return; + + kdWarning() << _filename << ":" << _lineNo + << " - Function name not set" << endl; + + ensureFile(); + ensureObject(); + + currentFunction = _data->function(_emptyString, + currentFile, + currentObject); + currentPartFunction = currentFunction->partFunction(_part, + currentPartFile, + currentPartObject); +} + +void CachegrindLoader::setFunction(const QString& name) +{ + ensureFile(); + ensureObject(); + + currentFunction = compressedFunction( name, + currentFile, + currentObject); + + if (!currentFunction) { + kdWarning() << _filename << ":" << _lineNo + << " - Invalid function, setting to unknown" << endl; + + currentFunction = _data->function(_emptyString, + currentFile, + currentObject); + } + + currentPartFunction = currentFunction->partFunction(_part, + currentPartFile, + currentPartObject); + + currentFunctionSource = 0; + currentLine = 0; + currentPartLine = 0; +} + +void CachegrindLoader::setCalledFunction(const QString& name) +{ + // if called object/file not set, use current object/file + if (!currentCalledObject) { + currentCalledObject = currentObject; + currentCalledPartObject = currentPartObject; + } + + if (!currentCalledFile) { + // !=0 as functions needs file + currentCalledFile = currentFile; + currentCalledPartFile = currentPartFile; + } + + currentCalledFunction = compressedFunction(name, + currentCalledFile, + currentCalledObject); + if (!currentCalledFunction) { + kdWarning() << _filename << ":" << _lineNo + << " - Invalid called function, setting to unknown" << endl; + + currentCalledFunction = _data->function(_emptyString, + currentCalledFile, + currentCalledObject); + } + + currentCalledPartFunction = + currentCalledFunction->partFunction(_part, + currentCalledPartFile, + currentCalledPartObject); +} + + +void CachegrindLoader::clearPosition() +{ + currentPos = PositionSpec(); + + // current function/line + currentFunction = 0; + currentPartFunction = 0; + currentFunctionSource = 0; + currentFile = 0; + currentPartFile = 0; + currentObject = 0; + currentPartObject = 0; + currentLine = 0; + currentPartLine = 0; + currentInstr = 0; + currentPartInstr = 0; + + // current call + currentCalledObject = 0; + currentCalledPartObject = 0; + currentCalledFile = 0; + currentCalledPartFile = 0; + currentCalledFunction = 0; + currentCalledPartFunction = 0; + currentCallCount = 0; + + // current jump + currentJumpToFile = 0; + currentJumpToFunction = 0; + targetPos = PositionSpec(); + jumpsFollowed = 0; + jumpsExecuted = 0; + + subMapping = 0; +} + + +/** + * The main import function... + */ +bool CachegrindLoader::loadTraceInternal(TracePart* part) +{ + clearCompression(); + clearPosition(); + + _part = part; + _data = part->data(); + QFile* pFile = part->file(); + + if (!pFile) return false; + + _filename = pFile->name(); + + FixFile file(pFile); + if (!file.exists()) { + kdError() << "File doesn't exist\n" << endl; + return false; + } + kdDebug() << "Loading " << _filename << " ..." << endl; + QString statusMsg = i18n("Loading %1").arg(_filename); + int statusProgress = 0; + emit updateStatus(statusMsg,statusProgress); + + +#if USE_FIXCOST + // FixCost Memory Pool + FixPool* pool = _data->fixPool(); +#endif + + _lineNo = 0; + FixString line; + char c; + bool totalsSet = false; + + // current position + nextLineType = SelfCost; + // default if there's no "positions:" line + hasLineInfo = true; + hasAddrInfo = false; + + while (file.nextLine(line)) { + + _lineNo++; + +#if TRACE_LOADER + kdDebug() << "[CachegrindLoader] " << _filename << ":" << _lineNo + << " - '" << QString(line) << "'" << endl; +#endif + + // if we cannot strip a character, this was an empty line + if (!line.first(c)) continue; + + if (c <= '9') { + + if (c == '#') continue; + + // parse position(s) + if (!parsePosition(line, currentPos)) { + kdError() << _filename << ":" << _lineNo + << " - Invalid position specification ('" + << QString(line) << "')" << endl; + continue; + } + + // go through after big switch + } + else { // if (c > '9') + + line.stripFirst(c); + + /* in order of probability */ + switch(c) { + + case 'f': + + // fl=, fi=, fe= + if (line.stripPrefix("l=") || + line.stripPrefix("i=") || + line.stripPrefix("e=")) { + + setFile(line); + continue; + } + + // fn= + if (line.stripPrefix("n=")) { + + setFunction(line); + + // on a new function, update status + int progress = (int)(100.0 * file.current() / file.len() +.5); + if (progress != statusProgress) { + statusProgress = progress; + + /* When this signal is connected, it most probably + * should lead to GUI update. Thus, when multiple + * "long operations" (like file loading) are in progress, + * this can temporarly switch to another operation. + */ + emit updateStatus(statusMsg,statusProgress); + } + + continue; + } + + break; + + case 'c': + // cob= + if (line.stripPrefix("ob=")) { + setCalledObject(line); + continue; + } + + // cfi= / cfl= + if (line.stripPrefix("fl=") || + line.stripPrefix("fi=")) { + setCalledFile(line); + continue; + } + + // cfn= + if (line.stripPrefix("fn=")) { + + setCalledFunction(line); + continue; + } + + // calls= + if (line.stripPrefix("alls=")) { + // ignore long lines... + line.stripUInt64(currentCallCount); + nextLineType = CallCost; + continue; + } + + // cmd: + if (line.stripPrefix("md:")) { + QString command = QString(line).stripWhiteSpace(); + if (!_data->command().isEmpty() && + _data->command() != command) { + + kdWarning() << _filename << ":" << _lineNo + << " - Redefined command, was '" + << _data->command() + << "'" << endl; + } + _data->setCommand(command); + continue; + } + + // creator: + if (line.stripPrefix("reator:")) { + // ignore ... + continue; + } + + break; + + case 'j': + + // jcnd= + if (line.stripPrefix("cnd=")) { + bool valid; + + valid = line.stripUInt64(jumpsFollowed) && + line.stripPrefix("/") && + line.stripUInt64(jumpsExecuted) && + parsePosition(line, targetPos); + + if (!valid) { + kdError() << _filename << ":" << _lineNo + << " - Invalid jcnd line" << endl; + } + else + nextLineType = CondJump; + continue; + } + + if (line.stripPrefix("ump=")) { + bool valid; + + valid = line.stripUInt64(jumpsExecuted) && + parsePosition(line, targetPos); + + if (!valid) { + kdError() << _filename << ":" << _lineNo + << " - Invalid jump line" << endl; + } + else + nextLineType = BoringJump; + continue; + } + + // jfi= + if (line.stripPrefix("fi=")) { + currentJumpToFile = compressedFile(line); + continue; + } + + // jfn= + if (line.stripPrefix("fn=")) { + + if (!currentJumpToFile) { + // !=0 as functions needs file + currentJumpToFile = currentFile; + } + + currentJumpToFunction = + compressedFunction(line, + currentJumpToFile, + currentObject); + continue; + } + + break; + + case 'o': + + // ob= + if (line.stripPrefix("b=")) { + setObject(line); + continue; + } + + break; + + case '#': + continue; + + case 't': + + // totals: + if (line.stripPrefix("otals:")) continue; + + // thread: + if (line.stripPrefix("hread:")) { + part->setThreadID(QString(line).toInt()); + continue; + } + + // timeframe (BB): + if (line.stripPrefix("imeframe (BB):")) { + part->setTimeframe(line); + continue; + } + + break; + + case 'd': + + // desc: + if (line.stripPrefix("esc:")) { + + line.stripSurroundingSpaces(); + + // desc: Trigger: + if (line.stripPrefix("Trigger:")) { + part->setTrigger(line); + } + + continue; + } + break; + + case 'e': + + // events: + if (line.stripPrefix("vents:")) { + subMapping = _data->mapping()->subMapping(line); + part->setFixSubMapping(subMapping); + continue; + } + + // event:<name>[=<formula>][:<long name>] + if (line.stripPrefix("vent:")) { + line.stripSurroundingSpaces(); + + FixString e, f, l; + if (!line.stripName(e)) { + kdError() << _filename << ":" << _lineNo + << " - Invalid event" << endl; + continue; + } + line.stripSpaces(); + if (!line.stripFirst(c)) continue; + + if (c=='=') f = line.stripUntil(':'); + line.stripSpaces(); + + // add to known cost types + if (line.isEmpty()) line = e; + TraceCostType::add(new TraceCostType(e,line,f)); + continue; + } + break; + + case 'p': + + // part: + if (line.stripPrefix("art:")) { + part->setPartNumber(QString(line).toInt()); + continue; + } + + // pid: + if (line.stripPrefix("id:")) { + part->setProcessID(QString(line).toInt()); + continue; + } + + // positions: + if (line.stripPrefix("ositions:")) { + QString positions(line); + hasLineInfo = (positions.find("line")>=0); + hasAddrInfo = (positions.find("instr")>=0); + continue; + } + break; + + case 'v': + + // version: + if (line.stripPrefix("ersion:")) { + part->setVersion(line); + continue; + } + break; + + case 's': + + // summary: + if (line.stripPrefix("ummary:")) { + if (!subMapping) { + kdError() << "No event line found. Skipping '" << _filename << endl; + return false; + } + + part->totals()->set(subMapping, line); + continue; + } + + case 'r': + + // rcalls= (deprecated) + if (line.stripPrefix("calls=")) { + // handle like normal calls: we need the sum of call count + // recursive cost is discarded in cycle detection + line.stripUInt64(currentCallCount); + nextLineType = CallCost; + + kdDebug() << "WARNING: This trace dump was generated by an old " + "version\n of the call-tree skin. Use a new one!" << endl; + + continue; + } + break; + + default: + break; + } + + kdError() << _filename << ":" << _lineNo + << " - Invalid line '" << c << QString(line) << "'" << endl; + continue; + } + + if (!subMapping) { + kdError() << "No event line found. Skipping '" << _filename << "'" << endl; + return false; + } + + // for a cost line, we always need a current function + ensureFunction(); + + +#if USE_FIXCOST + if (!currentFunctionSource || + (currentFunctionSource->file() != currentFile)) + currentFunctionSource = currentFunction->sourceFile(currentFile, + true); +#else + if (hasAddrInfo) { + if (!currentInstr || + (currentInstr->addr() != currentPos.fromAddr)) { + currentInstr = currentFunction->instr(currentPos.fromAddr, + true); + + if (!currentInstr) { + kdError() << _filename << ":" << _lineNo + << " - Invalid address " + << currentPos.fromAddr.toString() << endl; + + continue; + } + + currentPartInstr = currentInstr->partInstr(part, + currentPartFunction); + } + } + + if (hasLineInfo) { + if (!currentLine || + (currentLine->lineno() != currentPos.fromLine)) { + + currentLine = currentFunction->line(currentFile, + currentPos.fromLine, + true); + currentPartLine = currentLine->partLine(part, + currentPartFunction); + } + if (hasAddrInfo && currentInstr) + currentInstr->setLine(currentLine); + } +#endif + +#if TRACE_LOADER + kdDebug() << _filename << ":" << _lineNo + << endl << " currentInstr " + << (currentInstr ? currentInstr->toString().ascii() : ".") + << endl << " currentLine " + << (currentLine ? currentLine->toString().ascii() : ".") + << "( file " << currentFile->name() << ")" + << endl << " currentFunction " + << currentFunction->prettyName().ascii() + << endl << " currentCalled " + << (currentCalledFunction ? currentCalledFunction->prettyName().ascii() : ".") + << endl; +#endif + + // create cost item + + if (nextLineType == SelfCost) { + +#if USE_FIXCOST + new (pool) FixCost(part, pool, + currentFunctionSource, + currentPos, + currentPartFunction, + line); +#else + if (hasAddrInfo) { + TracePartInstr* partInstr; + partInstr = currentInstr->partInstr(part, currentPartFunction); + + if (hasLineInfo) { + // we need to set <line> back after reading for the line + int l = line.len(); + const char* s = line.ascii(); + + partInstr->addCost(subMapping, line); + line.set(s,l); + } + else + partInstr->addCost(subMapping, line); + } + + if (hasLineInfo) { + TracePartLine* partLine; + partLine = currentLine->partLine(part, currentPartFunction); + partLine->addCost(subMapping, line); + } +#endif + + if (!line.isEmpty()) { + kdError() << _filename << ":" << _lineNo + << " - Garbage at end of cost line ('" + << QString(line) << "')" << endl; + } + } + else if (nextLineType == CallCost) { + nextLineType = SelfCost; + + TraceCall* calling = currentFunction->calling(currentCalledFunction); + TracePartCall* partCalling = + calling->partCall(part, currentPartFunction, + currentCalledPartFunction); + +#if USE_FIXCOST + FixCallCost* fcc; + fcc = new (pool) FixCallCost(part, pool, + currentFunctionSource, + hasLineInfo ? currentPos.fromLine : 0, + hasAddrInfo ? currentPos.fromAddr : Addr(0), + partCalling, + currentCallCount, line); + fcc->setMax(_data->callMax()); +#else + if (hasAddrInfo) { + TraceInstrCall* instrCall; + TracePartInstrCall* partInstrCall; + + instrCall = calling->instrCall(currentInstr); + partInstrCall = instrCall->partInstrCall(part, partCalling); + partInstrCall->addCallCount(currentCallCount); + + if (hasLineInfo) { + // we need to set <line> back after reading for the line + int l = line.len(); + const char* s = line.ascii(); + + partInstrCall->addCost(subMapping, line); + line.set(s,l); + } + else + partInstrCall->addCost(subMapping, line); + + // update maximum of call cost + _data->callMax()->maxCost(partInstrCall); + } + + if (hasLineInfo) { + TraceLineCall* lineCall; + TracePartLineCall* partLineCall; + + lineCall = calling->lineCall(currentLine); + partLineCall = lineCall->partLineCall(part, partCalling); + + partLineCall->addCallCount(currentCallCount); + partLineCall->addCost(subMapping, line); + + // update maximum of call cost + _data->callMax()->maxCost(partLineCall); + } +#endif + currentCalledFile = 0; + currentCalledPartFile = 0; + currentCalledObject = 0; + currentCalledPartObject = 0; + currentCallCount = 0; + + if (!line.isEmpty()) { + kdError() << _filename << ":" << _lineNo + << " - Garbage at end of call cost line ('" + << QString(line) << "')" << endl; + } + } + else { // (nextLineType == BoringJump || nextLineType == CondJump) + + TraceFunctionSource* targetSource; + + if (!currentJumpToFunction) + currentJumpToFunction = currentFunction; + + targetSource = (currentJumpToFile) ? + currentJumpToFunction->sourceFile(currentJumpToFile, true) : + currentFunctionSource; + +#if USE_FIXCOST + new (pool) FixJump(part, pool, + /* source */ + hasLineInfo ? currentPos.fromLine : 0, + hasAddrInfo ? currentPos.fromAddr : 0, + currentPartFunction, + currentFunctionSource, + /* target */ + hasLineInfo ? targetPos.fromLine : 0, + hasAddrInfo ? targetPos.fromAddr : Addr(0), + currentJumpToFunction, + targetSource, + (nextLineType == CondJump), + jumpsExecuted, jumpsFollowed); +#endif + + if (0) { + kdDebug() << _filename << ":" << _lineNo + << " - jump from 0x" << currentPos.fromAddr.toString() + << " (line " << currentPos.fromLine + << ") to 0x" << targetPos.fromAddr.toString() + << " (line " << targetPos.fromLine << ")" << endl; + + if (nextLineType == BoringJump) + kdDebug() << " Boring Jump, count " << jumpsExecuted.pretty() << endl; + else + kdDebug() << " Cond. Jump, followed " << jumpsFollowed.pretty() + << ", executed " << jumpsExecuted.pretty() << endl; + } + + nextLineType = SelfCost; + currentJumpToFunction = 0; + currentJumpToFile = 0; + + if (!line.isEmpty()) { + kdError() << _filename << ":" << _lineNo + << " - Garbage at end of jump cost line ('" + << QString(line) << "')" << endl; + } + + } + } + + + emit updateStatus(statusMsg,100); + + _part->invalidate(); + if (!totalsSet) { + _part->totals()->clear(); + _part->totals()->addCost(_part); + } + + pFile->close(); + + return true; +} + diff --git a/kcachegrind/kcachegrind/callgraphview.cpp b/kcachegrind/kcachegrind/callgraphview.cpp new file mode 100644 index 00000000..f00efd94 --- /dev/null +++ b/kcachegrind/kcachegrind/callgraphview.cpp @@ -0,0 +1,2734 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Callgraph View + */ + +#include <stdlib.h> +#include <math.h> + +#include <qtooltip.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qwhatsthis.h> +#include <qcanvas.h> +#include <qwmatrix.h> +#include <qpair.h> +#include <qpainter.h> +#include <qpopupmenu.h> +#include <qstyle.h> +#include <qprocess.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kfiledialog.h> + +#include "configuration.h" +#include "callgraphview.h" +#include "toplevel.h" +#include "listutils.h" + + +/* + * TODO: + * - Zooming option for work canvas? (e.g. 1:1 - 1:3) + */ + +#define DEBUG_GRAPH 0 + +// CallGraphView defaults + +#define DEFAULT_FUNCLIMIT .05 +#define DEFAULT_CALLLIMIT .05 +#define DEFAULT_MAXCALLER 2 +#define DEFAULT_MAXCALLING -1 +#define DEFAULT_SHOWSKIPPED false +#define DEFAULT_EXPANDCYCLES false +#define DEFAULT_CLUSTERGROUPS false +#define DEFAULT_DETAILLEVEL 1 +#define DEFAULT_LAYOUT GraphOptions::TopDown +#define DEFAULT_ZOOMPOS Auto + + +// +// GraphEdgeList +// + +GraphEdgeList::GraphEdgeList() + : _sortCallerPos(true) +{} + +int GraphEdgeList::compareItems(Item item1, Item item2) +{ + CanvasEdge* e1 = ((GraphEdge*)item1)->canvasEdge(); + CanvasEdge* e2 = ((GraphEdge*)item2)->canvasEdge(); + + // edges without arrow visualisations are sorted as low + if (!e1) return -1; + if (!e2) return 1; + + int dx1, dy1, dx2, dy2; + int x, y; + if (_sortCallerPos) { + e1->controlPoints().point(0,&x,&y); + e2->controlPoints().point(0,&dx1,&dy1); + dx1 -= x; dy1 -= y; + } + else { + QPointArray a1 = e1->controlPoints(); + QPointArray a2 = e2->controlPoints(); + a1.point(a1.count()-2,&x,&y); + a2.point(a2.count()-1,&dx2,&dy2); + dx2 -= x; dy2 -= y; + } + double at1 = atan2(double(dx1), double(dy1)); + double at2 = atan2(double(dx2), double(dy2)); + + return (at1 < at2) ? 1:-1; +} + + + + +// +// GraphNode +// + +GraphNode::GraphNode() +{ + _f=0; + self = incl = 0; + _cn = 0; + + _visible = false; + _lastCallerIndex = _lastCallingIndex = -1; + + callers.setSortCallerPos(false); + callings.setSortCallerPos(true); + _lastFromCaller = true; +} + +TraceCall* GraphNode::visibleCaller() +{ + if (0) qDebug("GraphNode::visibleCaller %s: last %d, count %d", + _f->prettyName().ascii(), _lastCallerIndex, callers.count()); + + GraphEdge* e = callers.at(_lastCallerIndex); + if (e && !e->isVisible()) e = 0; + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + int idx = 0; + for(e = callers.first();e; e=callers.next(),idx++) + if (e->isVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCallerIndex = idx; + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +TraceCall* GraphNode::visibleCalling() +{ + if (0) qDebug("GraphNode::visibleCalling %s: last %d, count %d", + _f->prettyName().ascii(), _lastCallingIndex, callings.count()); + + GraphEdge* e = callings.at(_lastCallingIndex); + if (e && !e->isVisible()) e = 0; + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + int idx = 0; + for(e = callings.first();e; e=callings.next(),idx++) + if (e->isVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCallingIndex = idx; + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +void GraphNode::setCalling(GraphEdge* e) +{ + _lastCallingIndex = callings.findRef(e); + _lastFromCaller = false; +} + +void GraphNode::setCaller(GraphEdge* e) +{ + _lastCallerIndex = callers.findRef(e); + _lastFromCaller = true; +} + +TraceFunction* GraphNode::nextVisible() +{ + TraceCall* c; + if (_lastFromCaller) { + c = nextVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + c = nextVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + } + else { + c = nextVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + c = nextVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + } + return 0; +} + +TraceFunction* GraphNode::priorVisible() +{ + TraceCall* c; + if (_lastFromCaller) { + c = priorVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + c = priorVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + } + else { + c = priorVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + c = priorVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCaller(GraphEdge* last) +{ + GraphEdge* e; + bool found = false; + int idx = 0; + for(e = callers.first();e; e=callers.next(),idx++) { + if (found && e->isVisible()) { + _lastCallerIndex = idx; + return e->call(); + } + if (e == last) found = true; + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCalling(GraphEdge* last) +{ + GraphEdge* e; + bool found = false; + int idx = 0; + for(e = callings.first();e; e=callings.next(),idx++) { + if (found && e->isVisible()) { + _lastCallingIndex = idx; + return e->call(); + } + if (e == last) found = true; + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCaller(GraphEdge* last) +{ + GraphEdge *e, *prev = 0; + int prevIdx = -1, idx = 0; + for(e = callers.first(); e; e=callers.next(),idx++) { + if (e == last) { + _lastCallerIndex = prevIdx; + return prev ? prev->call() : 0; + } + if (e->isVisible()) { + prev = e; + prevIdx = idx; + } + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCalling(GraphEdge* last) +{ + GraphEdge *e, *prev = 0; + int prevIdx = -1, idx = 0; + for(e = callings.first(); e; e=callings.next(),idx++) { + if (e == last) { + _lastCallingIndex = prevIdx; + return prev ? prev->call() : 0; + } + if (e->isVisible()) { + prev = e; + prevIdx = idx; + } + } + return 0; +} + +// +// GraphEdge +// + +GraphEdge::GraphEdge() +{ + _c=0; + _from = _to = 0; + _fromNode = _toNode = 0; + cost = count = 0; + _ce = 0; + + _visible = false; + _lastFromCaller = true; +} + +QString GraphEdge::prettyName() +{ + if (_c) return _c->prettyName(); + if (_from) return i18n("Call(s) from %1").arg(_from->prettyName()); + if (_to) return i18n("Call(s) to %1").arg(_to->prettyName()); + return i18n("(unknown call)"); +} + + +TraceFunction* GraphEdge::visibleCaller() +{ + if (_from) { + _lastFromCaller = true; + if (_fromNode) _fromNode->setCalling(this); + return _from; + } + return 0; +} + +TraceFunction* GraphEdge::visibleCalling() +{ + if (_to) { + _lastFromCaller = false; + if (_toNode) _toNode->setCaller(this); + return _to; + } + return 0; +} + +TraceCall* GraphEdge::nextVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->nextVisibleCalling(this); + if (!res && _toNode) + res = _toNode->nextVisibleCaller(this); + } + else if (_toNode) { + res = _toNode->nextVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->nextVisibleCalling(this); + } + return res; +} + +TraceCall* GraphEdge::priorVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->priorVisibleCalling(this); + if (!res && _toNode) + res = _toNode->priorVisibleCaller(this); + } + else if (_toNode) { + res = _toNode->priorVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->priorVisibleCalling(this); + } + return res; +} + + + +// +// GraphOptions +// + +QString GraphOptions::layoutString(Layout l) +{ + if (l == Circular) return QString("Circular"); + if (l == LeftRight) return QString("LeftRight"); + return QString("TopDown"); +} + +GraphOptions::Layout GraphOptions::layout(QString s) +{ + if (s == QString("Circular")) return Circular; + if (s == QString("LeftRight")) return LeftRight; + return TopDown; +} + + +// +// StorableGraphOptions +// + +StorableGraphOptions::StorableGraphOptions() +{ + // default options + _funcLimit = DEFAULT_FUNCLIMIT; + _callLimit = DEFAULT_CALLLIMIT; + _maxCallerDepth = DEFAULT_MAXCALLER; + _maxCallingDepth = DEFAULT_MAXCALLING; + _showSkipped = DEFAULT_SHOWSKIPPED; + _expandCycles = DEFAULT_EXPANDCYCLES; + _detailLevel = DEFAULT_DETAILLEVEL; + _layout = DEFAULT_LAYOUT; +} + + + + +// +// GraphExporter +// + +GraphExporter::GraphExporter() +{ + _go = this; + _tmpFile = 0; + _item = 0; + reset(0, 0, 0, TraceItem::NoCostType, QString::null); +} + + +GraphExporter::GraphExporter(TraceData* d, TraceFunction* f, TraceCostType* ct, + TraceItem::CostType gt, QString filename) +{ + _go = this; + _tmpFile = 0; + _item = 0; + reset(d, f, ct, gt, filename); +} + + +GraphExporter::~GraphExporter() +{ + if (_item && _tmpFile) { +#if DEBUG_GRAPH + _tmpFile->unlink(); +#endif + delete _tmpFile; + } +} + + +void GraphExporter::reset(TraceData*, TraceItem* i, TraceCostType* ct, + TraceItem::CostType gt, QString filename) +{ + _graphCreated = false; + _nodeMap.clear(); + _edgeMap.clear(); + + if (_item && _tmpFile) { + _tmpFile->unlink(); + delete _tmpFile; + } + + if (i) { + switch(i->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + break; + default: + i = 0; + } + } + + _item = i; + _costType = ct; + _groupType = gt; + if (!i) return; + + if (filename.isEmpty()) { + _tmpFile = new KTempFile(QString::null, ".dot"); + _dotName = _tmpFile->name(); + _useBox = true; + } + else { + _tmpFile = 0; + _dotName = filename; + _useBox = false; + } +} + + + +void GraphExporter::setGraphOptions(GraphOptions* go) +{ + if (go == 0) go = this; + _go = go; +} + +void GraphExporter::createGraph() +{ + if (!_item) return; + if (_graphCreated) return; + _graphCreated = true; + + if ((_item->type() == TraceItem::Function) || + (_item->type() == TraceItem::FunctionCycle)) { + TraceFunction* f = (TraceFunction*) _item; + + double incl = f->inclusive()->subCost(_costType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = incl * _go->callLimit(); + + buildGraph(f, 0, true, 1.0); // down to callings + + // set costs of function back to 0, as it will be added again + GraphNode& n = _nodeMap[f]; + n.self = n.incl = 0.0; + + buildGraph(f, 0, false, 1.0); // up to callers + } + else { + TraceCall* c = (TraceCall*) _item; + + double incl = c->subCost(_costType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = incl * _go->callLimit(); + + // create edge + TraceFunction *caller, *called; + caller = c->caller(false); + called = c->called(false); + QPair<TraceFunction*,TraceFunction*> p(caller, called); + GraphEdge& e = _edgeMap[p]; + e.setCall(c); + e.setCaller(p.first); + e.setCalling(p.second); + e.cost = c->subCost(_costType); + e.count = c->callCount(); + + SubCost s = called->inclusive()->subCost(_costType); + buildGraph(called, 0, true, e.cost / s); // down to callings + s = caller->inclusive()->subCost(_costType); + buildGraph(caller, 0, false, e.cost / s); // up to callers + } +} + +void GraphExporter::writeDot() +{ + if (!_item) return; + + QFile* file = 0; + QTextStream* stream = 0; + + if (_tmpFile) + stream = _tmpFile->textStream(); + else { + file = new QFile(_dotName); + if ( !file->open( IO_WriteOnly ) ) { + kdError() << "Can't write dot file '" << _dotName << "'" << endl; + return; + } + stream = new QTextStream(file); + } + + if (!_graphCreated) createGraph(); + + /* Generate dot format... + * When used for the CallGraphView (in contrast to "Export Callgraph..."), + * the labels are only dummy placeholders to reserve space for our own + * drawings. + */ + + *stream << "digraph \"callgraph\" {\n"; + + if (_go->layout() == LeftRight) { + *stream << QString(" rankdir=LR;\n"); + } + else if (_go->layout() == Circular) { + TraceFunction *f = 0; + switch(_item->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + f = (TraceFunction*) _item; + break; + case TraceItem::Call: + f = ((TraceCall*)_item)->caller(true); + break; + default: + break; + } + if (f) + *stream << QString(" center=F%1;\n").arg((long)f, 0, 16); + *stream << QString(" overlap=false;\n splines=true;\n"); + } + + // for clustering + QMap<TraceCostItem*,QPtrList<GraphNode> > nLists; + + GraphNodeMap::Iterator nit; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + + if (n.incl <= _realFuncLimit) continue; + + // for clustering: get cost item group of function + TraceCostItem* g; + TraceFunction* f = n.function(); + switch(_groupType) { + case TraceItem::Object: g = f->object(); break; + case TraceItem::Class: g = f->cls(); break; + case TraceItem::File: g = f->file(); break; + case TraceItem::FunctionCycle: g = f->cycle(); break; + default: g = 0; break; + } + nLists[g].append(&n); + } + + QMap<TraceCostItem*,QPtrList<GraphNode> >::Iterator lit; + int cluster = 0; + for ( lit = nLists.begin(); + lit != nLists.end(); ++lit, cluster++ ) { + QPtrList<GraphNode>& l = lit.data(); + TraceCostItem* i = lit.key(); + + if (_go->clusterGroups() && i) { + QString iabr = i->prettyName(); + if ((int)iabr.length() > Configuration::maxSymbolLength()) + iabr = iabr.left(Configuration::maxSymbolLength()) + "..."; + + *stream << QString("subgraph \"cluster%1\" { label=\"%2\";\n") + .arg(cluster).arg(iabr); + } + + GraphNode* np; + for(np = l.first(); np; np = l.next() ) { + TraceFunction* f = np->function(); + + QString abr = f->prettyName(); + if ((int)abr.length() > Configuration::maxSymbolLength()) + abr = abr.left(Configuration::maxSymbolLength()) + "..."; + + *stream << QString(" F%1 [").arg((long)f, 0, 16); + if (_useBox) { + // make label 3 lines for CallGraphView + *stream << QString("shape=box,label=\"** %1 **\\n**\\n%2\"];\n") + .arg(abr) + .arg(SubCost(np->incl).pretty()); + } + else + *stream << QString("label=\"%1\\n%2\"];\n") + .arg(abr) + .arg(SubCost(np->incl).pretty()); + } + + if (_go->clusterGroups() && i) + *stream << QString("}\n"); + } + + GraphEdgeMap::Iterator eit; + for ( eit = _edgeMap.begin(); + eit != _edgeMap.end(); ++eit ) { + GraphEdge& e = *eit; + + if (e.cost < _realCallLimit) continue; + if (!_go->expandCycles()) { + // don't show inner cycle calls + if (e.call()->inCycle()>0) continue; + } + + + GraphNode& from = _nodeMap[e.from()]; + GraphNode& to = _nodeMap[e.to()]; + + e.setCallerNode(&from); + e.setCallingNode(&to); + + if ((from.incl <= _realFuncLimit) || + (to.incl <= _realFuncLimit)) continue; + + // remove dumped edges from n.callers/n.callings + from.callings.removeRef(&e); + to.callers.removeRef(&e); + from.callingSet.remove(&e); + to.callerSet.remove(&e); + + *stream << QString(" F%1 -> F%2 [weight=%3") + .arg((long)e.from(), 0, 16) + .arg((long)e.to(), 0, 16) + .arg((long)log(log(e.cost))); + + if (_go->detailLevel() ==1) + *stream << QString(",label=\"%1\"") + .arg(SubCost(e.cost).pretty()); + else if (_go->detailLevel() ==2) + *stream << QString(",label=\"%3\\n%4 x\"") + .arg(SubCost(e.cost).pretty()) + .arg(SubCost(e.count).pretty()); + + *stream << QString("];\n"); + } + + if (_go->showSkipped()) { + + // Create sum-edges for skipped edges + GraphEdge* e; + double costSum, countSum; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + if (n.incl <= _realFuncLimit) continue; + + costSum = countSum = 0.0; + for (e=n.callers.first();e;e=n.callers.next()) { + costSum += e->cost; + countSum += e->count; + } + if (costSum > _realCallLimit) { + + QPair<TraceFunction*,TraceFunction*> p(0, n.function()); + e = &(_edgeMap[p]); + e->setCalling(p.second); + e->cost = costSum; + e->count = countSum; + + *stream << QString(" R%1 [shape=point,label=\"\"];\n") + .arg((long)n.function(), 0, 16); + *stream << QString(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((long)n.function(), 0, 16) + .arg((long)n.function(), 0, 16) + .arg(SubCost(costSum).pretty()) + .arg(SubCost(countSum).pretty()) + .arg((int)log(costSum)); + } + + costSum = countSum = 0.0; + for (e=n.callings.first();e;e=n.callings.next()) { + costSum += e->cost; + countSum += e->count; + } + if (costSum > _realCallLimit) { + + QPair<TraceFunction*,TraceFunction*> p(n.function(), 0); + e = &(_edgeMap[p]); + e->setCaller(p.first); + e->cost = costSum; + e->count = countSum; + + *stream << QString(" S%1 [shape=point,label=\"\"];\n") + .arg((long)n.function(), 0, 16); + *stream << QString(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((long)n.function(), 0, 16) + .arg((long)n.function(), 0, 16) + .arg(SubCost(costSum).pretty()) + .arg(SubCost(countSum).pretty()) + .arg((int)log(costSum)); + } + } + } + + // clear edges here completely. + // Visible edges are inserted again on parsing in CallGraphView::refresh + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + n.callers.clear(); + n.callings.clear(); + n.callerSet.clear(); + n.callingSet.clear(); + } + + *stream << "}\n"; + + if (_tmpFile) { + _tmpFile->close(); + } + else { + file->close(); + delete file; + delete stream; + } +} + +void GraphExporter::sortEdges() +{ + GraphNodeMap::Iterator nit; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + + n.callers.sort(); + n.callings.sort(); + } +} + +TraceFunction* GraphExporter::toFunc(QString s) +{ + if (s[0] != 'F') return 0; + bool ok; + TraceFunction* f = (TraceFunction*) s.mid(1).toULong(&ok, 16); + if (!ok) return 0; + + return f; +} + +GraphNode* GraphExporter::node(TraceFunction* f) +{ + if (!f) return 0; + + GraphNodeMap::Iterator it = _nodeMap.find(f); + if (it == _nodeMap.end()) return 0; + + return &(*it); +} + +GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2) +{ + GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2)); + if (it == _edgeMap.end()) return 0; + + return &(*it); +} + + +/** + * We do a DFS and don't stop on already visited nodes/edges, + * but add up costs. We only stop if limits/max depth is reached. + * + * For a node/edge, it can happen that the first time visited the + * cost will below the limit, so the search is stopped. + * If on a further visit of the node/edge the limit is reached, + * we use the whole node/edge cost and continue search. + */ +void GraphExporter::buildGraph(TraceFunction* f, int d, + bool toCallings, double factor) +{ +#if DEBUG_GRAPH + kdDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor + << ") [to " << (toCallings ? "Callings":"Callers") << "]" << endl; +#endif + + double oldIncl = 0.0; + GraphNode& n = _nodeMap[f]; + if (n.function() == 0) { + n.setFunction(f); + } + else + oldIncl = n.incl; + + double incl = f->inclusive()->subCost(_costType) * factor; + n.incl += incl; + n.self += f->subCost(_costType) * factor; + if (0) qDebug(" Added Incl. %f, now %f", incl, n.incl); + + // A negative depth limit means "unlimited" + int maxDepth = toCallings ? _go->maxCallingDepth() : _go->maxCallerDepth(); + if ((maxDepth>=0) && (d >= maxDepth)) { + if (0) qDebug(" Cutoff, max depth reached"); + return; + } + + // if we just reached the limit by summing, do a DFS + // from here with full incl. cost because of previous cutoffs + if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit)) incl = n.incl; + + if (f->cycle()) { + // for cycles members, we never stop on first visit, but always on 2nd + // note: a 2nd visit never should happen, as we don't follow inner-cycle + // calls + if (oldIncl > 0.0) { + if (0) qDebug(" Cutoff, 2nd visit to Cycle Member"); + // and takeback cost addition, as it's added twice + n.incl = oldIncl; + n.self -= f->subCost(_costType) * factor; + return; + } + } + else if (incl <= _realFuncLimit) { + if (0) qDebug(" Cutoff, below limit"); + return; + } + + TraceCall* call; + TraceFunction* f2; + + + // on entering a cycle, only go the FunctionCycle + TraceCallList l = toCallings ? + f->callings(false) : f->callers(false); + + for (call=l.first();call;call=l.next()) { + + f2 = toCallings ? call->called(false) : call->caller(false); + + double count = call->callCount() * factor; + double cost = call->subCost(_costType) * factor; + + // ignore function calls with absolute cost < 3 per call + // No: This would skip a lot of functions e.g. with L2 cache misses + // if (count>0.0 && (cost/count < 3)) continue; + + double oldCost = 0.0; + QPair<TraceFunction*,TraceFunction*> p(toCallings ? f:f2, + toCallings ? f2:f); + GraphEdge& e = _edgeMap[p]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(p.first); + e.setCalling(p.second); + } + else + oldCost = e.cost; + + e.cost += cost; + e.count += count; + if (0) qDebug(" Edge to %s, added cost %f, now %f", + f2->prettyName().ascii(), cost, e.cost); + + // if this call goes into a FunctionCycle, we also show the real call + if (f2->cycle() == f2) { + TraceFunction* realF; + realF = toCallings ? call->called(true) : call->caller(true); + QPair<TraceFunction*,TraceFunction*> realP(toCallings ? f:realF, + toCallings ? realF:f); + GraphEdge& e = _edgeMap[realP]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(realP.first); + e.setCalling(realP.second); + } + e.cost += cost; + e.count += count; + } + + // - don't do a DFS on calls in recursion/cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (toCallings) { + GraphEdgeSet::Iterator it = n.callingSet.find(&e); + if (it == n.callingSet.end()) { + n.callings.append(&e); + n.callingSet.insert(&e, 1 ); + } + } + else { + GraphEdgeSet::Iterator it = n.callerSet.find(&e); + if (it == n.callerSet.end()) { + n.callers.append(&e); + n.callerSet.insert(&e, 1 ); + } + } + + // if we just reached the call limit (=func limit by summing, do a DFS + // from here with full incl. cost because of previous cutoffs + if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit)) cost = e.cost; + if (cost < _realCallLimit) { + if (0) qDebug(" Edge Cutoff, limit not reached"); + continue; + } + + SubCost s; + if (call->inCycle()) + s = f2->cycle()->inclusive()->subCost(_costType); + else + s = f2->inclusive()->subCost(_costType); + SubCost v = call->subCost(_costType); + buildGraph(f2, d+1, toCallings, factor * v / s); + } +} + + +// +// PannerView +// +PannerView::PannerView(QWidget * parent, const char * name) + : QCanvasView(parent, name, WNoAutoErase | WStaticContents) +{ + _movingZoomRect = false; + + // why doesn't this avoid flicker ? + viewport()->setBackgroundMode(Qt::NoBackground); + setBackgroundMode(Qt::NoBackground); +} + +void PannerView::setZoomRect(QRect r) +{ + QRect oldRect = _zoomRect; + _zoomRect = r; + updateContents(oldRect); + updateContents(_zoomRect); +} + +void PannerView::drawContents(QPainter * p, int clipx, int clipy, int clipw, int cliph) +{ + // save/restore around QCanvasView::drawContents seems to be needed + // for QT 3.0 to get the red rectangle drawn correct + p->save(); + QCanvasView::drawContents(p,clipx,clipy,clipw,cliph); + p->restore(); + if (_zoomRect.isValid()) { + p->setPen(red.dark()); + p->drawRect(_zoomRect); + p->setPen(red); + p->drawRect(QRect(_zoomRect.x()+1, _zoomRect.y()+1, + _zoomRect.width()-2, _zoomRect.height()-2)); + } +} + +void PannerView::contentsMousePressEvent(QMouseEvent* e) +{ + if (_zoomRect.isValid()) { + if (!_zoomRect.contains(e->pos())) + emit zoomRectMoved(e->pos().x() - _zoomRect.center().x(), + e->pos().y() - _zoomRect.center().y()); + + _movingZoomRect = true; + _lastPos = e->pos(); + } +} + +void PannerView::contentsMouseMoveEvent(QMouseEvent* e) +{ + if (_movingZoomRect) { + emit zoomRectMoved(e->pos().x() - _lastPos.x(), e->pos().y() - _lastPos.y()); + _lastPos = e->pos(); + } +} + +void PannerView::contentsMouseReleaseEvent(QMouseEvent*) +{ + _movingZoomRect = false; + emit zoomRectMoveFinished(); +} + + + + + +// +// CanvasNode +// + +CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, + int x, int y, int w, int h, QCanvas* c) + : QCanvasRectangle(x, y, w, h, c), _node(n), _view(v) +{ + setPosition(0, DrawParams::TopCenter); + setPosition(1, DrawParams::BottomCenter); + + updateGroup(); + + if (!_node || !_view) return; + + if (_node->function()) + setText(0, _node->function()->prettyName()); + + TraceCost* totalCost; + if (_view->topLevel()->showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } + else + totalCost = (TraceCost*) _view->activeItem(); + } + else + totalCost = _view->data(); + double total = totalCost->subCost(_view->costType()); + double inclP = 100.0 * n->incl / total; + if (_view->topLevel()->showPercentage()) + setText(1, QString("%1 %") + .arg(inclP, 0, 'f', Configuration::percentPrecision())); + else + setText(1, SubCost(n->incl).pretty()); + setPixmap(1, percentagePixmap(25,10,(int)(inclP+.5), Qt::blue, true)); +} + +void CanvasNode::setSelected(bool s) +{ + StoredDrawParams::setSelected(s); + update(); +} + +void CanvasNode::updateGroup() +{ + if (!_view || !_node) return; + + QColor c = Configuration::functionColor(_view->groupType(), + _node->function()); + setBackColor(c); + update(); +} + +void CanvasNode::drawShape(QPainter& p) +{ + QRect r = rect(), origRect = r; + + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + + RectDrawing d(r); + d.drawBack(&p, this); + r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4); + + if (StoredDrawParams::selected() && _view->hasFocus()) { + _view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r, + _view->colorGroup()); + } + + // draw afterwards to always get a frame even when zoomed + p.setPen(StoredDrawParams::selected() ? red : black); + p.drawRect(origRect); + + d.setRect(r); + d.drawField(&p, 0, this); + d.drawField(&p, 1, this); +} + + +// +// CanvasEdgeLabel +// + +CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, + int x, int y, int w, int h, QCanvas* c) + : QCanvasRectangle(x, y, w, h, c), _ce(ce), _view(v) +{ + GraphEdge* e = ce->edge(); + if (!e) return; + + setPosition(1, DrawParams::TopCenter); + setText(1, QString("%1 x").arg(SubCost(e->count).pretty())); + + setPosition(0, DrawParams::BottomCenter); + + TraceCost* totalCost; + if (_view->topLevel()->showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } + else + totalCost = (TraceCost*) _view->activeItem(); + } + else + totalCost = _view->data(); + double total = totalCost->subCost(_view->costType()); + double inclP = 100.0 * e->cost / total; + if (_view->topLevel()->showPercentage()) + setText(0, QString("%1 %") + .arg(inclP, 0, 'f', Configuration::percentPrecision())); + else + setText(0, SubCost(e->cost).pretty()); + setPixmap(0, percentagePixmap(25,10,(int)(inclP+.5), Qt::blue, true)); + + if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) { + QString icon = "undo"; + KIconLoader* loader = KApplication::kApplication()->iconLoader(); + QPixmap p= loader->loadIcon(icon, KIcon::Small, 0, + KIcon::DefaultState, 0, true); + setPixmap(0, p); + } +} + +void CanvasEdgeLabel::drawShape(QPainter& p) +{ + QRect r = rect(); + //p.setPen(blue); + //p.drawRect(r); + RectDrawing d(r); + d.drawField(&p, 0, this); + d.drawField(&p, 1, this); +} + +// +// CanvasEdgeArrow + +CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce, QCanvas* c) + : QCanvasPolygon(c), _ce(ce) +{} + +void CanvasEdgeArrow::drawShape(QPainter& p) +{ + if (_ce->isSelected()) p.setBrush(Qt::red); + + QCanvasPolygon::drawShape(p); +} + +// +// CanvasEdge +// + +CanvasEdge::CanvasEdge(GraphEdge* e, QCanvas* c) + : QCanvasSpline(c), _edge(e) +{ + _label = 0; + _arrow = 0; +} + +void CanvasEdge::setSelected(bool s) +{ + QCanvasItem::setSelected(s); + update(); + if (_arrow) _arrow->setSelected(s); +} + +QPointArray CanvasEdge::areaPoints() const +{ + int minX = poly[0].x(), minY = poly[0].y(); + int maxX = minX, maxY = minY; + int i; + + if (0) qDebug("CanvasEdge::areaPoints\n P 0: %d/%d", minX, minY); + int len = poly.count(); + for (i=1;i<len;i++) { + if (poly[i].x() < minX) minX = poly[i].x(); + if (poly[i].y() < minY) minY = poly[i].y(); + if (poly[i].x() > maxX) maxX = poly[i].x(); + if (poly[i].y() > maxY) maxY = poly[i].y(); + if (0) qDebug(" P %d: %d/%d", i, poly[i].x(), poly[i].y()); + } + QPointArray a = poly.copy(), b = poly.copy(); + if (minX == maxX) { + a.translate(-2, 0); + b.translate(2, 0); + } + else { + a.translate(0, -2); + b.translate(0, 2); + } + a.resize(2*len); + for (i=0;i<len;i++) + a[2 * len - 1 -i] = b[i]; + + if (0) { + qDebug(" Result:"); + for (i=0;i<2*len;i++) + qDebug(" P %d: %d/%d", i, a[i].x(), a[i].y()); + } + + return a; +} + +void CanvasEdge::drawShape(QPainter& p) +{ + if (isSelected()) p.setPen(Qt::red); + + p.drawPolyline(poly); +} + + +// +// CanvasFrame +// + +QPixmap* CanvasFrame::_p = 0; + +CanvasFrame::CanvasFrame(CanvasNode* n, QCanvas* c) + : QCanvasRectangle(c) +{ + if (!_p) { + + int d = 5; + float v1 = 130.0, v2 = 10.0, v = v1, f = 1.03; + + // calculate pix size + QRect r(0, 0, 30, 30); + while (v>v2) { + r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d); + v /= f; + } + + _p = new QPixmap(r.size()); + _p->fill(Qt::white); + QPainter p(_p); + p.setPen(Qt::NoPen); + + r.moveBy(-r.x(), -r.y()); + + while (v<v1) { + v *= f; + p.setBrush(QColor(265-(int)v, 265-(int)v, 265-(int)v)); + + p.drawRect(QRect(r.x(), r.y(), r.width(), d)); + p.drawRect(QRect(r.x(), r.bottom()-d, r.width(), d)); + p.drawRect(QRect(r.x(), r.y()+d, d, r.height()-2*d)); + p.drawRect(QRect(r.right()-d, r.y()+d, d, r.height()-2*d)); + + r.setRect(r.x()+d, r.y()+d, r.width()-2*d, r.height()-2*d); + } + } + + setSize(_p->width(), _p->height()); + move(n->rect().center().x()-_p->width()/2, + n->rect().center().y()-_p->height()/2); +} + + +void CanvasFrame::drawShape(QPainter& p) +{ + p.drawPixmap( int(x()), int(y()), *_p ); +} + + + + +// +// Tooltips for CallGraphView +// + +class CallGraphTip: public QToolTip +{ +public: + CallGraphTip( QWidget* p ):QToolTip(p) {} + +protected: + void maybeTip( const QPoint & ); +}; + +void CallGraphTip::maybeTip( const QPoint& pos ) +{ + if (!parentWidget()->inherits( "CallGraphView" )) return; + CallGraphView* cgv = (CallGraphView*)parentWidget(); + + QPoint cPos = cgv->viewportToContents(pos); + + if (0) qDebug("CallGraphTip for (%d/%d) -> (%d/%d) ?", + pos.x(), pos.y(), cPos.x(), cPos.y()); + + QCanvasItemList l = cgv->canvas()->collisions(cPos); + if (l.count() == 0) return; + QCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + CanvasNode* cn = (CanvasNode*)i; + GraphNode* n = cn->node(); + if (0) qDebug("CallGraphTip: Mouse on Node '%s'", + n->function()->prettyName().ascii()); + + QString tipStr = QString("%1 (%2)").arg(cn->text(0)).arg(cn->text(1)); + QPoint vPosTL = cgv->contentsToViewport(i->boundingRect().topLeft()); + QPoint vPosBR = cgv->contentsToViewport(i->boundingRect().bottomRight()); + tip(QRect(vPosTL, vPosBR), tipStr); + + return; + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + CanvasEdge* ce = (CanvasEdge*)i; + GraphEdge* e = ce->edge(); + if (0) qDebug("CallGraphTip: Mouse on Edge '%s'", + e->prettyName().ascii()); + + QString tipStr; + if (!ce->label()) + tipStr = e->prettyName(); + else + tipStr = QString("%1 (%2)") + .arg(ce->label()->text(0)).arg(ce->label()->text(1)); + tip(QRect(pos.x()-5,pos.y()-5,pos.x()+5,pos.y()+5), tipStr); + } +} + + + + +// +// CallGraphView +// +CallGraphView::CallGraphView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QCanvasView(parent, name), TraceItemView(parentView) +{ + _zoomPosition = DEFAULT_ZOOMPOS; + _lastAutoPosition = TopLeft; + + _canvas = 0; + _xMargin = _yMargin = 0; + _completeView = new PannerView(this); + _cvZoom = 1; + _selectedNode = 0; + _selectedEdge = 0; + + _exporter.setGraphOptions(this); + + _completeView->setVScrollBarMode(QScrollView::AlwaysOff); + _completeView->setHScrollBarMode(QScrollView::AlwaysOff); + _completeView->raise(); + _completeView->hide(); + + setFocusPolicy(QWidget::StrongFocus); + setBackgroundMode(Qt::NoBackground); + + connect(this, SIGNAL(contentsMoving(int,int)), + this, SLOT(contentsMovingSlot(int,int))); + connect(_completeView, SIGNAL(zoomRectMoved(int,int)), + this, SLOT(zoomRectMoved(int,int))); + connect(_completeView, SIGNAL(zoomRectMoveFinished()), + this, SLOT(zoomRectMoveFinished())); + + QWhatsThis::add( this, whatsThis() ); + + // tooltips... + _tip = new CallGraphTip(this); + + _renderProcess = 0; + _prevSelectedNode = 0; + connect(&_renderTimer, SIGNAL(timeout()), + this, SLOT(showRenderWarning())); +} + +CallGraphView::~CallGraphView() +{ + delete _completeView; + delete _tip; + + if (_canvas) { + setCanvas(0); + delete _canvas; + } +} + +QString CallGraphView::whatsThis() const +{ + return i18n( "<b>Call Graph around active Function</b>" + "<p>Depending on configuration, this view shows " + "the call graph environment of the active function. " + "Note: the shown cost is <b>only</b> the cost which is " + "spent while the active function was actually running; " + "i.e. the cost shown for main() - if it's visible - should " + "be the same as the cost of the active function, as that's " + "the part of inclusive cost of main() spent while the active " + "function was running.</p>" + "<p>For cycles, blue call arrows indicate that this is an " + "artificial call added for correct drawing which " + "actually never happened.</p>" + "<p>If the graph is larger than the widget area, an overview " + "panner is shown in one edge. " + "There are similar visualization options to the " + "Call Treemap; the selected function is highlighted.<p>"); +} + +void CallGraphView::updateSizes(QSize s) +{ + if (!_canvas) return; + + if (s == QSize(0,0)) s = size(); + + // the part of the canvas that should be visible + int cWidth = _canvas->width() - 2*_xMargin + 100; + int cHeight = _canvas->height() - 2*_yMargin + 100; + + // hide birds eye view if no overview needed + if (!_data || !_activeItem || + ((cWidth < s.width()) && cHeight < s.height())) { + _completeView->hide(); + return; + } + _completeView->show(); + + // first, assume use of 1/3 of width/height (possible larger) + double zoom = .33 * s.width() / cWidth; + if (zoom * cHeight < .33 * s.height()) zoom = .33 * s.height() / cHeight; + + // fit to widget size + if (cWidth * zoom > s.width()) zoom = s.width() / (double)cWidth; + if (cHeight * zoom > s.height()) zoom = s.height() / (double)cHeight; + + // scale to never use full height/width + zoom = zoom * 3/4; + + // at most a zoom of 1/3 + if (zoom > .33) zoom = .33; + + if (zoom != _cvZoom) { + _cvZoom = zoom; + if (0) qDebug("Canvas Size: %dx%d, Visible: %dx%d, Zoom: %f", + _canvas->width(), _canvas->height(), + cWidth, cHeight, zoom); + + QWMatrix wm; + wm.scale( zoom, zoom ); + _completeView->setWorldMatrix(wm); + + // make it a little bigger to compensate for widget frame + _completeView->resize(int(cWidth * zoom) + 4, + int(cHeight * zoom) + 4); + + // update ZoomRect in completeView + contentsMovingSlot(contentsX(), contentsY()); + } + + _completeView->setContentsPos(int(zoom*(_xMargin-50)), + int(zoom*(_yMargin-50))); + + int cvW = _completeView->width(); + int cvH = _completeView->height(); + int x = width()- cvW - verticalScrollBar()->width() -2; + int y = height()-cvH - horizontalScrollBar()->height() -2; + QPoint oldZoomPos = _completeView->pos(); + QPoint newZoomPos = QPoint(0,0); + ZoomPosition zp = _zoomPosition; + if (zp == Auto) { + QPoint tl1Pos = viewportToContents(QPoint(0,0)); + QPoint tl2Pos = viewportToContents(QPoint(cvW,cvH)); + QPoint tr1Pos = viewportToContents(QPoint(x,0)); + QPoint tr2Pos = viewportToContents(QPoint(x+cvW,cvH)); + QPoint bl1Pos = viewportToContents(QPoint(0,y)); + QPoint bl2Pos = viewportToContents(QPoint(cvW,y+cvH)); + QPoint br1Pos = viewportToContents(QPoint(x,y)); + QPoint br2Pos = viewportToContents(QPoint(x+cvW,y+cvH)); + int tlCols = _canvas->collisions(QRect(tl1Pos,tl2Pos)).count(); + int trCols = _canvas->collisions(QRect(tr1Pos,tr2Pos)).count(); + int blCols = _canvas->collisions(QRect(bl1Pos,bl2Pos)).count(); + int brCols = _canvas->collisions(QRect(br1Pos,br2Pos)).count(); + int minCols = tlCols; + zp = _lastAutoPosition; + switch(zp) { + case TopRight: minCols = trCols; break; + case BottomLeft: minCols = blCols; break; + case BottomRight: minCols = brCols; break; + default: + case TopLeft: minCols = tlCols; break; + } + if (minCols > tlCols) { minCols = tlCols; zp = TopLeft; } + if (minCols > trCols) { minCols = trCols; zp = TopRight; } + if (minCols > blCols) { minCols = blCols; zp = BottomLeft; } + if (minCols > brCols) { minCols = brCols; zp = BottomRight; } + + _lastAutoPosition = zp; + } + + switch(zp) { + case TopRight: + newZoomPos = QPoint(x,0); + break; + case BottomLeft: + newZoomPos = QPoint(0,y); + break; + case BottomRight: + newZoomPos = QPoint(x,y); + break; + default: + break; + } + if (newZoomPos != oldZoomPos) _completeView->move(newZoomPos); +} + +void CallGraphView::focusInEvent(QFocusEvent*) +{ + if (!_canvas) return; + + if (_selectedNode && _selectedNode->canvasNode()) { + _selectedNode->canvasNode()->setSelected(true); // requests item update + _canvas->update(); + } +} + +void CallGraphView::focusOutEvent(QFocusEvent* e) +{ + // trigger updates as in focusInEvent + focusInEvent(e); +} + +void CallGraphView::keyPressEvent(QKeyEvent* e) +{ + if (!_canvas) { + e->ignore(); + return; + } + + if ((e->key() == Key_Return) || + (e->key() == Key_Space)) { + if (_selectedNode) + activated(_selectedNode->function()); + else if (_selectedEdge && _selectedEdge->call()) + activated(_selectedEdge->call()); + return; + } + + // move selected node/edge + if (!(e->state() & (ShiftButton | ControlButton)) && + (_selectedNode || _selectedEdge) && + ((e->key() == Key_Up) || + (e->key() == Key_Down) || + (e->key() == Key_Left) || + (e->key() == Key_Right))) { + + TraceFunction* f = 0; + TraceCall* c = 0; + + // rotate arrow key meaning for LeftRight layout + int key = e->key(); + if (_layout == LeftRight) { + switch(key) { + case Key_Up: key = Key_Left; break; + case Key_Down: key = Key_Right; break; + case Key_Left: key = Key_Up; break; + case Key_Right: key = Key_Down; break; + default: break; + } + } + + if (_selectedNode) { + if (key == Key_Up) c = _selectedNode->visibleCaller(); + if (key == Key_Down) c = _selectedNode->visibleCalling(); + if (key == Key_Right) f = _selectedNode->nextVisible(); + if (key == Key_Left) f = _selectedNode->priorVisible(); + } + else if (_selectedEdge) { + if (key == Key_Up) f = _selectedEdge->visibleCaller(); + if (key == Key_Down) f = _selectedEdge->visibleCalling(); + if (key == Key_Right) c = _selectedEdge->nextVisible(); + if (key == Key_Left) c = _selectedEdge->priorVisible(); + } + + if (c) selected(c); + if (f) selected(f); + return; + } + + // move canvas... + if (e->key() == Key_Home) + scrollBy(-_canvas->width(),0); + else if (e->key() == Key_End) + scrollBy(_canvas->width(),0); + else if (e->key() == Key_Prior) + scrollBy(0,-visibleHeight()/2); + else if (e->key() == Key_Next) + scrollBy(0,visibleHeight()/2); + else if (e->key() == Key_Left) + scrollBy(-visibleWidth()/10,0); + else if (e->key() == Key_Right) + scrollBy(visibleWidth()/10,0); + else if (e->key() == Key_Down) + scrollBy(0,visibleHeight()/10); + else if (e->key() == Key_Up) + scrollBy(0,-visibleHeight()/10); + else e->ignore(); +} + +void CallGraphView::resizeEvent(QResizeEvent* e) +{ + QCanvasView::resizeEvent(e); + if (_canvas) updateSizes(e->size()); +} + +TraceItem* CallGraphView::canShow(TraceItem* i) +{ + if (i) { + switch(i->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + return i; + default: + break; + } + } + return 0; +} + +void CallGraphView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == costType2Changed) return; + + if (changeType == selectedItemChanged) { + if (!_canvas) return; + + if (!_selectedItem) return; + + GraphNode* n = 0; + GraphEdge* e = 0; + if ((_selectedItem->type() == TraceItem::Function) || + (_selectedItem->type() == TraceItem::FunctionCycle)) { + n = _exporter.node((TraceFunction*)_selectedItem); + if (n == _selectedNode) return; + } + else if (_selectedItem->type() == TraceItem::Call) { + TraceCall* c = (TraceCall*)_selectedItem; + e = _exporter.edge(c->caller(false), c->called(false)); + if (e == _selectedEdge) return; + } + + // unselected any selected item + if (_selectedNode && _selectedNode->canvasNode()) { + _selectedNode->canvasNode()->setSelected(false); + } + _selectedNode = 0; + if (_selectedEdge && _selectedEdge->canvasEdge()) { + _selectedEdge->canvasEdge()->setSelected(false); + } + _selectedEdge = 0; + + // select + CanvasNode* sNode = 0; + if (n && n->canvasNode()) { + _selectedNode = n; + _selectedNode->canvasNode()->setSelected(true); + + if (!_isMoving) sNode = _selectedNode->canvasNode(); + } + if (e && e->canvasEdge()) { + _selectedEdge = e; + _selectedEdge->canvasEdge()->setSelected(true); + +#if 0 // don't change position when selecting edge + if (!_isMoving) { + if (_selectedEdge->fromNode()) + sNode = _selectedEdge->fromNode()->canvasNode(); + if (!sNode && _selectedEdge->toNode()) + sNode = _selectedEdge->toNode()->canvasNode(); + } +#endif + } + if (sNode) { + double x = sNode->x() + sNode->width()/2; + double y = sNode->y() + sNode->height()/2; + + ensureVisible(int(x),int(y), + sNode->width()/2+50, sNode->height()/2+50); + } + + _canvas->update(); + return; + } + + if (changeType == groupTypeChanged) { + if (!_canvas) return; + + if (_clusterGroups) { + refresh(); + return; + } + + QCanvasItemList l = _canvas->allItems(); + QCanvasItemList::iterator it; + for (it = l.begin();it != l.end(); ++it) + if ((*it)->rtti() == CANVAS_NODE) + ((CanvasNode*) (*it))->updateGroup(); + + _canvas->update(); + return; + } + + if (changeType & dataChanged) { + // invalidate old selection and graph part + _exporter.reset(_data, _activeItem, _costType, _groupType); + _selectedNode = 0; + _selectedEdge = 0; + } + + refresh(); +} + +void CallGraphView::clear() +{ + if (!_canvas) return; + + delete _canvas; + _canvas = 0; + _completeView->setCanvas(0); + setCanvas(0); +} + +void CallGraphView::showText(QString s) +{ + clear(); + _renderTimer.stop(); + + _canvas = new QCanvas(QApplication::desktop()->width(), + QApplication::desktop()->height()); + + QCanvasText* t = new QCanvasText(s, _canvas); + t->move(5, 5); + t->show(); + center(0,0); + setCanvas(_canvas); + _canvas->update(); + _completeView->hide(); +} + +void CallGraphView::showRenderWarning() +{ + QString s; + + if (_renderProcess) + s =i18n("Warning: a long lasting graph layouting is in progress.\n" + "Reduce node/edge limits for speedup.\n"); + else + s = i18n("Layouting stopped.\n"); + + s.append(i18n("The call graph has %1 nodes and %2 edges.\n") + .arg(_exporter.nodeCount()) + .arg(_exporter.edgeCount())); + + showText(s); +} + +void CallGraphView::stopRendering() +{ + if (!_renderProcess) return; + + _renderProcess->kill(); + delete _renderProcess; + _renderProcess = 0; + _unparsedOutput = QString::null; + + _renderTimer.start(200, true); +} + +void CallGraphView::refresh() +{ + // trigger start of background rendering + if (_renderProcess) stopRendering(); + + // we want to keep a selected node item at the same global position + _prevSelectedNode = _selectedNode; + _prevSelectedPos = QPoint(-1,-1); + if (_selectedNode) { + QPoint center = _selectedNode->canvasNode()->boundingRect().center(); + _prevSelectedPos = contentsToViewport(center); + } + + if (!_data || !_activeItem) { + showText(i18n("No item activated for which to draw the call graph.")); + return; + } + + TraceItem::CostType t = _activeItem->type(); + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + break; + default: + showText(i18n("No call graph can be drawn for the active item.")); + return; + } + + if (1) kdDebug() << "CallGraphView::refresh" << endl; + + _selectedNode = 0; + _selectedEdge = 0; + _exporter.reset(_data, _activeItem, _costType, _groupType); + _exporter.writeDot(); + + _renderProcess = new QProcess(this); + if (_layout == GraphOptions::Circular) + _renderProcess->addArgument( "twopi" ); + else + _renderProcess->addArgument( "dot" ); + _renderProcess->addArgument(_exporter.filename()); + _renderProcess->addArgument( "-Tplain" ); + + connect( _renderProcess, SIGNAL(readyReadStdout()), + this, SLOT(readDotOutput()) ); + connect( _renderProcess, SIGNAL(processExited()), + this, SLOT(dotExited()) ); + + if (1) kdDebug() << "Running '" + << _renderProcess->arguments().join(" ") + << "'..." << endl; + + if ( !_renderProcess->start() ) { + QString e = i18n("No call graph is available because the following\n" + "command cannot be run:\n'%1'\n") + .arg(_renderProcess->arguments().join(" ")); + e += i18n("Please check that 'dot' is installed (package GraphViz)."); + showText(e); + + delete _renderProcess; + _renderProcess = 0; + + return; + } + + _unparsedOutput = QString::null; + + // layouting of more than seconds is dubious + _renderTimer.start(1000, true); +} + +void CallGraphView::readDotOutput() +{ + _unparsedOutput.append( _renderProcess->readStdout() ); +} + +void CallGraphView::dotExited() +{ + QString line, cmd; + CanvasNode *rItem; + QCanvasEllipse* eItem; + CanvasEdge* sItem; + CanvasEdgeLabel* lItem; + QTextStream* dotStream; + double scale = 1.0, scaleX = 1.0, scaleY = 1.0; + double dotWidth, dotHeight; + GraphNode* activeNode = 0; + GraphEdge* activeEdge = 0; + + _renderTimer.stop(); + viewport()->setUpdatesEnabled(false); + clear(); + dotStream = new QTextStream(_unparsedOutput, IO_ReadOnly); + + int lineno = 0; + while (1) { + line = dotStream->readLine(); + if (line.isNull()) break; + lineno++; + if (line.isEmpty()) continue; + + QTextStream lineStream(line, IO_ReadOnly); + lineStream >> cmd; + + if (0) qDebug("%s:%d - line '%s', cmd '%s'", + _exporter.filename().ascii(), lineno, + line.ascii(), cmd.ascii()); + + if (cmd == "stop") break; + + if (cmd == "graph") { + QString dotWidthString, dotHeightString; + lineStream >> scale >> dotWidthString >> dotHeightString; + dotWidth = dotWidthString.toDouble(); + dotHeight = dotHeightString.toDouble(); + + if (_detailLevel == 0) { scaleX = scale * 70; scaleY = scale * 40; } + else if (_detailLevel == 1) { scaleX = scale * 80; scaleY = scale * 70; } + else { scaleX = scale * 60; scaleY = scale * 100; } + + if (!_canvas) { + int w = (int)(scaleX * dotWidth); + int h = (int)(scaleY * dotHeight); + + // We use as minimum canvas size the desktop size. + // Otherwise, the canvas would have to be resized on widget resize. + _xMargin = 50; + if (w < QApplication::desktop()->width()) + _xMargin += (QApplication::desktop()->width()-w)/2; + + _yMargin = 50; + if (h < QApplication::desktop()->height()) + _yMargin += (QApplication::desktop()->height()-h)/2; + + _canvas = new QCanvas(int(w+2*_xMargin), int(h+2*_yMargin)); + +#if DEBUG_GRAPH + kdDebug() << _exporter.filename().ascii() << ":" << lineno + << " - graph (" << dotWidth << " x " << dotHeight + << ") => (" << w << " x " << h << ")" << endl; +#endif + } + else + kdWarning() << "Ignoring 2nd 'graph' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if ((cmd != "node") && (cmd != "edge")) { + kdWarning() << "Ignoring unknown command '" << cmd << "' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if (_canvas == 0) { + kdWarning() << "Ignoring '" << cmd << "' without 'graph' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if (cmd == "node") { + // x, y are centered in node + QString nodeName, label, nodeX, nodeY, nodeWidth, nodeHeight; + double x, y, width, height; + lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth >> nodeHeight; + x = nodeX.toDouble(); + y = nodeY.toDouble(); + width = nodeWidth.toDouble(); + height = nodeHeight.toDouble(); + + GraphNode* n = _exporter.node(_exporter.toFunc(nodeName)); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + int w = (int)(scaleX * width); + int h = (int)(scaleY * height); + +#if DEBUG_GRAPH + kdDebug() << _exporter.filename() << ":" << lineno + << " - node '" << nodeName << "' ( " + << x << "/" << y << " - " + << width << "x" << height << " ) => (" + << xx-w/2 << "/" << yy-h/2 << " - " + << w << "x" << h << ")" << endl; +#endif + + + // Unnamed nodes with collapsed edges (with 'R' and 'S') + if (nodeName[0] == 'R' || nodeName[0] == 'S') { + w = 10, h = 10; + eItem = new QCanvasEllipse(w, h, _canvas); + eItem->move(xx, yy); + eItem->setBrush(Qt::gray); + eItem->setZ(1.0); + eItem->show(); + continue; + } + + if (!n) { + qDebug("Warning: Unknown function '%s' ?!", nodeName.ascii()); + continue; + } + n->setVisible(true); + + rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h, _canvas); + n->setCanvasNode(rItem); + + if (n) { + if (n->function() == activeItem()) activeNode = n; + if (n->function() == selectedItem()) _selectedNode = n; + rItem->setSelected(n == _selectedNode); + } + + rItem->setZ(1.0); + rItem->show(); + + continue; + } + + // edge + + QString node1Name, node2Name, label, edgeX, edgeY; + double x, y; + QPointArray pa; + int points, i; + lineStream >> node1Name >> node2Name >> points; + + GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name), + _exporter.toFunc(node2Name)); + if (!e) { + kdWarning() << "Unknown edge '" << node1Name << "'-'" + << node2Name << "' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + e->setVisible(true); + if (e->fromNode()) e->fromNode()->callings.append(e); + if (e->toNode()) e->toNode()->callers.append(e); + + if (0) qDebug(" Edge with %d points:", points); + + pa.resize(points); + for (i=0;i<points;i++) { + if (lineStream.atEnd()) break; + lineStream >> edgeX >> edgeY; + x = edgeX.toDouble(); + y = edgeY.toDouble(); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + + if (0) qDebug(" P %d: ( %f / %f ) => ( %d / %d)", + i, x, y, xx, yy); + + pa.setPoint(i, xx, yy); + } + if (i < points) { + qDebug("CallGraphView: Can't read %d spline points (%s:%d)", + points, _exporter.filename().ascii(), lineno); + continue; + } + + // calls into/out of cycles are special: make them blue + QColor arrowColor = Qt::black; + TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : 0; + TraceFunction* called = e->toNode() ? e->toNode()->function() : 0; + if ( (caller && (caller->cycle() == caller)) || + (called && (called->cycle() == called)) ) arrowColor = Qt::blue; + + sItem = new CanvasEdge(e, _canvas); + e->setCanvasEdge(sItem); + sItem->setControlPoints(pa, false); + sItem->setPen(QPen(arrowColor, 1 /*(int)log(log(e->cost))*/ )); + sItem->setZ(0.5); + sItem->show(); + + if (e->call() == selectedItem()) _selectedEdge = e; + if (e->call() == activeItem()) activeEdge = e; + sItem->setSelected(e == _selectedEdge); + + // Arrow head + QPoint arrowDir; + int indexHead = -1; + + // check if head is at start of spline... + // this is needed because dot always gives points from top to bottom + CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : 0; + if (fromNode) { + QPoint toCenter = fromNode->rect().center(); + int dx0 = pa.point(0).x() - toCenter.x(); + int dy0 = pa.point(0).y() - toCenter.y(); + int dx1 = pa.point(points-1).x() - toCenter.x(); + int dy1 = pa.point(points-1).y() - toCenter.y(); + if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) { + // start of spline is nearer to call target node + indexHead=-1; + while(arrowDir.isNull() && (indexHead<points-2)) { + indexHead++; + arrowDir = pa.point(indexHead) - pa.point(indexHead+1); + } + } + } + + if (arrowDir.isNull()) { + indexHead = points; + // sometimes the last spline points from dot are the same... + while(arrowDir.isNull() && (indexHead>1)) { + indexHead--; + arrowDir = pa.point(indexHead) - pa.point(indexHead-1); + } + } + + if (!arrowDir.isNull()) { + // arrow around pa.point(indexHead) with direction arrowDir + arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() + + arrowDir.y()*arrowDir.y())); + QPointArray a(3); + a.setPoint(0, pa.point(indexHead) + arrowDir); + a.setPoint(1, pa.point(indexHead) + QPoint(arrowDir.y()/2, + -arrowDir.x()/2)); + a.setPoint(2, pa.point(indexHead) + QPoint(-arrowDir.y()/2, + arrowDir.x()/2)); + + if (0) qDebug(" Arrow: ( %d/%d, %d/%d, %d/%d)", + a.point(0).x(), a.point(0).y(), + a.point(1).x(), a.point(1).y(), + a.point(2).x(), a.point(2).y()); + + CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem,_canvas); + aItem->setPoints(a); + aItem->setBrush(arrowColor); + aItem->setZ(1.5); + aItem->show(); + + sItem->setArrow(aItem); + } + + if (lineStream.atEnd()) continue; + + // parse quoted label + QChar c; + lineStream >> c; + while (c.isSpace()) lineStream >> c; + if (c != '\"') { + lineStream >> label; + label = c + label; + } + else { + lineStream >> c; + while(!c.isNull() && (c != '\"')) { + //if (c == '\\') lineStream >> c; + + label += c; + lineStream >> c; + } + } + lineStream >> edgeX >> edgeY; + x = edgeX.toDouble(); + y = edgeY.toDouble(); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + + if (0) qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)", + label.ascii(), x, y, xx, yy); + + // Fixed Dimensions for Label: 100 x 40 + int w = 100; + int h = _detailLevel * 20; + lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h, _canvas); + // edge labels above nodes + lItem->setZ(1.5); + sItem->setLabel(lItem); + if (h>0) lItem->show(); + + } + delete dotStream; + + // for keyboard navigation + // TODO: Edge sorting. Better keep left-to-right edge order from dot now + // _exporter.sortEdges(); + + if (!_canvas) { + _canvas = new QCanvas(size().width(),size().height()); + QString s = i18n("Error running the graph layouting tool.\n"); + s += i18n("Please check that 'dot' is installed (package GraphViz)."); + QCanvasText* t = new QCanvasText(s, _canvas); + t->move(5, 5); + t->show(); + center(0,0); + } + else if (!activeNode && !activeEdge) { + QString s = i18n("There is no call graph available for function\n" + "\t'%1'\n" + "because it has no cost of the selected event type."); + QCanvasText* t = new QCanvasText(s.arg(_activeItem->name()), _canvas); + // t->setTextFlags(Qt::AlignHCenter | Qt::AlignVCenter); + t->move(5,5); + t->show(); + center(0,0); + } + + _completeView->setCanvas(_canvas); + setCanvas(_canvas); + + // if we don't have a selection, or the old selection is not + // in visible graph, make active function selected for this view + if ((!_selectedNode || !_selectedNode->canvasNode()) && + (!_selectedEdge || !_selectedEdge->canvasEdge())) { + if (activeNode) { + _selectedNode = activeNode; + _selectedNode->canvasNode()->setSelected(true); + } + else if (activeEdge) { + _selectedEdge = activeEdge; + _selectedEdge->canvasEdge()->setSelected(true); + } + } + + CanvasNode* sNode = 0; + if (_selectedNode) + sNode = _selectedNode->canvasNode(); + else if (_selectedEdge) { + if (_selectedEdge->fromNode()) + sNode = _selectedEdge->fromNode()->canvasNode(); + if (!sNode && _selectedEdge->toNode()) + sNode = _selectedEdge->toNode()->canvasNode(); + } + if (sNode) { + int x = int(sNode->x() + sNode->width()/2); + int y = int(sNode->y() + sNode->height()/2); + + if (_prevSelectedNode) { + if (rect().contains(_prevSelectedPos)) + setContentsPos(x-_prevSelectedPos.x(), + y-_prevSelectedPos.y()); + else + ensureVisible(x,y, + sNode->width()/2+50, sNode->height()/2+50); + } + else center(x,y); + } + + if (activeNode) { + CanvasNode* cn = activeNode->canvasNode(); + CanvasFrame* f = new CanvasFrame(cn, _canvas); + f->setZ(-1); + f->show(); + } + + _cvZoom = 0; + updateSizes(); + + _canvas->update(); + viewport()->setUpdatesEnabled(true); + + delete _renderProcess; + _renderProcess = 0; +} + +void CallGraphView::contentsMovingSlot(int x, int y) +{ + QRect z(int(x * _cvZoom), int(y * _cvZoom), + int(visibleWidth() * _cvZoom)-1, int(visibleHeight() * _cvZoom)-1); + if (0) qDebug("moving: (%d,%d) => (%d/%d - %dx%d)", + x, y, z.x(), z.y(), z.width(), z.height()); + _completeView->setZoomRect(z); +} + +void CallGraphView::zoomRectMoved(int dx, int dy) +{ + if (leftMargin()>0) dx = 0; + if (topMargin()>0) dy = 0; + scrollBy(int(dx/_cvZoom),int(dy/_cvZoom)); +} + +void CallGraphView::zoomRectMoveFinished() +{ + if (_zoomPosition == Auto) updateSizes(); +} + +void CallGraphView::contentsMousePressEvent(QMouseEvent* e) +{ + // clicking on the viewport sets focus + setFocus(); + + _isMoving = true; + + QCanvasItemList l = canvas()->collisions(e->pos()); + if (l.count()>0) { + QCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Got Node '%s'", + n->function()->prettyName().ascii()); + + selected(n->function()); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) qDebug("CallGraphView: Got Edge '%s'", + e->prettyName().ascii()); + + if (e->call()) selected(e->call()); + } + } + _lastPos = e->globalPos(); +} + +void CallGraphView::contentsMouseMoveEvent(QMouseEvent* e) +{ + if (_isMoving) { + int dx = e->globalPos().x() - _lastPos.x(); + int dy = e->globalPos().y() - _lastPos.y(); + scrollBy(-dx, -dy); + _lastPos = e->globalPos(); + } +} + +void CallGraphView::contentsMouseReleaseEvent(QMouseEvent*) +{ + _isMoving = false; + if (_zoomPosition == Auto) updateSizes(); +} + +void CallGraphView::contentsMouseDoubleClickEvent(QMouseEvent* e) +{ + QCanvasItemList l = canvas()->collisions(e->pos()); + if (l.count() == 0) return; + QCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Double Clicked on Node '%s'", + n->function()->prettyName().ascii()); + + activated(n->function()); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (e->call()) { + if (0) qDebug("CallGraphView: Double Clicked On Edge '%s'", + e->call()->prettyName().ascii()); + + activated(e->call()); + } + } +} + +void CallGraphView::contentsContextMenuEvent(QContextMenuEvent* e) +{ + QCanvasItemList l = canvas()->collisions(e->pos()); + QCanvasItem* i = (l.count() == 0) ? 0 : l.first(); + + QPopupMenu popup; + TraceFunction *f = 0, *cycle = 0; + TraceCall* c = 0; + + if (i) { + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Menu on Node '%s'", + n->function()->prettyName().ascii()); + f = n->function(); + cycle = f->cycle(); + + QString name = f->prettyName(); + popup.insertItem(i18n("Go to '%1'") + .arg(Configuration::shortenSymbol(name)), 93); + if (cycle && (cycle != f)) { + name = Configuration::shortenSymbol(cycle->prettyName()); + popup.insertItem(i18n("Go to '%1'").arg(name), 94); + } + popup.insertSeparator(); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) qDebug("CallGraphView: Menu on Edge '%s'", + e->prettyName().ascii()); + c = e->call(); + if (c) { + QString name = c->prettyName(); + popup.insertItem(i18n("Go to '%1'") + .arg(Configuration::shortenSymbol(name)), 95); + + popup.insertSeparator(); + } + } + } + + if (_renderProcess) { + popup.insertItem(i18n("Stop Layouting"), 999); + popup.insertSeparator(); + } + + addGoMenu(&popup); + popup.insertSeparator(); + + QPopupMenu epopup; + epopup.insertItem(i18n("As PostScript"), 201); + epopup.insertItem(i18n("As Image ..."), 202); + + popup.insertItem(i18n("Export Graph"), &epopup, 200); + popup.insertSeparator(); + + QPopupMenu gpopup1; + gpopup1.setCheckable(true); + gpopup1.insertItem(i18n("Unlimited"), 100); + gpopup1.setItemEnabled(100, (_funcLimit>0.005)); + gpopup1.insertSeparator(); + gpopup1.insertItem(i18n("None"), 101); + gpopup1.insertItem(i18n("max. 2"), 102); + gpopup1.insertItem(i18n("max. 5"), 103); + gpopup1.insertItem(i18n("max. 10"), 104); + gpopup1.insertItem(i18n("max. 15"), 105); + if (_maxCallerDepth<-1) _maxCallerDepth=-1; + switch(_maxCallerDepth) { + case -1: gpopup1.setItemChecked(100,true); break; + case 0: gpopup1.setItemChecked(101,true); break; + case 2: gpopup1.setItemChecked(102,true); break; + case 5: gpopup1.setItemChecked(103,true); break; + case 10: gpopup1.setItemChecked(104,true); break; + case 15: gpopup1.setItemChecked(105,true); break; + default: + gpopup1.insertItem(i18n("< %1").arg(_maxCallerDepth), 106); + gpopup1.setItemChecked(106,true); break; + } + + QPopupMenu gpopup2; + gpopup2.setCheckable(true); + gpopup2.insertItem(i18n("Unlimited"), 110); + gpopup2.setItemEnabled(110, (_funcLimit>0.005)); + gpopup2.insertSeparator(); + gpopup2.insertItem(i18n("None"), 111); + gpopup2.insertItem(i18n("max. 2"), 112); + gpopup2.insertItem(i18n("max. 5"), 113); + gpopup2.insertItem(i18n("max. 10"), 114); + gpopup2.insertItem(i18n("max. 15"), 115); + if (_maxCallingDepth<-1) _maxCallingDepth=-1; + switch(_maxCallingDepth) { + case -1: gpopup2.setItemChecked(110,true); break; + case 0: gpopup2.setItemChecked(111,true); break; + case 2: gpopup2.setItemChecked(112,true); break; + case 5: gpopup2.setItemChecked(113,true); break; + case 10: gpopup2.setItemChecked(114,true); break; + case 15: gpopup2.setItemChecked(115,true); break; + default: + gpopup2.insertItem(i18n("< %1").arg(_maxCallingDepth), 116); + gpopup2.setItemChecked(116,true); break; + } + + QPopupMenu gpopup3; + gpopup3.setCheckable(true); + gpopup3.insertItem(i18n("No Minimum"), 120); + gpopup3.setItemEnabled(120, + (_maxCallerDepth>=0) && (_maxCallingDepth>=0)); + gpopup3.insertSeparator(); + gpopup3.insertItem(i18n("50 %"), 121); + gpopup3.insertItem(i18n("20 %"), 122); + gpopup3.insertItem(i18n("10 %"), 123); + gpopup3.insertItem(i18n("5 %"), 124); + gpopup3.insertItem(i18n("3 %"), 125); + gpopup3.insertItem(i18n("2 %"), 126); + gpopup3.insertItem(i18n("1.5 %"), 127); + gpopup3.insertItem(i18n("1 %"), 128); + if (_funcLimit<0) _funcLimit = DEFAULT_FUNCLIMIT; + if (_funcLimit>.5) _funcLimit = .5; + if (_funcLimit == 0.0) gpopup3.setItemChecked(120,true); + else if (_funcLimit >= 0.5) gpopup3.setItemChecked(121,true); + else if (_funcLimit >= 0.2) gpopup3.setItemChecked(122,true); + else if (_funcLimit >= 0.1) gpopup3.setItemChecked(123,true); + else if (_funcLimit >= 0.05) gpopup3.setItemChecked(124,true); + else if (_funcLimit >= 0.03) gpopup3.setItemChecked(125,true); + else if (_funcLimit >= 0.02) gpopup3.setItemChecked(126,true); + else if (_funcLimit >= 0.015) gpopup3.setItemChecked(127,true); + else gpopup3.setItemChecked(128,true); + double oldFuncLimit = _funcLimit; + + QPopupMenu gpopup4; + gpopup4.setCheckable(true); + gpopup4.insertItem(i18n("Same as Node"), 160); + gpopup4.insertItem(i18n("50 % of Node"), 161); + gpopup4.insertItem(i18n("20 % of Node"), 162); + gpopup4.insertItem(i18n("10 % of Node"), 163); + if (_callLimit<0) _callLimit = DEFAULT_CALLLIMIT; + if (_callLimit >= _funcLimit) _callLimit = _funcLimit; + if (_callLimit == _funcLimit) gpopup4.setItemChecked(160,true); + else if (_callLimit >= 0.5 * _funcLimit) gpopup4.setItemChecked(161,true); + else if (_callLimit >= 0.2 * _funcLimit) gpopup4.setItemChecked(162,true); + else gpopup4.setItemChecked(163,true); + + QPopupMenu gpopup; + gpopup.setCheckable(true); + gpopup.insertItem(i18n("Caller Depth"), &gpopup1, 80); + gpopup.insertItem(i18n("Callee Depth"), &gpopup2, 81); + gpopup.insertItem(i18n("Min. Node Cost"), &gpopup3, 82); + gpopup.insertItem(i18n("Min. Call Cost"), &gpopup4, 83); + gpopup.insertSeparator(); + gpopup.insertItem(i18n("Arrows for Skipped Calls"), 130); + gpopup.setItemChecked(130,_showSkipped); + gpopup.insertItem(i18n("Inner-cycle Calls"), 131); + gpopup.setItemChecked(131,_expandCycles); + gpopup.insertItem(i18n("Cluster Groups"), 132); + gpopup.setItemChecked(132,_clusterGroups); + + QPopupMenu vpopup; + vpopup.setCheckable(true); + vpopup.insertItem(i18n("Compact"), 140); + vpopup.insertItem(i18n("Normal"), 141); + vpopup.insertItem(i18n("Tall"), 142); + vpopup.setItemChecked(140,_detailLevel == 0); + vpopup.setItemChecked(141,_detailLevel == 1); + vpopup.setItemChecked(142,_detailLevel == 2); + vpopup.insertSeparator(); + vpopup.insertItem(i18n("Top to Down"), 150); + vpopup.insertItem(i18n("Left to Right"), 151); + vpopup.insertItem(i18n("Circular"), 152); + vpopup.setItemChecked(150,_layout == TopDown); + vpopup.setItemChecked(151,_layout == LeftRight); + vpopup.setItemChecked(152,_layout == Circular); + + QPopupMenu opopup; + opopup.insertItem(i18n("TopLeft"), 170); + opopup.insertItem(i18n("TopRight"), 171); + opopup.insertItem(i18n("BottomLeft"), 172); + opopup.insertItem(i18n("BottomRight"), 173); + opopup.insertItem(i18n("Automatic"), 174); + opopup.setItemChecked(170,_zoomPosition == TopLeft); + opopup.setItemChecked(171,_zoomPosition == TopRight); + opopup.setItemChecked(172,_zoomPosition == BottomLeft); + opopup.setItemChecked(173,_zoomPosition == BottomRight); + opopup.setItemChecked(174,_zoomPosition == Auto); + + popup.insertItem(i18n("Graph"), &gpopup, 70); + popup.insertItem(i18n("Visualization"), &vpopup, 71); + popup.insertItem(i18n("Birds-eye View"), &opopup, 72); + + int r = popup.exec(e->globalPos()); + + switch(r) { + case 93: activated(f); break; + case 94: activated(cycle); break; + case 95: activated(c); break; + + case 999: stopRendering(); break; + + case 201: + { + TraceFunction* f = activeFunction(); + if (!f) break; + + GraphExporter ge(data(), f, costType(), groupType(), + QString("callgraph.dot")); + ge.setGraphOptions(this); + ge.writeDot(); + + system("(dot callgraph.dot -Tps > callgraph.ps; kghostview callgraph.ps)&"); + } + break; + + case 202: + // write current content of canvas as image to file + { + if (!_canvas) return; + + QString fn = KFileDialog::getSaveFileName(":","*.png"); + + if (!fn.isEmpty()) { + QPixmap pix(_canvas->size()); + QPainter p(&pix); + _canvas->drawArea( _canvas->rect(), &p ); + pix.save(fn,"PNG"); + } + } + break; + + case 100: _maxCallerDepth = -1; break; + case 101: _maxCallerDepth = 0; break; + case 102: _maxCallerDepth = 2; break; + case 103: _maxCallerDepth = 5; break; + case 104: _maxCallerDepth = 10; break; + case 105: _maxCallerDepth = 15; break; + + case 110: _maxCallingDepth = -1; break; + case 111: _maxCallingDepth = 0; break; + case 112: _maxCallingDepth = 2; break; + case 113: _maxCallingDepth = 5; break; + case 114: _maxCallingDepth = 10; break; + case 115: _maxCallingDepth = 15; break; + + case 120: _funcLimit = 0; break; + case 121: _funcLimit = 0.5; break; + case 122: _funcLimit = 0.2; break; + case 123: _funcLimit = 0.1; break; + case 124: _funcLimit = 0.05; break; + case 125: _funcLimit = 0.03; break; + case 126: _funcLimit = 0.02; break; + case 127: _funcLimit = 0.015; break; + case 128: _funcLimit = 0.01; break; + + case 130: _showSkipped = !_showSkipped; break; + case 131: _expandCycles = !_expandCycles; break; + case 132: _clusterGroups = !_clusterGroups; break; + + case 140: _detailLevel = 0; break; + case 141: _detailLevel = 1; break; + case 142: _detailLevel = 2; break; + + case 150: _layout = TopDown; break; + case 151: _layout = LeftRight; break; + case 152: _layout = Circular; break; + + case 160: _callLimit = _funcLimit; break; + case 161: _callLimit = .5 * _funcLimit; break; + case 162: _callLimit = .2 * _funcLimit; break; + case 163: _callLimit = .1 * _funcLimit; break; + + case 170: _zoomPosition = TopLeft; break; + case 171: _zoomPosition = TopRight; break; + case 172: _zoomPosition = BottomLeft; break; + case 173: _zoomPosition = BottomRight; break; + case 174: _zoomPosition = Auto; break; + + default: break; + } + if (r>=120 && r<130) _callLimit *= _funcLimit / oldFuncLimit; + + if (r>99 && r<170) refresh(); + if (r>169 && r<180) updateSizes(); +} + +CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s) +{ + if (s == QString("TopLeft")) return TopLeft; + if (s == QString("TopRight")) return TopRight; + if (s == QString("BottomLeft")) return BottomLeft; + if (s == QString("BottomRight")) return BottomRight; + if (s == QString("Automatic")) return Auto; + + return DEFAULT_ZOOMPOS; +} + +QString CallGraphView::zoomPosString(ZoomPosition p) +{ + if (p == TopRight) return QString("TopRight"); + if (p == BottomLeft) return QString("BottomLeft"); + if (p == BottomRight) return QString("BottomRight"); + if (p == Auto) return QString("Automatic"); + + return QString("TopLeft"); +} + +void CallGraphView::readViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup* g = configGroup(c, prefix, postfix); + + if (0) qDebug("CallGraphView::readViewConfig"); + + _maxCallerDepth = g->readNumEntry("MaxCaller", DEFAULT_MAXCALLER); + _maxCallingDepth = g->readNumEntry("MaxCalling", DEFAULT_MAXCALLING); + _funcLimit = g->readDoubleNumEntry("FuncLimit", DEFAULT_FUNCLIMIT); + _callLimit = g->readDoubleNumEntry("CallLimit", DEFAULT_CALLLIMIT); + _showSkipped = g->readBoolEntry("ShowSkipped", DEFAULT_SHOWSKIPPED); + _expandCycles = g->readBoolEntry("ExpandCycles", DEFAULT_EXPANDCYCLES); + _clusterGroups = g->readBoolEntry("ClusterGroups", + DEFAULT_CLUSTERGROUPS); + _detailLevel = g->readNumEntry("DetailLevel", DEFAULT_DETAILLEVEL); + _layout = GraphOptions::layout(g->readEntry("Layout", + layoutString(DEFAULT_LAYOUT))); + _zoomPosition = zoomPos(g->readEntry("ZoomPosition", + zoomPosString(DEFAULT_ZOOMPOS))); + + delete g; +} + +void CallGraphView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + writeConfigEntry(&g, "MaxCaller", _maxCallerDepth, DEFAULT_MAXCALLER); + writeConfigEntry(&g, "MaxCalling", _maxCallingDepth, DEFAULT_MAXCALLING); + writeConfigEntry(&g, "FuncLimit", _funcLimit, DEFAULT_FUNCLIMIT); + writeConfigEntry(&g, "CallLimit", _callLimit, DEFAULT_CALLLIMIT); + writeConfigEntry(&g, "ShowSkipped", _showSkipped, DEFAULT_SHOWSKIPPED); + writeConfigEntry(&g, "ExpandCycles", _expandCycles, DEFAULT_EXPANDCYCLES); + writeConfigEntry(&g, "ClusterGroups", _clusterGroups, + DEFAULT_CLUSTERGROUPS); + writeConfigEntry(&g, "DetailLevel", _detailLevel, DEFAULT_DETAILLEVEL); + writeConfigEntry(&g, "Layout", + layoutString(_layout), layoutString(DEFAULT_LAYOUT).utf8().data()); + writeConfigEntry(&g, "ZoomPosition", + zoomPosString(_zoomPosition), + zoomPosString(DEFAULT_ZOOMPOS).utf8().data()); +} + +#include "callgraphview.moc" + diff --git a/kcachegrind/kcachegrind/callgraphview.h b/kcachegrind/kcachegrind/callgraphview.h new file mode 100644 index 00000000..af861d41 --- /dev/null +++ b/kcachegrind/kcachegrind/callgraphview.h @@ -0,0 +1,499 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Callgraph View + */ + +#ifndef CALLGRAPHVIEW_H +#define CALLGRAPHVIEW_H + +#include <qcanvas.h> +#include <qwidget.h> +#include <qmap.h> +#include <qtimer.h> + +#include "treemap.h" // for DrawParams +#include "tracedata.h" +#include "traceitemview.h" + +class QProcess; + +class KTempFile; +class CanvasNode; +class CanvasEdge; +class GraphEdge; +class CallGraphView; + +// sorts according start/end position of a call arc +// this depends on attached CanvasEdge's ! +class GraphEdgeList: public QPtrList<GraphEdge> +{ + public: + GraphEdgeList(); + void setSortCallerPos(bool b) { _sortCallerPos = b; } + + protected: + int compareItems ( Item item1, Item item2 ); + + private: + bool _sortCallerPos; +}; + + +typedef QMap<GraphEdge*, int> GraphEdgeSet; + +// temporary parts of call graph to be shown +class GraphNode +{ +public: + GraphNode(); + + TraceFunction* function() { return _f; } + void setFunction(TraceFunction* f) { _f = f; } + + CanvasNode* canvasNode() { return _cn; } + void setCanvasNode(CanvasNode* cn) { _cn = cn; } + + bool isVisible() { return _visible; } + void setVisible(bool v) { _visible = v; } + + // keyboard navigation + TraceCall* visibleCaller(); + TraceCall* visibleCalling(); + void setCalling(GraphEdge*); + void setCaller(GraphEdge*); + TraceFunction* nextVisible(); + TraceFunction* priorVisible(); + TraceCall* nextVisibleCaller(GraphEdge*); + TraceCall* nextVisibleCalling(GraphEdge*); + TraceCall* priorVisibleCaller(GraphEdge*); + TraceCall* priorVisibleCalling(GraphEdge*); + + double self, incl; + GraphEdgeList callers, callings; + // for fast unique insertion of GraphEdges in above lists + GraphEdgeSet callerSet, callingSet; + + private: + TraceFunction* _f; + CanvasNode* _cn; + bool _visible; + + // for keyboard navigation + int _lastCallerIndex, _lastCallingIndex; + bool _lastFromCaller; +}; + +class GraphEdge +{ +public: + GraphEdge(); + + CanvasEdge* canvasEdge() { return _ce; } + void setCanvasEdge(CanvasEdge* ce) { _ce = ce; } + + TraceCall* call() { return _c; } + void setCall(TraceCall* c) { _c = c; } + + bool isVisible() { return _visible; } + void setVisible(bool v) { _visible = v; } + + GraphNode* fromNode() { return _fromNode; } + GraphNode* toNode() { return _toNode; } + TraceFunction* from() { return _from; } + TraceFunction* to() { return _to; } + + // has special cases for collapsed edges + QString prettyName(); + + void setCaller(TraceFunction* f) { _from = f; } + void setCalling(TraceFunction* f) { _to = f; } + void setCallerNode(GraphNode* n) { _fromNode = n; } + void setCallingNode(GraphNode* n) { _toNode = n; } + + // keyboard navigation + TraceFunction* visibleCaller(); + TraceFunction* visibleCalling(); + TraceCall* nextVisible(); + TraceCall* priorVisible(); + + double cost, count; + + private: + // we have a _c *and* _from/_to because for collapsed edges, + // only _to or _from will be unequal NULL + TraceCall* _c; + TraceFunction * _from, * _to; + GraphNode *_fromNode, *_toNode; + CanvasEdge* _ce; + bool _visible; + // for keyboard navigation: have we last reached this edge via a caller? + bool _lastFromCaller; + +}; + + +typedef QMap<TraceFunction*, GraphNode> GraphNodeMap; +typedef QMap<QPair<TraceFunction*, TraceFunction*>, GraphEdge> GraphEdgeMap; + + +/* Abstract Interface for graph options */ +class GraphOptions +{ + public: + enum Layout { TopDown, LeftRight, Circular}; + + virtual double funcLimit() = 0; + virtual double callLimit() = 0; + virtual int maxCallerDepth() = 0; + virtual int maxCallingDepth() = 0; + virtual bool showSkipped() = 0; + virtual bool expandCycles() = 0; + virtual bool clusterGroups() = 0; + virtual int detailLevel() = 0; + virtual Layout layout() = 0; + + static QString layoutString(Layout); + static Layout layout(QString); +}; + +/* Graph Options Storage */ +class StorableGraphOptions: public GraphOptions +{ + public: + StorableGraphOptions(); + + // implementation of getters + virtual double funcLimit() { return _funcLimit; } + virtual double callLimit() { return _callLimit; } + virtual int maxCallerDepth() { return _maxCallerDepth; } + virtual int maxCallingDepth() { return _maxCallingDepth; } + virtual bool showSkipped() { return _showSkipped; } + virtual bool expandCycles() { return _expandCycles; } + virtual bool clusterGroups() { return _clusterGroups; } + virtual int detailLevel() { return _detailLevel; } + virtual Layout layout() { return _layout; } + + // setters + void setMaxCallerDepth(int d) { _maxCallerDepth = d; } + void setMaxCallingDepth(int d) { _maxCallingDepth = d; } + void setFuncLimit(double l) { _funcLimit = l; } + void setCallLimit(double l) { _callLimit = l; } + void setShowSkipped(bool b) { _showSkipped = b; } + void setExpandCycles(bool b) { _expandCycles = b; } + void setClusterGroups(bool b) { _clusterGroups = b; } + void setDetailLevel(int l) { _detailLevel = l; } + void setLayout(Layout l) { _layout = l; } + + protected: + double _funcLimit, _callLimit; + int _maxCallerDepth, _maxCallingDepth; + bool _showSkipped, _expandCycles, _clusterGroups; + int _detailLevel; + Layout _layout; +}; + +/** + * GraphExporter + * + * Generates a graph file for "dot" + * Create an instance and + */ +class GraphExporter: public StorableGraphOptions +{ +public: + GraphExporter(); + GraphExporter(TraceData*, TraceFunction*, TraceCostType*, + TraceItem::CostType, QString filename = QString::null); + virtual ~GraphExporter(); + + void reset(TraceData*, TraceItem*, TraceCostType*, + TraceItem::CostType, QString filename = QString::null); + + QString filename() { return _dotName; } + int edgeCount() { return _edgeMap.count(); } + int nodeCount() { return _nodeMap.count(); } + + // Set the object from which to get graph options for creation. + // Default is this object itself (supply 0 for default) + void setGraphOptions(GraphOptions* go = 0); + + // Create a subgraph with given limits/maxDepths + void createGraph(); + + // calls createGraph before dumping of not already created + void writeDot(); + + // to map back to structures when parsing a layouted graph + + /* <toFunc> is a helper for node() and edge(). + * Don't use the returned pointer directly, but only with + * node() or edge(), because it could be a dangling pointer. + */ + TraceFunction* toFunc(QString); + GraphNode* node(TraceFunction*); + GraphEdge* edge(TraceFunction*, TraceFunction*); + + /* After CanvasEdges are attached to GraphEdges, we can + * sort the incoming and outgoing edges of all nodes + * regarding start/end points for keyboard navigation + */ + void sortEdges(); + +private: + void buildGraph(TraceFunction*, int, bool, double); + + QString _dotName; + TraceItem* _item; + TraceCostType* _costType; + TraceItem::CostType _groupType; + KTempFile* _tmpFile; + double _realFuncLimit, _realCallLimit; + int _maxDepth; + bool _graphCreated; + + GraphOptions* _go; + + // optional graph attributes + bool _useBox; + + // graph parts written to file + GraphNodeMap _nodeMap; + GraphEdgeMap _edgeMap; +}; + +/** + * A panner layed over a QCanvas + */ +class PannerView: public QCanvasView +{ + Q_OBJECT + +public: + PannerView(QWidget * parent = 0, const char * name = 0); + + void setZoomRect(QRect r); + +signals: + void zoomRectMoved(int dx, int dy); + void zoomRectMoveFinished(); + +protected: + void contentsMousePressEvent(QMouseEvent*); + void contentsMouseMoveEvent(QMouseEvent*); + void contentsMouseReleaseEvent(QMouseEvent*); + void drawContents(QPainter * p, int clipx, int clipy, int clipw, int cliph); + + QRect _zoomRect; + bool _movingZoomRect; + QPoint _lastPos; +}; + + +/* + * Canvas Items: + * - CanvasNode (Rectangular Area) + * - CanvasEdge (Spline curve) + * - CanvasEdgeLabel (Label for edges) + * - CanvasEdgeArrow (Arrows at the end of the edge spline) + * - CanvasFrame (Grey background blending to show active node) + */ + +enum { + CANVAS_NODE = 1122, + CANVAS_EDGE, CANVAS_EDGELABEL, CANVAS_EDGEARROW, + CANVAS_FRAME +}; + +class CanvasNode: public QCanvasRectangle, public StoredDrawParams +{ +public: + CanvasNode(CallGraphView*,GraphNode*, int, int, int, int, QCanvas*); + + void updateGroup(); + void setSelected(bool); + void drawShape(QPainter&); + + GraphNode* node() { return _node; } + int rtti() const { return CANVAS_NODE; } + +private: + GraphNode* _node; + CallGraphView* _view; +}; + +class CanvasEdgeLabel: public QCanvasRectangle, public StoredDrawParams +{ +public: + CanvasEdgeLabel(CallGraphView*, CanvasEdge*, int, int, int, int, QCanvas*); + + void drawShape(QPainter&); + + CanvasEdge* canvasEdge() { return _ce; } + int rtti() const { return CANVAS_EDGELABEL; } + +private: + CanvasEdge* _ce; + CallGraphView* _view; +}; + +class CanvasEdgeArrow: public QCanvasPolygon +{ +public: + CanvasEdgeArrow(CanvasEdge*, QCanvas*); + + void drawShape(QPainter&); + + CanvasEdge* canvasEdge() { return _ce; } + int rtti() const { return CANVAS_EDGEARROW; } + +private: + CanvasEdge* _ce; +}; + + +class CanvasEdge: public QCanvasSpline +{ +public: + CanvasEdge(GraphEdge*, QCanvas*); + + void setSelected(bool); + void drawShape(QPainter&); + QPointArray areaPoints() const; + + CanvasEdgeLabel* label() { return _label; } + void setLabel(CanvasEdgeLabel* l) { _label = l; } + CanvasEdgeArrow* arrow() { return _arrow; } + void setArrow(CanvasEdgeArrow* a) { _arrow = a; } + + GraphEdge* edge() { return _edge; } + int rtti() const { return CANVAS_EDGE; } + +private: + GraphEdge* _edge; + CanvasEdgeLabel* _label; + CanvasEdgeArrow* _arrow; +}; + + +class CanvasFrame: public QCanvasRectangle +{ +public: + CanvasFrame( CanvasNode*, QCanvas *canvas ); + int rtti () const { return CANVAS_FRAME; } + bool hit( const QPoint&) const { return false; } +protected: + void drawShape( QPainter & ); +private: + static QPixmap* _p; +}; + + +class CallGraphTip; + +/** + * A CanvasView showing a part of the call graph + * and another zoomed out CanvasView in a border acting as + * a panner to select to visible part (only if needed) + */ +class CallGraphView: public QCanvasView, public TraceItemView, + public StorableGraphOptions +{ + Q_OBJECT + +public: + enum ZoomPosition { TopLeft, TopRight, BottomLeft, BottomRight, Auto }; + + CallGraphView(TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + ~CallGraphView(); + + void readViewConfig(KConfig*, QString prefix, QString postfix, bool); + void saveViewConfig(KConfig*, QString prefix, QString postfix, bool); + + QWidget* widget() { return this; } + QString whatsThis() const; + + ZoomPosition zoomPos() const { return _zoomPosition; } + static ZoomPosition zoomPos(QString); + static QString zoomPosString(ZoomPosition); + +public slots: + void contentsMovingSlot(int,int); + void zoomRectMoved(int,int); + void zoomRectMoveFinished(); + + void showRenderWarning(); + void stopRendering(); + void readDotOutput(); + void dotExited(); + +protected: + void resizeEvent(QResizeEvent*); + void contentsMousePressEvent(QMouseEvent*); + void contentsMouseMoveEvent(QMouseEvent*); + void contentsMouseReleaseEvent(QMouseEvent*); + void contentsMouseDoubleClickEvent(QMouseEvent*); + void contentsContextMenuEvent(QContextMenuEvent*); + void keyPressEvent(QKeyEvent*); + void focusInEvent(QFocusEvent*); + void focusOutEvent(QFocusEvent*); + +private: + void updateSizes(QSize s = QSize(0,0)); + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); + void makeFrame(CanvasNode*, bool active); + void clear(); + void showText(QString); + + QCanvas *_canvas; + int _xMargin, _yMargin; + PannerView *_completeView; + double _cvZoom; + + CallGraphTip* _tip; + + bool _isMoving; + QPoint _lastPos; + + GraphExporter _exporter; + + GraphNode* _selectedNode; + GraphEdge* _selectedEdge; + + // widget options + ZoomPosition _zoomPosition, _lastAutoPosition; + + // background rendering + QProcess* _renderProcess; + QTimer _renderTimer; + GraphNode* _prevSelectedNode; + QPoint _prevSelectedPos; + QString _unparsedOutput; +}; + + + + +#endif + + + diff --git a/kcachegrind/kcachegrind/callitem.cpp b/kcachegrind/kcachegrind/callitem.cpp new file mode 100644 index 00000000..51bc19d2 --- /dev/null +++ b/kcachegrind/kcachegrind/callitem.cpp @@ -0,0 +1,185 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items for caller/callee view. + */ + +#include <qpixmap.h> +#include <klocale.h> +#include <kapplication.h> +#include <kiconloader.h> + +#include "configuration.h" +#include "listutils.h" +#include "callitem.h" +#include "callview.h" +#include "toplevel.h" + +// CallItem + + +CallItem::CallItem(CallView* view, QListView* parent, TraceCall* c) + : QListViewItem(parent) +{ + _call = c; + _view = view; + + _active = _view->activeFunction(); + bool baseIsCycle = (_active && (_active == _active->cycle())); + + QString fName; + if (_view->showCallers()) { + _shown = _call->caller(true); + fName = c->callerName(!baseIsCycle); + } + else { + _shown = _call->called(true); + fName = c->calledName(!baseIsCycle); + } + + _shown->addPrettyLocation(fName); + + setText(3, fName); + updateGroup(); + updateCost(); +} + +void CallItem::updateGroup() +{ + QColor c = Configuration::functionColor(_view->groupType(), _shown); + setPixmap(3, colorPixmap(10, 10, c)); +} + +void CallItem::updateCost() +{ + bool sameCycle = _shown->cycle() && (_active->cycle() == _shown->cycle()); + bool shownIsCycle = (_shown == _shown->cycle()); + bool selectedIsCycle = (_active == _active->cycle()); + if (_call->isRecursion()) sameCycle=true; + + QString cStr; + if ((selectedIsCycle || shownIsCycle) && sameCycle) + cStr = "-"; + else { + _cc = _call->callCount(); + if (_cc == 0) + cStr = i18n("(active)"); + else + cStr = _call->prettyCallCount(); + } + setText(2, cStr); + + TraceCost* totalCost; + if (_view->topLevel()->showExpanded()) { + if (_active->cycle()) + totalCost = _active->cycle()->inclusive(); + else + totalCost = _active->inclusive(); + } + else + totalCost = _active->data(); + + TraceCostType* ct = _view->costType(); + _sum = _call->subCost(ct); + double total = totalCost->subCost(ct); + + if (total == 0.0) { + QString str = "-"; + + setText(0, str); + setPixmap(0, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (_view->topLevel()->showPercentage()) + setText(0, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + else + setText(0, _call->prettySubCost(ct)); + + setPixmap(0, costPixmap(ct, _call, total, false)); + } + + // Cost Type 2 + TraceCostType* ct2 = _view->costType2(); + if (ct2) { + _sum2 = _call->subCost(ct2); + double total = totalCost->subCost(ct2); + + if (total == 0.0) { + QString str = "-"; + + setText(1, str); + setPixmap(1, QPixmap()); + } + else { + double sum = 100.0 * _sum2 / total; + + if (_view->topLevel()->showPercentage()) + setText(1, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + else + setText(1, _call->prettySubCost(ct2)); + + setPixmap(1, costPixmap(ct2, _call, total, false)); + } + } + + QPixmap p; + if (sameCycle && !selectedIsCycle && !shownIsCycle) { + + QString icon = "undo"; + KIconLoader* loader = KApplication::kApplication()->iconLoader(); + p= loader->loadIcon(icon, KIcon::Small, 0, + KIcon::DefaultState, 0, true); + } + setPixmap(2, p); +} + + +int CallItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + const CallItem* ci1 = this; + const CallItem* ci2 = (CallItem*) i; + + // we always want descending order + if (ascending) { + ci1 = ci2; + ci2 = this; + } + + if (col==0) { + if (ci1->_sum < ci2->_sum) return -1; + if (ci1->_sum > ci2->_sum) return 1; + return 0; + } + if (col==1) { + if (ci1->_sum2 < ci2->_sum2) return -1; + if (ci1->_sum2 > ci2->_sum2) return 1; + return 0; + } + if (col==2) { + if (ci1->_cc < ci2->_cc) return -1; + if (ci1->_cc > ci2->_cc) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} + diff --git a/kcachegrind/kcachegrind/callitem.h b/kcachegrind/kcachegrind/callitem.h new file mode 100644 index 00000000..b3013b0c --- /dev/null +++ b/kcachegrind/kcachegrind/callitem.h @@ -0,0 +1,50 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of call view. + */ + +#ifndef CALLITEM_H +#define CALLITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class CallView; + +class CallItem: public QListViewItem +{ +public: + CallItem(CallView*, QListView*, TraceCall* c); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceCall* call() { return _call; } + CallView* view() { return _view; } + void updateCost(); + void updateGroup(); + +private: + SubCost _sum, _sum2; + SubCost _cc; + TraceCall* _call; + CallView* _view; + TraceFunction *_active, *_shown; +}; + +#endif diff --git a/kcachegrind/kcachegrind/callmapview.cpp b/kcachegrind/kcachegrind/callmapview.cpp new file mode 100644 index 00000000..8098c6b1 --- /dev/null +++ b/kcachegrind/kcachegrind/callmapview.cpp @@ -0,0 +1,999 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Call Map View + */ + +#include <qwhatsthis.h> +#include <qpopupmenu.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kapplication.h> +#include <kconfig.h> + +#include "callmapview.h" +#include "configuration.h" +#include "listutils.h" +#include "toplevel.h" + +// +// CallMapView +// + + +// defaults +#define DEFAULT_SPLITMODE "Rows" +#define DEFAULT_DRAWNAME true +#define DEFAULT_DRAWCOST true +#define DEFAULT_DRAWLOCATION false +#define DEFAULT_DRAWCALLS false +#define DEFAULT_FORCESTRINGS false +#define DEFAULT_ROTATION true +#define DEFAULT_SHADING true +#define DEFAULT_MAXAREA 100 + + +CallMapView::CallMapView(bool showCallers, TraceItemView* parentView, + QWidget* parent, const char* name) + : TreeMapWidget(new CallMapBaseItem(), parent, name), TraceItemView(parentView) +{ + _showCallers = showCallers; + + setFieldType(0, i18n( "Name" )); + setFieldType(1, i18n( "Cost" )); + setFieldType(2, i18n( "Location" )); + setFieldPosition(2, TreeMapItem::TopLeft); + setFieldType(3, i18n( "Calls" )); + setFieldPosition(3, TreeMapItem::TopRight); + + setSplitMode(DEFAULT_SPLITMODE); + setFieldVisible(0, DEFAULT_DRAWNAME); + setFieldVisible(1, DEFAULT_DRAWCOST); + setFieldVisible(2, DEFAULT_DRAWLOCATION); + setFieldVisible(3, DEFAULT_DRAWCALLS); + setFieldForced(0, DEFAULT_FORCESTRINGS); + setFieldForced(1, DEFAULT_FORCESTRINGS); + setFieldForced(2, DEFAULT_FORCESTRINGS); + setFieldForced(3, DEFAULT_FORCESTRINGS); + setAllowRotation(DEFAULT_ROTATION); + setShadingEnabled(DEFAULT_SHADING); + setMinimalArea(DEFAULT_MAXAREA); + + connect(this, + SIGNAL(doubleClicked(TreeMapItem*)), + SLOT(activatedSlot(TreeMapItem*))); + connect(this, + SIGNAL(returnPressed(TreeMapItem*)), + SLOT(activatedSlot(TreeMapItem*))); + connect(this, + SIGNAL(currentChanged(TreeMapItem*, bool)), + SLOT(selectedSlot(TreeMapItem*, bool))); + connect(this, + SIGNAL(contextMenuRequested(TreeMapItem*,const QPoint &)), + SLOT(context(TreeMapItem*,const QPoint &))); + + QWhatsThis::add( this, whatsThis()); +} + +QString CallMapView::whatsThis() const +{ + QString s = _showCallers ? + i18n( "<b>Caller Map</b>" + "<p>This graph shows the nested hierarchy of " + "all callers of the current activated function. " + "Each colored rectangle represents a function; " + "its size tries to be proportional to the cost spent " + "therein while the active function is running " + "(however, there are drawing constrains).</p>") : + i18n("<b>Call Map</b>" + "<p>This graph shows the nested hierarchy of " + "all callees of the current activated function. " + "Each colored rectangle represents a function; " + "its size tries to be proportional to the cost spent " + "therein while the active function is running " + "(however, there are drawing constrains).</p>"); + + s += i18n( "<p>Appearance options can be found in the " + "in the context menu. To get exact size proportions, " + "choose 'Hide incorrect borders'. As this mode can be " + "<em>very</em> time consuming, you may want to limit " + "the maximum drawn nesting level before. " + "'Best' determinates the split direction for children " + "from the aspect ratio of the parent. " + "'Always Best' decides on remaining space for each " + "sibling. " + "'Ignore Proportions' takes space for function name " + "drawing <em>before</em> drawing children. Note that " + "size proportions can get <em>heavily</em> wrong.</p>" + + "<p>This is a <em>TreeMap</em> widget. " + "Keyboard navigation is available with the left/right arrow " + "keys for traversing siblings, and up/down arrow keys " + "to go a nesting level up/down. " + "<em>Return</em> activates the current item.</p>"); + + return s; +} + +void CallMapView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + ((CallMapBaseItem*)base())->setFunction(0); +} + +void CallMapView::context(TreeMapItem* i,const QPoint & p) +{ + if (!i) return; + + QPopupMenu popup; + QPopupMenu fpopup; // select function subpopup + QPopupMenu vpopup; // visualisation subpopup + QPopupMenu dpopup; // split direction + QPopupMenu bpopup; // border subpopup + QPopupMenu l1popup; // depth limit subpopup + QPopupMenu l2popup; // function limit subpopup + QPopupMenu l3popup; // area limit subpopup + + TreeMapItem* item = i; + int count; + + QString shortCurrentName; + if (i) { + shortCurrentName = i->text(0); + if ((int)shortCurrentName.length() > Configuration::maxSymbolLength()) + shortCurrentName = + shortCurrentName.left(Configuration::maxSymbolLength()) + "..."; + } + + if (item) { + popup.insertItem(i18n("Go To"), &fpopup, 100); + count = 0; + while (count<Configuration::maxSymbolCount() && item) { + QString name = item->text(0); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + fpopup.insertItem(name, 101+count); + item = item->parent(); + count++; + } + popup.insertSeparator(); + } + + addGoMenu(&popup); + popup.insertSeparator(); + + l1popup.setCheckable(true); + popup.insertItem(i18n("Stop at Depth"), &l1popup, 12); + + int maxDepth = maxDrawingDepth(); + l1popup.insertItem(i18n("No Depth Limit"), 50); + l1popup.setItemChecked(50, maxDepth==-1); + l1popup.insertSeparator(); + l1popup.insertItem(i18n("Depth 10"), 51); + l1popup.setItemChecked(51, maxDepth==10); + l1popup.insertItem(i18n("Depth 15"), 52); + l1popup.setItemChecked(52, maxDepth==15); + l1popup.insertItem(i18n("Depth 20"), 53); + l1popup.setItemChecked(53, maxDepth==20); + if (i) { + l1popup.insertSeparator(); + l1popup.insertItem(i18n("Depth of '%1' (%2)") + .arg(shortCurrentName).arg(i->depth()), 55); + l1popup.setItemChecked(55, maxDepth == i->depth()); + } + if (maxDepth>0) { + l1popup.insertSeparator(); + l1popup.insertItem(i18n("Decrement Depth (to %1)").arg(maxDepth-1), 56); + l1popup.insertItem(i18n("Increment Depth (to %1)").arg(maxDepth+1), 57); + } + + l2popup.setCheckable(true); + popup.insertItem(i18n("Stop at Function"), &l2popup, 13); + l2popup.insertItem(i18n("No Function Limit"), 200); + l2popup.setItemChecked(200, fieldStop(0).isEmpty()); + bool foundStopName = false; + item = i; + if (i) { + l2popup.insertSeparator(); + count = 0; + while (count<Configuration::maxSymbolCount() && item) { + QString name = item->text(0); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + l2popup.insertItem(name, 201+count); + if (item->text(0) == fieldStop(0)) { + l2popup.setItemChecked(201+count, true); + foundStopName = true; + } + item = item->parent(); + count++; + } + } + if (!foundStopName && !fieldStop(0).isEmpty()) { + l2popup.insertSeparator(); + QString name = fieldStop(0); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + l2popup.insertItem(name, 199); + l2popup.setItemChecked(199, true); + } + + l3popup.setCheckable(true); + popup.insertItem(i18n("Stop at Area"), &l3popup, 14); + + int mArea = minimalArea(); + l3popup.insertItem(i18n("No Area Limit"), 60); + l3popup.setItemChecked(60, mArea ==-1); + l3popup.insertSeparator(); + l3popup.insertItem(i18n("50 Pixels"), 63); + l3popup.setItemChecked(63, mArea==50); + l3popup.insertItem(i18n("100 Pixels"), 64); + l3popup.setItemChecked(64, mArea==100); + l3popup.insertItem(i18n("200 Pixels"), 65); + l3popup.setItemChecked(65, mArea==200); + l3popup.insertItem(i18n("500 Pixels"), 66); + l3popup.setItemChecked(66, mArea==500); + int currentArea = 0; + if (i) { + currentArea = i->width() * i->height(); + l3popup.insertSeparator(); + l3popup.insertItem(i18n("Area of '%1' (%2)") + .arg(shortCurrentName).arg(currentArea), 67); + l3popup.setItemChecked(67, mArea == currentArea); + } + if (mArea>0) { + l3popup.insertSeparator(); + l3popup.insertItem(i18n("Double Area Limit (to %1)") + .arg(mArea*2), 68); + l3popup.insertItem(i18n("Half Area Limit (to %1)") + .arg(mArea/2), 69); + } + + popup.insertSeparator(); + + vpopup.setCheckable(true); + popup.insertItem(i18n("Visualisation"), &vpopup, 10); + + QPopupMenu splitpopup; + addSplitDirectionItems(&splitpopup, 1001); + vpopup.insertItem(i18n("Split Direction"), &splitpopup, 1000); + + vpopup.insertItem(i18n("Skip Incorrect Borders"), 40); + vpopup.setItemEnabled(40, !_showCallers); + vpopup.setItemChecked(40, skipIncorrectBorder()); + + bpopup.setCheckable(true); + vpopup.insertItem(i18n("Border Width"), &bpopup, 41); + bpopup.insertItem(i18n("Border 0"), 42); + bpopup.setItemEnabled(42, !_showCallers); + bpopup.setItemChecked(42, borderWidth()==0); + bpopup.insertItem(i18n("Border 1"), 43); + bpopup.setItemChecked(43, borderWidth()==1); + bpopup.insertItem(i18n("Border 2"), 44); + bpopup.setItemChecked(44, borderWidth()==2); + bpopup.insertItem(i18n("Border 3"), 45); + bpopup.setItemChecked(45, borderWidth()==3); + + vpopup.insertSeparator(); + + vpopup.insertItem(i18n("Draw Symbol Names"), 20); + vpopup.insertItem(i18n("Draw Cost"), 21); + vpopup.insertItem(i18n("Draw Location"), 22); + vpopup.insertItem(i18n("Draw Calls"), 23); + vpopup.insertSeparator(); + + vpopup.insertItem(i18n("Ignore Proportions"), 24); + vpopup.insertItem(i18n("Allow Rotation"), 25); + if (!fieldVisible(0) && + !fieldVisible(1) && + !fieldVisible(2) && + !fieldVisible(3)) { + vpopup.setItemEnabled(24, false); + vpopup.setItemEnabled(25, false); + } + else { + vpopup.setItemChecked(20,fieldVisible(0)); + vpopup.setItemChecked(21,fieldVisible(1)); + vpopup.setItemChecked(22,fieldVisible(2)); + vpopup.setItemChecked(23,fieldVisible(3)); + vpopup.setItemChecked(24,fieldForced(0)); + vpopup.setItemChecked(25,allowRotation()); + } + + vpopup.insertItem(i18n("Shading"), 26); + vpopup.setItemChecked(26,isShadingEnabled()); + + int r = popup.exec(mapToGlobal(p)); + + if (r>100 && r<150) { + r -= 100; + while (i && (r>1)) { + i=i->parent(); + r--; + } + activatedSlot(i); + return; + } + + if (r>200 && r<250) { + r -= 200; + while (i && (r>1)) { + i=i->parent(); + r--; + } + if (i) + setFieldStop(0, i->text(0)); + + return; + } + + switch(r) { + case 20: + setFieldVisible(0, !vpopup.isItemChecked(20)); + break; + + case 21: + setFieldVisible(1, !vpopup.isItemChecked(21)); + break; + + case 22: + setFieldVisible(2, !vpopup.isItemChecked(22)); + break; + + case 23: + setFieldVisible(3, !vpopup.isItemChecked(23)); + break; + + case 24: + setFieldForced(0, !vpopup.isItemChecked(24)); + setFieldForced(1, !vpopup.isItemChecked(24)); + setFieldForced(2, !vpopup.isItemChecked(24)); + setFieldForced(3, !vpopup.isItemChecked(24)); + break; + + case 25: setAllowRotation(!vpopup.isItemChecked(25)); break; + case 26: setShadingEnabled(!vpopup.isItemChecked(26)); break; + + case 40: + setSkipIncorrectBorder(!vpopup.isItemChecked(40)); + break; + + case 42: setBorderWidth(0); break; + case 43: setBorderWidth(1); break; + case 44: setBorderWidth(2); break; + case 45: setBorderWidth(3); break; + + case 50: setMaxDrawingDepth(-1); break; + case 51: setMaxDrawingDepth(10); break; + case 52: setMaxDrawingDepth(15); break; + case 53: setMaxDrawingDepth(20); break; + case 55: setMaxDrawingDepth(i->depth()); break; + case 56: setMaxDrawingDepth(maxDepth-1); break; + case 57: setMaxDrawingDepth(maxDepth+1); break; + + case 200: setFieldStop(0, QString::null); break; + + case 60: setMinimalArea(-1); break; + case 61: setMinimalArea(10); break; + case 62: setMinimalArea(20); break; + case 63: setMinimalArea(50); break; + case 64: setMinimalArea(100); break; + case 65: setMinimalArea(200); break; + case 66: setMinimalArea(500); break; + case 67: setMinimalArea(currentArea); break; + case 68: setMinimalArea(mArea*2); break; + case 69: setMinimalArea(mArea/2); break; + } +} + +void CallMapView::activatedSlot(TreeMapItem* item) +{ + if (!item) return; + + if (item->rtti() == 1) { + CallMapBaseItem* bi = (CallMapBaseItem*)item; + activated(bi->function()); + } + else if (item->rtti() == 2) { + CallMapCallingItem* ci = (CallMapCallingItem*)item; + activated(ci->function()); + } + else if (item->rtti() == 3) { + CallMapCallerItem* ci = (CallMapCallerItem*)item; + activated(ci->function()); + } +} + +void CallMapView::selectedSlot(TreeMapItem* item, bool kbd) +{ + if (!item) return; + if (item->text(0).isEmpty()) return; + + if (kbd) { + QString msg = i18n("Call Map: Current is '%1'").arg(item->text(0)); + if (_topLevel) + _topLevel->showMessage(msg, 5000); + } + + TraceFunction* f = 0; + + if (item->rtti() == 1) { + CallMapBaseItem* bi = (CallMapBaseItem*)item; + f = bi->function(); + } + else if (item->rtti() == 2) { + CallMapCallingItem* ci = (CallMapCallingItem*)item; + f = ci->function(); + } + else if (item->rtti() == 3) { + CallMapCallerItem* ci = (CallMapCallerItem*)item; + f = ci->function(); + } + if (f) { + // this avoids marking + _selectedItem = f; + selected(f); + } +} + +TraceItem* CallMapView::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CallMapView::doUpdate(int changeType) +{ + if (changeType == costType2Changed) return; + + // if there is a selected item, always draw marking... + if (changeType & selectedItemChanged) { + TraceFunction* f = 0; + + if (_selectedItem) { + switch(_selectedItem->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + f = (TraceFunction*)_selectedItem; + break; + default: + break; + } + } + // if this is the only change... + if (changeType == selectedItemChanged) { + setMarked(f ? 1:0, true); + return; + } + setMarked(f ? 1:0, false); + } + + + if (changeType & activeItemChanged) { + TraceFunction* f = 0; + + if (_activeItem) { + switch(_activeItem->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + f = (TraceFunction*)_activeItem; + break; + default: + break; + } + } + ((CallMapBaseItem*)base())->setFunction(f); + } + else if ( ((changeType & partsChanged) && Configuration::showCycles()) || + (changeType & dataChanged) || + (changeType & configChanged)) { + /* regenerates the treemap because traceitems were added/removed */ + base()->refresh(); + } + else if ((changeType & partsChanged) || + (changeType & costTypeChanged)) { + /* we need to do the draw order sorting again as the values change */ + resort(); + redraw(); + } + else + redraw(); +} + + + +QColor CallMapView::groupColor(TraceFunction* f) const +{ + if (!f) + return colorGroup().button(); + + return Configuration::functionColor(_groupType, f); +} + + +QString CallMapView::tipString(TreeMapItem* i) const +{ + QString tip, itemTip; + int count = 0; + + //qDebug("CallMapView::tipString for '%s'", i->text(0).ascii()); + + // first, SubPartItem's + while (i && count<Configuration::maxSymbolCount()) { + itemTip = i->text(0); + if ((int)itemTip.length()>Configuration::maxSymbolLength()) + itemTip = itemTip.left(Configuration::maxSymbolLength()) + "..."; + + if (!i->text(1).isEmpty()) + itemTip += " (" + i->text(1) + ")"; + + if (!tip.isEmpty()) tip += "\n"; + + tip += itemTip; + i = i->parent(); + count++; + } + if (count == Configuration::maxSymbolCount()) tip += "\n..."; + + return tip; +} + + +TraceCost* CallMapView::totalCost() +{ + TraceFunction* f = ((CallMapBaseItem*)base())->function(); + if (!f) return 0; + + return Configuration::showExpanded() ? f->inclusive() : f->data(); +} + + + + +// CallMapBaseItem + +CallMapBaseItem::CallMapBaseItem() +{ + _f = 0; +} + +void CallMapBaseItem::setFunction(TraceFunction* f) +{ + if (f == _f) return; + + _f = f; + refresh(); +} + + +QString CallMapBaseItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_f) + return i18n("(no function)"); + + return _f->prettyName(); + } + + if (!_f) return QString::null; + + if (textNo == 2) return _f->prettyLocation(); + if (textNo == 3) return _f->calledCount().pretty(); + if (textNo != 1) return QString::null; + + TraceCostType* ct = ((CallMapView*)widget())->costType(); + TraceCost* t = ((CallMapView*)widget())->totalCost(); + + if (Configuration::showPercentage()) { + double sum, total = t->subCost(ct); + if (total == 0.0) + sum = 100.0; + else + sum = 100.0 * _f->inclusive()->subCost(ct) / total; + + return QString("%1 %") + .arg(sum, 0, 'f', Configuration::percentPrecision()); + } + return _f->inclusive()->prettySubCost(ct); +} + +QPixmap CallMapBaseItem::pixmap(int i) const +{ + if ((i != 1) || !_f) return QPixmap(); + + TraceCostType* ct = ((CallMapView*)widget())->costType(); + TraceCost* t = ((CallMapView*)widget())->totalCost(); + + // colored level meter with frame + return costPixmap( ct, _f->inclusive(), (double) (t->subCost(ct)), true); +} + + +double CallMapBaseItem::value() const +{ + if (!_f) return 0.0; + + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + return (double) _f->inclusive()->subCost(ct); +} + + +double CallMapBaseItem::sum() const +{ + if (!_f) return 0.0; + + CallMapView* w = (CallMapView*)widget(); + + if (w->showCallers()) + return 0.0; + else + return (double) _f->inclusive()->subCost(w->costType()); +} + + +bool CallMapBaseItem::isMarked(int) const +{ + return ((CallMapView*)widget())->selectedItem() == _f; +} + +TreeMapItemList* CallMapBaseItem::children() +{ + if (_f && !initialized()) { + CallMapView* w = (CallMapView*)widget(); + + if (0) qDebug("Create Function %s (%s)", + w->showCallers() ? "Callers":"Callees", + text(0).ascii()); + + TraceCall* call; + + setSorting(-1); + if (w->showCallers()) { + TraceCallList l = _f->callers(); + for (call=l.first();call;call=l.next()) { + + // don't show calls inside of a cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + addItem(new CallMapCallerItem(1.0, call)); + } + + setSum(0); + } + else { + TraceCallList l = _f->callings(); + for (call=l.first();call;call=l.next()) { + + // don't show calls inside of a cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + CallMapCallingItem* i = new CallMapCallingItem(1.0, call); + i->init(); + addItem(i); + } + + setSum(_f->inclusive()->subCost(w->costType())); + } + setSorting(-2, false); + } + + return _children; +} + +QColor CallMapBaseItem::backColor() const +{ + return ((CallMapView*)widget())->groupColor(_f); +} + + + +// CallMapCallingItems + +CallMapCallingItem::CallMapCallingItem(double factor, TraceCall* c) +{ + _factor = factor; + _c = c; +} + +void CallMapCallingItem::init() +{ +#if 0 + // create assoziation: if not possible, i.e. an ass. already exists + // for the function, we need to draw the recursive version + _recursive = !setFunction(_c->called()); + _valid = true; +#endif +} + +QString CallMapCallingItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_c) + return i18n("(no call)"); + + return _c->calledName(); + } + + if (textNo == 2) return _c->called()->prettyLocation(); + if (textNo == 3) return SubCost(_factor * _c->callCount()).pretty(); + if (textNo != 1) return QString::null; + + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + + SubCost val = SubCost(_factor * _c->subCost(ct)); + if (Configuration::showPercentage()) { + // percentage relative to function cost + TraceCost* t = ((CallMapView*)widget())->totalCost(); + double p = 100.0 * _factor * _c->subCost(ct) / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', Configuration::percentPrecision()); + } + return val.pretty(); +} + +QPixmap CallMapCallingItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + TraceCostType* ct = ((CallMapView*)widget())->costType(); + TraceCost* t = ((CallMapView*)widget())->totalCost(); + + // colored level meter with frame + return costPixmap( ct, _c, t->subCost(ct) / _factor, true); +} + + +double CallMapCallingItem::value() const +{ + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + return _factor * _c->subCost(ct); +} + +double CallMapCallingItem::sum() const +{ + return value(); +} + +bool CallMapCallingItem::isMarked(int) const +{ + return ((CallMapView*)widget())->selectedItem() == _c->called(); +} + + +TreeMapItemList* CallMapCallingItem::children() +{ + if (!initialized()) { + if (0) qDebug("Create Calling subitems (%s)", path(0).join("/").ascii()); + + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + + // same as sum() + SubCost s = _c->called()->inclusive()->subCost(ct); + SubCost v = _c->subCost(ct); + if (v>s) { + qDebug("Warning: CallingItem subVal %u > Sum %u (%s)", + (unsigned)v, (unsigned)s, _c->called()->prettyName().ascii()); + v = s; + } + double newFactor = _factor * v / s; + +#if 0 + qDebug("CallingItem: Subitems of %s => %s, factor %f * %d/%d => %f", + _c->caller()->prettyName().ascii(), + _c->called()->prettyName().ascii(), + _factor, v, s, newFactor); +#endif + setSorting(-1); + TraceCall* call; + TraceCallList l = _c->called()->callings(); + for (call=l.first();call;call=l.next()) { + + // don't show calls inside of a cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + CallMapCallingItem* i = new CallMapCallingItem(newFactor, call); + i->init(); + addItem(i); + } + setSorting(-2, false); + } + + return _children; +} + + +QColor CallMapCallingItem::backColor() const +{ + CallMapView* w = (CallMapView*)widget(); + return w->groupColor(_c->called()); +} + + +// CallMapCallerItem + +CallMapCallerItem::CallMapCallerItem(double factor, TraceCall* c) +{ + _factor = factor; + _c = c; +} + +QString CallMapCallerItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_c) + return i18n("(no call)"); + + return _c->callerName(); + } + + if (textNo == 2) return _c->caller()->prettyLocation(); + if (textNo == 3) return SubCost(_factor * _c->callCount()).pretty(); + if (textNo != 1) return QString::null; + + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + + SubCost val = SubCost(_factor * _c->subCost(ct)); + if (Configuration::showPercentage()) { + TraceCost* t = ((CallMapView*)widget())->totalCost(); + double p = 100.0 * _factor * _c->subCost(ct) / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', Configuration::percentPrecision()); + } + return val.pretty(); +} + + +QPixmap CallMapCallerItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + TraceCostType* ct = ((CallMapView*)widget())->costType(); + TraceCost* t = ((CallMapView*)widget())->totalCost(); + + // colored level meter with frame + return costPixmap( ct, _c, t->subCost(ct) / _factor, true ); +} + + +double CallMapCallerItem::value() const +{ + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + return (double) _c->subCost(ct); +} + +bool CallMapCallerItem::isMarked(int) const +{ + return ((CallMapView*)widget())->selectedItem() == _c->caller(); +} + + +TreeMapItemList* CallMapCallerItem::children() +{ + if (!initialized()) { + //qDebug("Create Caller subitems (%s)", name().ascii()); + + TraceCostType* ct; + ct = ((CallMapView*)widget())->costType(); + + SubCost s = _c->caller()->inclusive()->subCost(ct); + SubCost v = _c->subCost(ct); + double newFactor = _factor * v / s; + + +#if 0 + qDebug("CallerItem: Subitems of %s => %s, factor %f * %d/%d => %f", + _c->caller()->prettyName().ascii(), + _c->called()->prettyName().ascii(), + _factor, v, s, newFactor); +#endif + setSorting(-1); + + TraceCall* call; + TraceCallList l = _c->caller()->callers(); + for (call=l.first();call;call=l.next()) { + + // don't show calls inside of a cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + TreeMapItem* i = new CallMapCallerItem(newFactor, call); + addItem(i); + } + setSorting(-2, false); + } + + return _children; +} + +QColor CallMapCallerItem::backColor() const +{ + CallMapView* w = (CallMapView*)widget(); + return w->groupColor(_c->caller()); +} + +void CallMapView::readViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup* g = configGroup(c, prefix, postfix); + + setSplitMode(g->readEntry("SplitMode", DEFAULT_SPLITMODE)); + + setFieldVisible(0, g->readBoolEntry("DrawName", DEFAULT_DRAWNAME)); + setFieldVisible(1, g->readBoolEntry("DrawCost", DEFAULT_DRAWCOST)); + setFieldVisible(2, g->readBoolEntry("DrawLocation", DEFAULT_DRAWLOCATION)); + setFieldVisible(3, g->readBoolEntry("DrawCalls", DEFAULT_DRAWCALLS)); + + bool enable = g->readBoolEntry("ForceStrings", DEFAULT_FORCESTRINGS); + setFieldForced(0, enable); + setFieldForced(1, enable); + setFieldForced(2, enable); + setFieldForced(3, enable); + + setAllowRotation(g->readBoolEntry("AllowRotation", DEFAULT_ROTATION)); + setShadingEnabled(g->readBoolEntry("Shading", DEFAULT_SHADING)); + setFieldStop(0, g->readEntry("StopName")); + setMaxDrawingDepth(g->readNumEntry("MaxDepth", -1)); + setMinimalArea(g->readNumEntry("MaxArea", DEFAULT_MAXAREA)); + + delete g; +} + +void CallMapView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + writeConfigEntry(&g, "SplitMode", splitModeString(), DEFAULT_SPLITMODE); + writeConfigEntry(&g, "DrawName", fieldVisible(0), DEFAULT_DRAWNAME); + writeConfigEntry(&g, "DrawCost", fieldVisible(1), DEFAULT_DRAWCOST); + writeConfigEntry(&g, "DrawLocation", fieldVisible(2), DEFAULT_DRAWLOCATION); + writeConfigEntry(&g, "DrawCalls", fieldVisible(3), DEFAULT_DRAWCALLS); + // when option for all text (0-3) + writeConfigEntry(&g, "ForceStrings", fieldForced(0), DEFAULT_FORCESTRINGS); + + writeConfigEntry(&g, "AllowRotation", allowRotation(), DEFAULT_ROTATION); + writeConfigEntry(&g, "Shading", isShadingEnabled(), DEFAULT_SHADING); + + writeConfigEntry(&g, "StopName", fieldStop(0), ""); + writeConfigEntry(&g, "MaxDepth", maxDrawingDepth(), -1); + writeConfigEntry(&g, "MaxArea", minimalArea(), DEFAULT_MAXAREA); +} + +#include "callmapview.moc" diff --git a/kcachegrind/kcachegrind/callmapview.h b/kcachegrind/kcachegrind/callmapview.h new file mode 100644 index 00000000..8561a0ac --- /dev/null +++ b/kcachegrind/kcachegrind/callmapview.h @@ -0,0 +1,129 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Call Map View + */ + +#ifndef CALLMAPVIEW_H +#define CALLMAPVIEW_H + +#include "treemap.h" +#include "tracedata.h" +#include "traceitemview.h" + +class CallMapView: public TreeMapWidget, public TraceItemView +{ + Q_OBJECT + +public: + + CallMapView(bool showCallers, TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + + QWidget* widget() { return this; } + QString whatsThis() const; + void setData(TraceData*); + + void readViewConfig(KConfig*, QString prefix, QString postfix, bool); + void saveViewConfig(KConfig*, QString prefix, QString postfix, bool); + + bool showCallers() const { return _showCallers; } + TraceCost* totalCost(); + QString tipString(TreeMapItem*) const; + QColor groupColor(TraceFunction*) const; + +private slots: + void context(TreeMapItem*,const QPoint &); + void selectedSlot(TreeMapItem*, bool); + void activatedSlot(TreeMapItem*); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + + bool _showCallers; +}; + + + +// Subitems of CallMap + +class CallMapBaseItem: public TreeMapItem +{ +public: + CallMapBaseItem(); + + void setFunction(TraceFunction* f); + TraceFunction* function() { return _f; } + int rtti() const { return 1; } + double sum() const; + double value() const ; + bool isMarked(int) const; + QString text(int) const; + QPixmap pixmap(int) const; + TreeMapItemList* children(); + QColor backColor() const; + +private: + TraceFunction* _f; +}; + + +class CallMapCallingItem: public TreeMapItem +{ +public: + CallMapCallingItem(double factor, TraceCall* c); + void init(); + int rtti() const { return 2; } + int borderWidth() const { return widget()->borderWidth(); } + TraceFunction* function() { return _c->called(); } + double value() const; + double sum() const; + bool isMarked(int) const; + QString text(int) const; + QPixmap pixmap(int) const; + TreeMapItemList* children(); + QColor backColor() const; + +private: + TraceCall* _c; + double _factor; +}; + +class CallMapCallerItem: public TreeMapItem +{ +public: + CallMapCallerItem(double factor, TraceCall* c); + int rtti() const { return 3; } + int borderWidth() const { return widget()->borderWidth(); } + TraceFunction* function() { return _c->caller(); } + double value() const; + bool isMarked(int) const; + QString text(int) const; + QPixmap pixmap(int) const; + TreeMapItemList* children(); + QColor backColor() const; + +private: + TraceCall* _c; + double _factor; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/callview.cpp b/kcachegrind/kcachegrind/callview.cpp new file mode 100644 index 00000000..edd7ee6a --- /dev/null +++ b/kcachegrind/kcachegrind/callview.cpp @@ -0,0 +1,256 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Call Views + */ + +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <klocale.h> + +#include "configuration.h" +#include "callitem.h" +#include "callview.h" + + + +// +// CallView +// + + +CallView::CallView(bool showCallers, TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + _showCallers = showCallers; + + addColumn( i18n( "Cost" ) ); + addColumn( i18n( "Cost 2" ) ); + if (_showCallers) { + addColumn( i18n( "Count" ) ); + addColumn( i18n( "Caller" ) ); + } + else { + addColumn( i18n( "Count" ) ); + addColumn( i18n( "Callee" ) ); + } + + setSorting(0,false); + setColumnAlignment(0, Qt::AlignRight); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + setAllColumnsShowFocus(true); + setResizeMode(QListView::LastColumn); + setMinimumHeight(50); + + connect( this, + SIGNAL( selectionChanged(QListViewItem*) ), + SLOT( selectedSlot(QListViewItem*) ) ); + + connect( this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + connect(this, + SIGNAL(doubleClicked(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(returnPressed(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + QWhatsThis::add( this, whatsThis() ); +} + +QString CallView::whatsThis() const +{ + return _showCallers ? + i18n( "<b>List of direct Callers</b>" + "<p>This list shows all functions calling the " + "current selected one directly, together with " + "a call count and the cost spent in the current " + "selected function while being called from the " + "function from the list.</p>" + "<p>An icon instead of an inclusive cost specifies " + "that this is a call inside of a recursive cycle. " + "An inclusive cost makes no sense here.</p>" + "<p>Selecting a function makes it the current selected " + "one of this information panel. " + "If there are two panels (Split mode), the " + "function of the other panel is changed instead.</p>") : + i18n( "<b>List of direct Callees</b>" + "<p>This list shows all functions called by the " + "current selected one directly, together with " + "a call count and the cost spent in this function " + "while being called from the selected function.</p>" + "<p>Selecting a function makes it the current selected " + "one of this information panel. " + "If there are two panels (Split mode), the " + "function of the other panel is changed instead.</p>"); +} + + +void CallView::context(QListViewItem* i, const QPoint & p, int col) +{ + QPopupMenu popup; + + // Menu entry: + TraceCall* c = i ? ((CallItem*) i)->call() : 0; + TraceFunction *f = 0, *cycle = 0; + + if (c) { + QString name = _showCallers ? c->callerName(true) : c->calledName(true); + f = _showCallers ? c->caller(true) : c->called(true); + cycle = f->cycle(); + + popup.insertItem(i18n("Go to '%1'") + .arg(Configuration::shortenSymbol(name)), 93); + + if (cycle) { + name = Configuration::shortenSymbol(cycle->prettyName()); + popup.insertItem(i18n("Go to '%1'").arg(name), 94); + } + + popup.insertSeparator(); + } + + if ((col == 0) || (col == 1)) { + addCostMenu(&popup); + popup.insertSeparator(); + } + addGoMenu(&popup); + + int r = popup.exec(p); + if (r == 93) activated(f); + else if (r == 94) activated(cycle); +} + +void CallView::selectedSlot(QListViewItem * i) +{ + if (!i) return; + TraceCall* c = ((CallItem*) i)->call(); + // Should we skip cycles here? + TraceItem* f = _showCallers ? c->caller(false) : c->called(false); + + _selectedItem = f; + selected(f); +} + +void CallView::activatedSlot(QListViewItem * i) +{ + if (!i) return; + TraceCall* c = ((CallItem*) i)->call(); + // skip cycles: use the context menu to get to the cycle... + TraceItem* f = _showCallers ? c->caller(true) : c->called(true); + + activated(f); +} + +TraceItem* CallView::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CallView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + CallItem* ci = (CallItem*) QListView::selectedItem(); + TraceCall* c; + TraceItem* ti; + if (ci) { + c = ci->call(); + ti = _showCallers ? c->caller() : c->called(); + if (ti == _selectedItem) return; + } + + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) { + c = ((CallItem*) item)->call(); + ti = _showCallers ? c->caller() : c->called(); + if (ti == _selectedItem) { + ensureItemVisible(item); + setSelected(item, true); + break; + } + } + if (!item && ci) clearSelection(); + return; + } + + if (changeType == groupTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + ((CallItem*)item)->updateGroup(); + return; + } + + refresh(); +} + +void CallView::refresh() +{ + clear(); + setColumnWidth(0, 50); + setColumnWidth(1, _costType2 ? 50:0); + setColumnWidth(2, 50); + if (_costType) + setColumnText(0, _costType->name()); + if (_costType2) + setColumnText(1, _costType2->name()); + + if (!_data || !_activeItem) return; + + TraceFunction* f = activeFunction(); + if (!f) return; + + TraceCall* call; + // In the call lists, we skip cycles to show the real call relations + TraceCallList l = _showCallers ? f->callers(true) : f->callings(true); + + // Allow resizing of column 1 + setColumnWidthMode(1, QListView::Maximum); + + for (call=l.first();call;call=l.next()) + if (call->subCost(_costType)>0) + new CallItem(this, this, call); + + if (!_costType2) { + setColumnWidthMode(1, QListView::Manual); + setColumnWidth(1, 0); + } +} + +#include "callview.moc" diff --git a/kcachegrind/kcachegrind/callview.h b/kcachegrind/kcachegrind/callview.h new file mode 100644 index 00000000..7b5adc79 --- /dev/null +++ b/kcachegrind/kcachegrind/callview.h @@ -0,0 +1,55 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Call Views + */ + +#ifndef CALLVIEW_H +#define CALLVIEW_H + +#include <qlistview.h> +#include "tracedata.h" +#include "traceitemview.h" + +class CallView: public QListView, public TraceItemView +{ + Q_OBJECT + +public: + CallView(bool showCallers, TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + bool showCallers() const { return _showCallers; } + +private slots: + void context(QListViewItem*,const QPoint &, int); + void selectedSlot(QListViewItem*); + void activatedSlot(QListViewItem*); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); + + bool _showCallers; +}; + +#endif diff --git a/kcachegrind/kcachegrind/configdlg.cpp b/kcachegrind/kcachegrind/configdlg.cpp new file mode 100644 index 00000000..61e85dff --- /dev/null +++ b/kcachegrind/kcachegrind/configdlg.cpp @@ -0,0 +1,398 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Configuration Dialog for KCachegrind + */ + +#include <qcombobox.h> +#include <qcheckbox.h> +#include <qlineedit.h> +#include <qlistview.h> +#include <qdict.h> +#include <qmessagebox.h> + +#include <kcolorbutton.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <knumvalidator.h> + +#include "configdlg.h" +#include "tracedata.h" +#include "configuration.h" + + +ConfigDlg::ConfigDlg(Configuration* c, TraceData* data, + QWidget* parent, const char* name) + :ConfigDlgBase(parent, name) +{ + _config = c; + _data = data; + _objectCS = 0; + _classCS = 0; + _fileCS = 0; + KIntValidator * numValidator = new KIntValidator( this ); + maxListEdit->setValidator(numValidator ); + symbolCount->setValidator(numValidator ); + symbolLength->setValidator(numValidator ); + precisionEdit->setValidator(numValidator ); + contextEdit->setValidator(numValidator ); + +#if 0 + QListViewItem *oItem, *fItem, *cItem, *fnItem; + oItem = new(colorList, i18n("ELF Objects")); + + fItem = new(colorList, i18n("Source Files")); + cItem = new(colorList, i18n("C++ Classes")); + fnItem = new(colorList, i18n("Function (no Grouping)")); +#endif + + connect(objectCombo, SIGNAL(activated(const QString &)), + this, SLOT(objectActivated(const QString &))); + connect(objectCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(objectActivated(const QString &))); + connect(objectCheck, SIGNAL(toggled(bool)), + this, SLOT(objectCheckChanged(bool))); + connect(objectColor, SIGNAL(changed(const QColor &)), + this, SLOT(objectColorChanged(const QColor &))); + + connect(classCombo, SIGNAL(activated(const QString &)), + this, SLOT(classActivated(const QString &))); + connect(classCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(classActivated(const QString &))); + connect(classCheck, SIGNAL(toggled(bool)), + this, SLOT(classCheckChanged(bool))); + connect(classColor, SIGNAL(changed(const QColor &)), + this, SLOT(classColorChanged(const QColor &))); + + connect(fileCombo, SIGNAL(activated(const QString &)), + this, SLOT(fileActivated(const QString &))); + connect(fileCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(fileActivated(const QString &))); + connect(fileCheck, SIGNAL(toggled(bool)), + this, SLOT(fileCheckChanged(bool))); + connect(fileColor, SIGNAL(changed(const QColor &)), + this, SLOT(fileColorChanged(const QColor &))); + + QString objectPrefix = TraceCost::typeName(TraceCost::Object); + QString classPrefix = TraceCost::typeName(TraceCost::Class); + QString filePrefix = TraceCost::typeName(TraceCost::File); + + objectCombo->setDuplicatesEnabled(false); + classCombo->setDuplicatesEnabled(false); + fileCombo->setDuplicatesEnabled(false); + objectCombo->setAutoCompletion(true); + classCombo->setAutoCompletion(true); + fileCombo->setAutoCompletion(true); + + // first unspecified cost items from data + TraceObjectMap::Iterator oit; + QStringList oList; + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) + oList.append((*oit).prettyName()); + + TraceClassMap::Iterator cit; + QStringList cList; + for ( cit = data->classMap().begin(); + cit != data->classMap().end(); ++cit ) + cList.append((*cit).prettyName()); + + TraceFileMap::Iterator fit; + QStringList fList; + for ( fit = data->fileMap().begin(); + fit != data->fileMap().end(); ++fit ) + fList.append((*fit).prettyName()); + + // then already defined colors (have to check for duplicates!) + QDictIterator<Configuration::ColorSetting> it( c->_colors ); + for( ; it.current(); ++it ) { + if ((*it)->automatic) continue; + + QString n = it.currentKey(); + if (n.startsWith(objectPrefix)) { + n = n.remove(0, objectPrefix.length()+1); + if (oList.findIndex(n) == -1) oList.append(n); + } + else if (n.startsWith(classPrefix)) { + n = n.remove(0, classPrefix.length()+1); + if (cList.findIndex(n) == -1) cList.append(n); + } + else if (n.startsWith(filePrefix)) { + n = n.remove(0, filePrefix.length()+1); + if (fList.findIndex(n) == -1) fList.append(n); + } + } + + oList.sort(); + cList.sort(); + fList.sort(); + objectCombo->insertStringList(oList); + classCombo->insertStringList(cList); + fileCombo->insertStringList(fList); + + objectActivated(objectCombo->currentText()); + classActivated(classCombo->currentText()); + fileActivated(fileCombo->currentText()); + + maxListEdit->setText(QString::number(c->_maxListCount)); + + _dirItem = 0; + + QListViewItem* i = new QListViewItem(dirList, i18n("(always)")); + i->setOpen(true); + QStringList::Iterator sit = c->_generalSourceDirs.begin(); + for(; sit != c->_generalSourceDirs.end(); ++sit ) { + QString d = (*sit); + if (d.isEmpty()) d = "/"; + new QListViewItem(i, d); + } + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) { + QString n = (*oit).name(); + i = new QListViewItem(dirList, n); + i->setOpen(true); + QStringList* dirs = c->_objectSourceDirs[n]; + if (!dirs) continue; + + sit = dirs->begin(); + for(; sit != dirs->end(); ++sit ) { + QString d = (*sit); + if (d.isEmpty()) d = "/"; + new QListViewItem(i, d); + } + } + + connect(dirList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(dirsItemChanged(QListViewItem*))); + connect(addDirButton, SIGNAL(clicked()), + this, SLOT(dirsAddPressed())); + connect(deleteDirButton, SIGNAL(clicked()), + this, SLOT(dirsDeletePressed())); + dirList->setSelected(dirList->firstChild(), true); + + symbolCount->setText(QString::number(c->_maxSymbolCount)); + symbolLength->setText(QString::number(c->_maxSymbolLength)); + precisionEdit->setText(QString::number(c->_percentPrecision)); + contextEdit->setText(QString::number(c->_context)); +} + +ConfigDlg::~ConfigDlg() +{ +} + +bool ConfigDlg::configure(Configuration* c, TraceData* d, QWidget* p) +{ + ConfigDlg dlg(c, d, p); + + if (dlg.exec()) { + + bool ok; + int newValue = dlg.maxListEdit->text().toUInt(&ok); + if (ok && newValue < 500) + c->_maxListCount = newValue; + else + QMessageBox::warning(p, i18n("KCachegrind Configuration"), + i18n("The Maximum Number of List Items should be below 500." + "The previous set value (%1) will still be used.") + .arg(QString::number(c->_maxListCount)), + QMessageBox::Ok, 0); + + c->_maxSymbolCount = dlg.symbolCount->text().toInt(); + c->_maxSymbolLength = dlg.symbolLength->text().toInt(); + c->_percentPrecision = dlg.precisionEdit->text().toInt(); + c->_context = dlg.contextEdit->text().toInt(); + return true; + } + return false; +} + +void ConfigDlg::objectActivated(const QString & s) +{ +// qDebug("objectActivated: %s", s.ascii()); + + if (s.isEmpty()) { _objectCS=0; return; } + + QString n = TraceCost::typeName(TraceCost::Object) + "-" + s; + + Configuration* c = Configuration::config(); + Configuration::ColorSetting* cs = c->_colors[n]; + if (!cs) + cs = Configuration::color(n); +// else +// qDebug("found color %s", n.ascii()); + + _objectCS = cs; + + objectCheck->setChecked(cs->automatic); + objectColor->setColor(cs->color); + + /* + qDebug("Found Color %s, automatic to %s", + _objectCS->name.ascii(), + _objectCS->automatic ? "true":"false"); + */ +} + + +void ConfigDlg::objectCheckChanged(bool b) +{ + if (_objectCS) { + _objectCS->automatic = b; + /* + qDebug("Set Color %s automatic to %s", + _objectCS->name.ascii(), + _objectCS->automatic ? "true":"false"); + */ + } +} + +void ConfigDlg::objectColorChanged(const QColor & c) +{ + if (_objectCS) _objectCS->color = c; +} + +void ConfigDlg::classActivated(const QString & s) +{ +// qDebug("classActivated: %s", s.ascii()); + + if (s.isEmpty()) { _classCS=0; return; } + + QString n = TraceCost::typeName(TraceCost::Class) + "-" + s; + + Configuration* c = Configuration::config(); + Configuration::ColorSetting* cs = c->_colors[n]; + if (!cs) + cs = Configuration::color(n); + + _classCS = cs; + + classCheck->setChecked(cs->automatic); + classColor->setColor(cs->color); + +} + + +void ConfigDlg::classCheckChanged(bool b) +{ + if (_classCS) _classCS->automatic = b; +} + +void ConfigDlg::classColorChanged(const QColor & c) +{ + if (_classCS) _classCS->color = c; +} + + +void ConfigDlg::fileActivated(const QString & s) +{ +// qDebug("fileActivated: %s", s.ascii()); + + if (s.isEmpty()) { _fileCS=0; return; } + + QString n = TraceCost::typeName(TraceCost::File) + "-" + s; + + Configuration* c = Configuration::config(); + Configuration::ColorSetting* cs = c->_colors[n]; + if (!cs) + cs = Configuration::color(n); + + _fileCS = cs; + + fileCheck->setChecked(cs->automatic); + fileColor->setColor(cs->color); +} + + +void ConfigDlg::fileCheckChanged(bool b) +{ + if (_fileCS) _fileCS->automatic = b; +} + +void ConfigDlg::fileColorChanged(const QColor & c) +{ + if (_fileCS) _fileCS->color = c; +} + + +void ConfigDlg::dirsItemChanged(QListViewItem* i) +{ + _dirItem = i; + deleteDirButton->setEnabled(i->depth() == 1); + addDirButton->setEnabled(i->depth() == 0); +} + +void ConfigDlg::dirsDeletePressed() +{ + if (!_dirItem || (_dirItem->depth() == 0)) return; + QListViewItem* p = _dirItem->parent(); + if (!p) return; + + Configuration* c = Configuration::config(); + QString objName = p->text(0); + + QStringList* dirs; + if (objName == i18n("(always)")) + dirs = &(c->_generalSourceDirs); + else + dirs = c->_objectSourceDirs[objName]; + if (!dirs) return; + + dirs->remove(_dirItem->text(0)); + delete _dirItem; + _dirItem = 0; + + deleteDirButton->setEnabled(false); +} + +void ConfigDlg::dirsAddPressed() +{ + if (!_dirItem || (_dirItem->depth() >0)) return; + + Configuration* c = Configuration::config(); + QString objName = _dirItem->text(0); + + QStringList* dirs; + if (objName == i18n("(always)")) + dirs = &(c->_generalSourceDirs); + else { + dirs = c->_objectSourceDirs[objName]; + if (!dirs) { + dirs = new QStringList; + c->_objectSourceDirs.insert(objName, dirs); + } + } + + QString newDir; + newDir = KFileDialog::getExistingDirectory(QString::null, + this, + i18n("Choose Source Folder")); + if (newDir.isEmpty()) return; + + // even for "/", we strip the tailing slash + if (newDir.endsWith("/")) + newDir = newDir.left(newDir.length()-1); + + if (dirs->findIndex(newDir)>=0) return; + + dirs->append(newDir); + if (newDir.isEmpty()) newDir = QString("/"); + new QListViewItem(_dirItem, newDir); +} + +#include "configdlg.moc" diff --git a/kcachegrind/kcachegrind/configdlg.h b/kcachegrind/kcachegrind/configdlg.h new file mode 100644 index 00000000..d8bd2f89 --- /dev/null +++ b/kcachegrind/kcachegrind/configdlg.h @@ -0,0 +1,64 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Configuration Dialog for KCachegrind + */ + +#ifndef CONFIGDLG_H +#define CONFIGDLG_H + +#include "configdlgbase.h" +#include "configuration.h" + +class TraceData; + +class ConfigDlg : public ConfigDlgBase +{ + Q_OBJECT + +public: + ConfigDlg(Configuration*, TraceData*, + QWidget* parent = 0, const char* name = 0); + ~ConfigDlg(); + + static bool configure(Configuration*, TraceData*, QWidget*); + +protected slots: + void objectActivated(const QString &); + void objectCheckChanged(bool); + void objectColorChanged(const QColor &); + void classActivated(const QString &); + void classCheckChanged(bool); + void classColorChanged(const QColor &); + void fileActivated(const QString &); + void fileCheckChanged(bool); + void fileColorChanged(const QColor &); + void dirsItemChanged(QListViewItem*); + void dirsDeletePressed(); + void dirsAddPressed(); + +private: + Configuration* _config; + TraceData* _data; + + Configuration::ColorSetting *_objectCS, *_classCS, *_fileCS; + QListViewItem* _dirItem; +}; + +#endif diff --git a/kcachegrind/kcachegrind/configdlgbase.ui b/kcachegrind/kcachegrind/configdlgbase.ui new file mode 100644 index 00000000..bf2c1bd8 --- /dev/null +++ b/kcachegrind/kcachegrind/configdlgbase.ui @@ -0,0 +1,653 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>ConfigDlgBase</class> +<widget class="QDialog"> + <property name="name"> + <cstring>configDlgBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>447</width> + <height>378</height> + </rect> + </property> + <property name="caption"> + <string>Configuration</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>11</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QTabWidget"> + <property name="name"> + <cstring>tabWidget2</cstring> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>General</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLineEdit" row="3" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>precisionEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QLabel" row="2" column="1"> + <property name="name"> + <cstring>TextLabel2</cstring> + </property> + <property name="text"> + <string>Truncated when more/longer than:</string> + </property> + </widget> + <widget class="QLabel" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>TextLabel4_3</cstring> + </property> + <property name="text"> + <string>Precision of percentage values:</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>TextLabel3</cstring> + </property> + <property name="text"> + <string>Symbols in tooltips and context menus</string> + </property> + </widget> + <widget class="QLineEdit" row="2" column="3"> + <property name="name"> + <cstring>symbolLength</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <spacer row="2" column="0"> + <property name="name"> + <cstring>Spacer6_2_2_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLineEdit" row="0" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>maxListEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="2" column="2"> + <property name="name"> + <cstring>symbolCount</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QLabel" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>TextLabel5</cstring> + </property> + <property name="text"> + <string>Maximum number of items in lists:</string> + </property> + </widget> + </grid> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + <property name="text"> + <string>Cost Item Colors</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout9</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <spacer row="1" column="1"> + <property name="name"> + <cstring>Spacer9</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + <spacer row="0" column="0"> + <property name="name"> + <cstring>Spacer6</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>Layout9</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QComboBox" row="1" column="1"> + <property name="name"> + <cstring>classCombo</cstring> + </property> + <property name="maximumSize"> + <size> + <width>300</width> + <height>32767</height> + </size> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" row="2" column="2"> + <property name="name"> + <cstring>fileCheck</cstring> + </property> + <property name="text"> + <string>Automatic</string> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>TextLabel4</cstring> + </property> + <property name="text"> + <string>Object:</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel4_2_2</cstring> + </property> + <property name="text"> + <string>Class:</string> + </property> + </widget> + <widget class="KColorButton" row="2" column="3"> + <property name="name"> + <cstring>fileColor</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QCheckBox" row="1" column="2"> + <property name="name"> + <cstring>classCheck</cstring> + </property> + <property name="text"> + <string>Automatic</string> + </property> + </widget> + <widget class="KColorButton" row="0" column="3"> + <property name="name"> + <cstring>objectColor</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QCheckBox" row="0" column="2"> + <property name="name"> + <cstring>objectCheck</cstring> + </property> + <property name="text"> + <string>Automatic</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>TextLabel4_2</cstring> + </property> + <property name="text"> + <string>File:</string> + </property> + </widget> + <widget class="KColorButton" row="1" column="3"> + <property name="name"> + <cstring>classColor</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QComboBox" row="2" column="1"> + <property name="name"> + <cstring>fileCombo</cstring> + </property> + <property name="maximumSize"> + <size> + <width>300</width> + <height>32767</height> + </size> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + <widget class="QComboBox" row="0" column="1"> + <property name="name"> + <cstring>objectCombo</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>300</width> + <height>32767</height> + </size> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </grid> + </widget> + </grid> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Annotations</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel4_3_2</cstring> + </property> + <property name="text"> + <string>Context lines in annotations:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>contextEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1_2</cstring> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>Source Folders</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="0" column="0"> + <property name="name"> + <cstring>Spacer6_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QListView" row="0" column="1"> + <column> + <property name="text"> + <string>Object / Related Source Base</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>dirList</cstring> + </property> + <property name="rootIsDecorated"> + <bool>true</bool> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="2"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>addDirButton</cstring> + </property> + <property name="text"> + <string>Add...</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>Spacer5</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>49</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>deleteDirButton</cstring> + </property> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </vbox> + </widget> + <spacer row="1" column="1"> + <property name="name"> + <cstring>Spacer9_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </grid> + </widget> + </vbox> + </widget> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>Line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <spacer> + <property name="name"> + <cstring>Spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>210</width> + <height>0</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>PushButton2</cstring> + </property> + <property name="text"> + <string>&OK</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>PushButton1</cstring> + </property> + <property name="text"> + <string>&Cancel</string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<connections> + <connection> + <sender>PushButton2</sender> + <signal>clicked()</signal> + <receiver>configDlgBase</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>PushButton1</sender> + <signal>clicked()</signal> + <receiver>configDlgBase</receiver> + <slot>reject()</slot> + </connection> + <connection> + <sender>classCheck</sender> + <signal>toggled(bool)</signal> + <receiver>classColor</receiver> + <slot>setDisabled(bool)</slot> + </connection> + <connection> + <sender>fileCheck</sender> + <signal>toggled(bool)</signal> + <receiver>fileColor</receiver> + <slot>setDisabled(bool)</slot> + </connection> + <connection> + <sender>objectCheck</sender> + <signal>toggled(bool)</signal> + <receiver>objectColor</receiver> + <slot>setDisabled(bool)</slot> + </connection> +</connections> +<tabstops> + <tabstop>objectCombo</tabstop> + <tabstop>objectCheck</tabstop> + <tabstop>classCombo</tabstop> + <tabstop>classCheck</tabstop> + <tabstop>classColor</tabstop> + <tabstop>fileCombo</tabstop> + <tabstop>fileCheck</tabstop> + <tabstop>fileColor</tabstop> + <tabstop>maxListEdit</tabstop> + <tabstop>PushButton1</tabstop> + <tabstop>PushButton2</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in implementation">kcolorbutton.h</include> +</includes> +<pixmapinproject/> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kcachegrind/kcachegrind/configuration.cpp b/kcachegrind/kcachegrind/configuration.cpp new file mode 100644 index 00000000..f04f434b --- /dev/null +++ b/kcachegrind/kcachegrind/configuration.cpp @@ -0,0 +1,490 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Configuration for KCachegrind + */ + +#include <kconfig.h> +#include <klocale.h> +#include <kdebug.h> + +#include "configuration.h" +#include "tracedata.h" +#include "configdlgbase.h" + +#include "traceitemview.h" + +// +// Some predefined cost types... +// + +static QStringList knownTypes() +{ + QStringList l; + + l << "Ir" << "Dr" << "Dw" + << "I1mr" << "D1mr" << "D1mw" + << "I2mr" << "D2mr" << "D2mw" + + << "Smp" << "Sys" << "User" + << "L1m" << "L2m" << "CEst"; + + return l; +} + + +static QString knownFormula(QString name) +{ + if (name =="L1m") return QString("I1mr + D1mr + D1mw"); + if (name =="L2m") return QString("I2mr + D2mr + D2mw"); + if (name =="CEst") return QString("Ir + 10 L1m + 100 L2m"); + + return QString::null; +} + +static QString knownLongName(QString name) +{ + if (name == "Ir") return i18n("Instruction Fetch"); + if (name =="Dr") return i18n("Data Read Access"); + if (name =="Dw") return i18n("Data Write Access"); + if (name =="I1mr") return i18n("L1 Instr. Fetch Miss"); + if (name =="D1mr") return i18n("L1 Data Read Miss"); + if (name =="D1mw") return i18n("L1 Data Write Miss"); + if (name =="I2mr") return i18n("L2 Instr. Fetch Miss"); + if (name =="D2mr") return i18n("L2 Data Read Miss"); + if (name =="D2mw") return i18n("L2 Data Write Miss"); + if (name =="Smp") return i18n("Samples"); + if (name =="Sys") return i18n("System Time"); + if (name =="User") return i18n("User Time"); + if (name =="L1m") return i18n("L1 Miss Sum"); + if (name =="L2m") return i18n("L2 Miss Sum"); + if (name =="CEst") return i18n("Cycle Estimation"); + + return QString::null; +} + + + + +// +// Configuration +// + +Configuration* Configuration::_config = 0; + +Configuration::Configuration() + :_colors(517) +{ + _config = 0; + + _colors.setAutoDelete(true); + _objectSourceDirs.setAutoDelete(true); + + // defaults + _showPercentage = true; + _showExpanded = false; + _showCycles = true; + _cycleCut = 0.0; + _percentPrecision = 2; + + // max symbol count/length in tooltip/popup + _maxSymbolLength = 30; + _maxSymbolCount = 10; + _maxListCount = 100; + + // annotation behaviour + _context = 3; + _noCostInside = 20; +} + +Configuration* Configuration::config() +{ + if (!_config) + _config = new Configuration(); + + return _config; +} + + +void Configuration::saveOptions(KConfig* kconfig) +{ + Configuration* c = config(); + + // color options + KConfigGroup colorConfig(kconfig, QCString("CostColors")); + QDictIterator<ColorSetting> it( c->_colors ); + int count = 1; + for( ; it.current(); ++it ) { + if ( !(*it)->automatic ) { + colorConfig.writeEntry( QString("Name%1").arg(count), + it.currentKey()); + colorConfig.writeEntry( QString("Color%1").arg(count), + (*it)->color); + //qDebug("Written Color %s (%d)", it.currentKey().ascii(), count); + + count++; + } + } + colorConfig.writeEntry( "Count", count-1); + + // source options + KConfigGroup sourceConfig(kconfig, QCString("Source")); + sourceConfig.writeEntry("Dirs", c->_generalSourceDirs, ':'); + QDictIterator<QStringList> it2( c->_objectSourceDirs ); + count = 1; + for( ; it2.current(); ++it2 ) { + sourceConfig.writeEntry( QString("Object%1").arg(count), + it2.currentKey()); + sourceConfig.writeEntry( QString("Dirs%1").arg(count), + *(*it2), ':'); + count++; + } + sourceConfig.writeEntry( "Count", count-1); + + // general options + KConfigGroup generalConfig(kconfig, QCString("General")); + generalConfig.writeEntry("ShowPercentage", c->_showPercentage); + generalConfig.writeEntry("ShowExpanded", c->_showExpanded); + generalConfig.writeEntry("ShowCycles", c->_showCycles); + generalConfig.writeEntry("CycleCut", c->_cycleCut); + generalConfig.writeEntry("MaxSymbolCount", c->_maxSymbolCount); + generalConfig.writeEntry("MaxListCount", c->_maxListCount); + generalConfig.writeEntry("MaxSymbolLength", c->_maxSymbolLength); + generalConfig.writeEntry("PercentPrecision", c->_percentPrecision); + + generalConfig.writeEntry("Context", c->_context); + generalConfig.writeEntry("NoCostInside", c->_noCostInside); + + KConfigGroup ctConfig(kconfig, QCString("CostTypes")); + int ctCount = TraceCostType::knownTypeCount(); + ctConfig.writeEntry( "Count", ctCount); + for (int i=0; i<ctCount; i++) { + TraceCostType* t = TraceCostType::knownType(i); + ctConfig.writeEntry( QString("Name%1").arg(i+1), t->name()); + + // Use localized key + TraceItemView::writeConfigEntry(&ctConfig, + QString("Longname%1").arg(i+1).ascii(), + t->longName(), + knownLongName(t->name()).utf8().data() /*, true */ ); + TraceItemView::writeConfigEntry(&ctConfig, + QString("Formula%1").arg(i+1).ascii(), + t->formula(), knownFormula(t->name()).utf8().data()); + } +} + + + + +void Configuration::readOptions(KConfig* kconfig) +{ + int i, count; + Configuration* c = config(); + + // color options + c->_colors.clear(); + + // colors for default cost types: + // red for L2 misses, green for L1 misses, blue for normal accesses + c->color("CostType-I2mr")->color = QColor(240, 0, 0); + c->color("CostType-D2mr")->color = QColor(180,40,40); + c->color("CostType-D2mw")->color = QColor(120,80,80); + + c->color("CostType-I1mr")->color = QColor(0, 240, 0); + c->color("CostType-D1mr")->color = QColor(40,180,40); + c->color("CostType-D1mw")->color = QColor(80,120,80); + + c->color("CostType-Ir")->color = QColor(0, 0, 240); + c->color("CostType-Dr")->color = QColor(40,40,180); + c->color("CostType-Dw")->color = QColor(80,80,120); + + KConfigGroup colorConfig(kconfig, QCString("CostColors")); + count = colorConfig.readNumEntry("Count", 0); + for (i=1;i<=count;i++) { + QString n = colorConfig.readEntry(QString("Name%1").arg(i)); + QColor color = colorConfig.readColorEntry(QString("Color%1").arg(i)); + + if (n.isEmpty()) continue; + + ColorSetting* cs = new ColorSetting; + cs->name = n; + cs->automatic = false; + cs->color = color; + + c->_colors.insert(n, cs); + + //qDebug("Read Color %s", n.ascii()); + } + + // source options + KConfigGroup sourceConfig(kconfig, QCString("Source")); + QStringList dirs; + dirs = sourceConfig.readListEntry("Dirs", ':'); + if (dirs.count()>0) c->_generalSourceDirs = dirs; + count = sourceConfig.readNumEntry("Count", 0); + c->_objectSourceDirs.clear(); + if (count>17) c->_objectSourceDirs.resize(count); + for (i=1;i<=count;i++) { + QString n = sourceConfig.readEntry(QString("Object%1").arg(i)); + dirs = sourceConfig.readListEntry(QString("Dirs%1").arg(i), ':'); + + if (n.isEmpty() || (dirs.count()==0)) continue; + + c->_objectSourceDirs.insert(n, new QStringList(dirs)); + } + + + // general options + KConfigGroup generalConfig(kconfig, QCString("General")); + c->_showPercentage = generalConfig.readBoolEntry("ShowPercentage", true); + c->_showExpanded = generalConfig.readBoolEntry("ShowExpanded", false); + c->_showCycles = generalConfig.readBoolEntry("ShowCycles", true); + c->_cycleCut = generalConfig.readDoubleNumEntry("CycleCut", 0.0); + c->_maxSymbolCount = generalConfig.readNumEntry("MaxSymbolCount", 10); + c->_maxListCount = generalConfig.readNumEntry("MaxListCount", 100); + c->_maxSymbolLength = generalConfig.readNumEntry("MaxSymbolLength", 30); + c->_percentPrecision = generalConfig.readNumEntry("PercentPrecision", 2); + + c->_context = generalConfig.readNumEntry("Context", 3); + c->_noCostInside = generalConfig.readNumEntry("NoCostInside", 20); + + // known cost types + if (TraceCostType::knownTypeCount()==0) { + + KConfigGroup ctConfig(kconfig, QCString("CostTypes")); + int ctCount = ctConfig.readNumEntry("Count", 0); + if (ctCount>0) { + for (int i=1;i<=ctCount;i++) { + QString n = ctConfig.readEntry(QString("Name%1").arg(i)); + QString l = ctConfig.readEntry(QString("Longname%1").arg(i)); + if (l.isEmpty()) l = knownLongName(n); + QString f = ctConfig.readEntry(QString("Formula%1").arg(i)); + if (f.isEmpty()) f = knownFormula(n); + + TraceCostType::add(new TraceCostType(n, l, f)); + } + } + else { + // add default types + + QString longName, formula; + TraceCostType* ct; + QStringList l = knownTypes(); + for ( QStringList::Iterator it = l.begin(); + it != l.end(); ++it ) { + longName = knownLongName(*it); + formula = knownFormula(*it); + ct = new TraceCostType(*it, longName, formula); + TraceCostType::add(ct); + } + } + } +} + +QColor Configuration::groupColor(TraceItem* cost) +{ + QString n; + + if (!cost) + n = QString("default"); + else + n = TraceCost::typeName(cost->type()) + "-" + cost->prettyName(); + + return color(n)->color; +} + +QColor Configuration::costTypeColor(TraceCostType* t) +{ + QString n; + + if (!t) + n = QString("CostType-default"); + else + n = QString("CostType-%1").arg(t->name()); + + return color(n)->color; +} + +QColor Configuration::functionColor(TraceCost::CostType gt, + TraceFunction* f) +{ + TraceCost* group = f; + QString n; + + switch(gt) { + case TraceCost::Object: group = f->object(); break; + case TraceCost::Class: group = f->cls(); break; + case TraceCost::File: group = f->file(); break; + default: + break; + } + + if (group != f) { + // first look for manual color of a function in a group + n = TraceCost::typeName(group->type()) + + "-" + group->prettyName() + + "-" + f->prettyName(); + + ColorSetting* cs = color(n, false); + if (cs) return cs->color; + } + return groupColor(group); +} + +Configuration::ColorSetting* Configuration::color(QString n, bool createNew) +{ +// qDebug("Color for %s", n.latin1()); + + // predefined ? + Configuration* c = config(); + ColorSetting* cs = c->_colors[n]; + if (cs || !createNew) return cs; + + // automatic colors... + int h = 0, s = 100; + const char* str = n.ascii(); + while (*str) { + h = (h * 37 + s* (unsigned)*str) % 256; + s = (s * 17 + h* (unsigned)*str) % 192; + str++; + } + + //qDebug("New color for %s: H %d, S %d", n.ascii(), h, 64+s); + QColor color = QColor(h, 64+s, 192, QColor::Hsv); + + cs = new ColorSetting; + cs->name = n; + cs->automatic = true; + cs->color = color; + c->_colors.insert(n, cs); + + //qDebug("new Color %s", n.ascii()); + + return cs; +} + +/* Gives back a list of all Source Base Directories of Objects in + * current trace. If a special object is given in 2nd argument, + * put its Source Base in front. + */ +QStringList Configuration::sourceDirs(TraceData* data, TraceObject* o) +{ + QStringList l = config()->_generalSourceDirs, *ol, *ol2 = 0; + TraceObjectMap::Iterator oit; + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) { + ol = config()->_objectSourceDirs[(*oit).name()]; + if (&(*oit) == o) { + ol2 = ol; + continue; + } + if (!ol) continue; + + for(unsigned int i=0;i<ol->count();i++) + l.prepend( (*ol)[i] ); + } + if (ol2) { + for(unsigned int i=0;i<ol2->count();i++) + l.prepend( (*ol2)[i] ); + } + if (0) kdDebug() << "Configuration::sourceDirs: " << l.join(":") << endl; + + return l; +} + +bool Configuration::showPercentage() +{ + return config()->_showPercentage; +} + +bool Configuration::showExpanded() +{ + return config()->_showExpanded; +} + +bool Configuration::showCycles() +{ + return config()->_showCycles; +} + +void Configuration::setShowPercentage(bool s) +{ + Configuration* c = config(); + if (c->_showPercentage == s) return; + + c->_showPercentage = s; +} + +void Configuration::setShowExpanded(bool s) +{ + Configuration* c = config(); + if (c->_showExpanded == s) return; + + c->_showExpanded = s; +} + +void Configuration::setShowCycles(bool s) +{ + Configuration* c = config(); + if (c->_showCycles == s) return; + + c->_showCycles = s; +} + +double Configuration::cycleCut() +{ + return config()->_cycleCut; +} + +int Configuration::percentPrecision() +{ + return config()->_percentPrecision; +} + +int Configuration::maxSymbolLength() +{ + return config()->_maxSymbolLength; +} + +QString Configuration::shortenSymbol(QString s) +{ + if ((int)s.length() > maxSymbolLength()) + s = s.left(maxSymbolLength()) + "..."; + return s; +} + +int Configuration::maxListCount() +{ + return config()->_maxListCount; +} + +int Configuration::maxSymbolCount() +{ + return config()->_maxSymbolCount; +} + +int Configuration::context() +{ + return config()->_context; +} + +int Configuration::noCostInside() +{ + return config()->_noCostInside; +} diff --git a/kcachegrind/kcachegrind/configuration.h b/kcachegrind/kcachegrind/configuration.h new file mode 100644 index 00000000..6c808c0c --- /dev/null +++ b/kcachegrind/kcachegrind/configuration.h @@ -0,0 +1,101 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Configuration for KCachegrind + */ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include <qcolor.h> +#include <qstringlist.h> +#include <qdict.h> + +#include "tracedata.h" + +class KConfig; + +class Configuration +{ + friend class ConfigDlg; + +public: + Configuration(); + + static Configuration* config(); + + static void saveOptions(KConfig*); + static void readOptions(KConfig*); + + // color for visualisation of an object + static QColor functionColor(TraceItem::CostType gt, TraceFunction*); + static QColor groupColor(TraceItem*); + static QColor costTypeColor(TraceCostType*); + static QStringList sourceDirs(TraceData*, TraceObject* o = 0); + static bool showPercentage(); + static bool showExpanded(); + static bool showCycles(); + + // lower percentage limit of cost items filled into lists + static int percentPrecision(); + // max symbol lengths/count in tooltip/popup + static int maxSymbolLength(); + // strip a symbol name according to <maxSymbolLength> + static QString shortenSymbol(QString); + static int maxSymbolCount(); + // max. number of items in lists + static int maxListCount(); + + // how many lines of context to show before/after annotated source/assembler + static int context(); + // how many lines without cost are still regarded as inside a function + static int noCostInside(); + + static void setShowPercentage(bool); + static void setShowExpanded(bool); + + static void setShowCycles(bool); + // upper limit for cutting of a call in cycle detection + static double cycleCut(); + +private: + struct ColorSetting { + QString name; + QColor color; + bool automatic; + }; + + static ColorSetting* color(QString, bool createNew = true); + + QDict<ColorSetting> _colors; + + QStringList _generalSourceDirs; + QDict<QStringList> _objectSourceDirs; + + bool _showPercentage, _showExpanded, _showCycles; + double _cycleCut; + int _percentPrecision; + int _maxSymbolLength, _maxSymbolCount, _maxListCount; + int _context, _noCostInside; + + static Configuration* _config; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/costlistitem.cpp b/kcachegrind/kcachegrind/costlistitem.cpp new file mode 100644 index 00000000..bfe22730 --- /dev/null +++ b/kcachegrind/kcachegrind/costlistitem.cpp @@ -0,0 +1,136 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <math.h> + +#include <qpainter.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kapplication.h> + +#include "listutils.h" +#include "costlistitem.h" +#include "coverage.h" +#include "configuration.h" + +// CostListItem + + +CostListItem::CostListItem(QListView* parent, TraceCostItem* costItem, + TraceCostType* ct, int size) + :QListViewItem(parent) +{ + _groupSize = size; + _skipped = 0; + _costItem = costItem; + setCostType(ct); + + if (costItem) { + updateName(); + setPixmap(1, colorPixmap(10, 10, + Configuration::groupColor(_costItem))); + } +} + +CostListItem::CostListItem(QListView* parent, int skipped, + TraceCostItem* costItem, TraceCostType* ct) + :QListViewItem(parent) +{ + _skipped = skipped; + _costItem = costItem; + setCostType(ct); + + setText(1, i18n("(%n item skipped)", "(%n items skipped)", _skipped)); +} + +void CostListItem::setCostType(TraceCostType* ct) +{ + _costType = ct; + update(); +} + +void CostListItem::updateName() +{ + if (!_costItem) return; + + QString n = _costItem->prettyName(); + if (_groupSize>=0) n += QString(" (%1)").arg(_groupSize); + + setText(1, n); +} + +void CostListItem::setSize(int s) +{ + _groupSize = s; + updateName(); +} + +void CostListItem::update() +{ + if (!_costItem) return; + TraceData* d = _costItem->data(); + + double total = d->subCost(_costType); + if (total == 0.0) { + setText(0, QString("---")); + setPixmap(0, QPixmap()); + return; + } + + _pure = _costItem->subCost(_costType); + double pure = 100.0 * _pure / total; + QString str; + if (Configuration::showPercentage()) + str = QString("%1").arg(pure, 0, 'f', Configuration::percentPrecision()); + else + str = _costItem->prettySubCost(_costType); + + if (_skipped) { + // special handling for skip entries... + setText(0, QString("< %1").arg(str)); + return; + } + + setText(0, str); + setPixmap(0, costPixmap(_costType, _costItem, total, false)); +} + +int CostListItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + const CostListItem* fi1 = this; + const CostListItem* fi2 = (CostListItem*) i; + + // we always want descending order + if (ascending) { + fi1 = fi2; + fi2 = this; + } + + // a skip entry is always sorted last + if (fi1->_skipped) return -1; + if (fi2->_skipped) return 1; + + if (col==0) { + if (fi1->_pure < fi2->_pure) return -1; + if (fi1->_pure > fi2->_pure) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} diff --git a/kcachegrind/kcachegrind/costlistitem.h b/kcachegrind/kcachegrind/costlistitem.h new file mode 100644 index 00000000..28b90248 --- /dev/null +++ b/kcachegrind/kcachegrind/costlistitem.h @@ -0,0 +1,52 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef COSTLISTITEM_H +#define COSTLISTITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class CostListItem: public QListViewItem +{ +public: + CostListItem(QListView* parent, TraceCostItem* cost, + TraceCostType* ct, int size = -1); + // entry with multiple skipped items + CostListItem(QListView* parent, int skipped, TraceCostItem* cost, + TraceCostType* ct); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceCostItem* costItem() { return (_skipped) ? 0 : _costItem; } + void setCostType(TraceCostType* ct); + void update(); + void setSize(int s); + +private: + void updateName(); + + SubCost _pure; + TraceCostType* _costType; + TraceCostItem* _costItem; + // >0 only for last item in list, if items are skipped + int _skipped; + // number of items in group, is put in parenthesis after name + int _groupSize; +}; + +#endif diff --git a/kcachegrind/kcachegrind/costtypeitem.cpp b/kcachegrind/kcachegrind/costtypeitem.cpp new file mode 100644 index 00000000..99d123d8 --- /dev/null +++ b/kcachegrind/kcachegrind/costtypeitem.cpp @@ -0,0 +1,149 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of cost type view. + */ + +#include <qpixmap.h> +#include <klocale.h> + +#include "configuration.h" +#include "listutils.h" +#include "costtypeitem.h" + + +// CostTypeItem + + +CostTypeItem::CostTypeItem(QListView* parent, TraceCostItem* costItem, + TraceCostType* ct, TraceCost::CostType gt) + :QListViewItem(parent) +{ + _costItem = costItem; + _costType = ct; + _groupType = gt; + + if (ct) { + setText(0, ct->longName()); + setText(3, ct->name()); + QString formula = ct->formula(); + setText(5, formula); + if (!formula.isEmpty()) { + setText(4, "="); + // we have a virtual type: allow editing + setRenameEnabled(0, true); + setRenameEnabled(3, true); + setRenameEnabled(5, true); + } + } + else { + setText(0, i18n("Unknown Type")); + } + update(); +} + +void CostTypeItem::setGroupType(TraceCost::CostType gt) +{ + if (_groupType == gt) return; + + _groupType = gt; + update(); +} + +void CostTypeItem::update() +{ + TraceData* d = _costItem ? _costItem->data() : 0; + double total = d ? ((double)d->subCost(_costType)) : 0.0; + + if (total == 0.0) { + setText(1, "-"); + setPixmap(1, QPixmap()); + setText(2, "-"); + setPixmap(2, QPixmap()); + return; + } + + TraceFunction* f = (_costItem->type()==TraceCost::Function) ? + (TraceFunction*)_costItem : 0; + + TraceCost* selfTotalCost = f ? f->data() : d; + if (f && Configuration::showExpanded()) { + switch(_groupType) { + case TraceCost::Object: selfTotalCost = f->object(); break; + case TraceCost::Class: selfTotalCost = f->cls(); break; + case TraceCost::File: selfTotalCost = f->file(); break; + case TraceCost::FunctionCycle: selfTotalCost = f->cycle(); break; + default: break; + } + } + if (_costItem->type()==TraceCost::FunctionCycle) { + f = (TraceFunction*)_costItem; + selfTotalCost = f->data(); + } + + double selfTotal = selfTotalCost->subCost(_costType); + + // for all cost items there's a self cost + _pure = _costItem ? _costItem->subCost(_costType) : SubCost(0); + double pure = 100.0 * _pure / selfTotal; + if (Configuration::showPercentage()) { + setText(2, QString("%1") + .arg(pure, 0, 'f', Configuration::percentPrecision())); + } + else + setText(2, _costItem->prettySubCost(_costType)); + + setPixmap(2, costPixmap(_costType, _costItem, selfTotal, false)); + + if (!f) { + setText(1, "-"); + setPixmap(1, QPixmap()); + return; + } + + _sum = f->inclusive()->subCost(_costType); + double sum = 100.0 * _sum / total; + if (Configuration::showPercentage()) { + setText(1, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + } + else + setText(1, _sum.pretty()); + + setPixmap(1, costPixmap(_costType, f->inclusive(), total, false)); +} + + +int CostTypeItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + CostTypeItem* fi = (CostTypeItem*) i; + if (col==0) { + if (_sum < fi->_sum) return -1; + if (_sum > fi->_sum) return 1; + return 0; + } + if (col==1) { + if (_pure < fi->_pure) return -1; + if (_pure > fi->_pure) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} + + diff --git a/kcachegrind/kcachegrind/costtypeitem.h b/kcachegrind/kcachegrind/costtypeitem.h new file mode 100644 index 00000000..a9df7d91 --- /dev/null +++ b/kcachegrind/kcachegrind/costtypeitem.h @@ -0,0 +1,50 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of cost type view. + */ + +#ifndef COSTTYEPITEM_H +#define COSTTYEPITEM_H + +#include <qlistview.h> +#include "tracedata.h" + + +class CostTypeItem: public QListViewItem +{ +public: + CostTypeItem(QListView* parent, TraceCostItem* costItem, + TraceCostType* ct, TraceCost::CostType gt); + + int compare(QListViewItem * i, int col, bool ascending ) const; + void setGroupType(TraceCost::CostType); + TraceCostItem* costItem() { return _costItem; } + TraceCostType* costType() { return _costType; } + void update(); + +private: + SubCost _sum, _pure; + TraceCostType* _costType; + TraceCostItem* _costItem; + TraceCost::CostType _groupType; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/costtypeview.cpp b/kcachegrind/kcachegrind/costtypeview.cpp new file mode 100644 index 00000000..6c08a8c4 --- /dev/null +++ b/kcachegrind/kcachegrind/costtypeview.cpp @@ -0,0 +1,310 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Cost Type View + */ + +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <klocale.h> + +#include "configuration.h" +#include "costtypeitem.h" +#include "costtypeview.h" +#include "toplevel.h" + + +// +// CostTypeView +// + + +CostTypeView::CostTypeView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + addColumn( i18n( "Event Type" ) ); + addColumn( i18n( "Incl." ) ); + addColumn( i18n( "Self" ) ); + addColumn( i18n( "Short" ) ); + addColumn( QString::null ); + addColumn( i18n( "Formula" ) ); + + setSorting(-1); + setAllColumnsShowFocus(true); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + setColumnAlignment(3, Qt::AlignRight); + setMinimumHeight(50); + + connect( this, + SIGNAL( selectionChanged(QListViewItem*) ), + SLOT( selectedSlot(QListViewItem*) ) ); + + connect( this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + connect(this, + SIGNAL(doubleClicked(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(returnPressed(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(itemRenamed(QListViewItem*,int,const QString&)), + SLOT(renamedSlot(QListViewItem*,int,const QString&))); + + QWhatsThis::add( this, whatsThis() ); +} + +QString CostTypeView::whatsThis() const +{ + return i18n( "<b>Cost Types List</b>" + "<p>This list shows all cost types available " + "and what the self/inclusive cost of the " + "current selected function is for that cost type.</p>" + "<p>By choosing a cost type from the list, " + "you change the cost type of costs shown " + "all over KCachegrind to be the selected one.</p>"); +} + + +void CostTypeView::context(QListViewItem* i, const QPoint & p, int) +{ + QPopupMenu popup; + + TraceCostType* ct = i ? ((CostTypeItem*) i)->costType() : 0; + + if (ct) + popup.insertItem(i18n("Set Secondary Event Type"), 99); + if (_costType2) + popup.insertItem(i18n("Remove Secondary Event Type"), 98); + if (popup.count()>0) + popup.insertSeparator(); + + if (ct && !ct->isReal()) { + popup.insertItem(i18n("Edit Long Name"), 93); + popup.insertItem(i18n("Edit Short Name"), 94); + popup.insertItem(i18n("Edit Formula"), 95); + popup.insertItem(i18n("Remove"), 96); + popup.insertSeparator(); + } + + addGoMenu(&popup); + + popup.insertSeparator(); + popup.insertItem(i18n("New Cost Type ..."), 97); + + int r = popup.exec(p); + if (r == 98) selectedCostType2(0); + else if (r == 99) selectedCostType2(ct); + else if (r == 93) i->startRename(0); + else if (r == 94) i->startRename(3); + else if (r == 95) i->startRename(5); + else if (r == 96) { + + // search for a previous type + TraceCostType* prev = 0, *ct = 0; + TraceCostMapping* m = _data->mapping(); + for (int i=0;i<m->realCount();i++) { + ct = m->realType(i); + if (ct) prev = ct; + } + for (int i=0;i<m->virtualCount();i++) { + ct = m->virtualType(i); + if (ct == _costType) break; + if (ct) prev = ct; + } + + if (_data->mapping()->remove(ct)) { + // select previous cost type + selectedCostType(prev); + if (_costType2 == ct) + selectedCostType2(prev); + refresh(); + } + } + else if (r == 97) { + int i = 1; + while(1) { + if (!TraceCostType::knownVirtualType(i18n("New%1").arg(i))) + break; + i++; + } + // add same new cost type to this mapping and to known types + QString shortName = i18n("New%1").arg(i); + QString longName = i18n("New Cost Type %1").arg(i); + TraceCostType::add(new TraceCostType(shortName, longName, "0")); + _data->mapping()->add(new TraceCostType(shortName, longName, "0")); + refresh(); + } +} + +void CostTypeView::selectedSlot(QListViewItem * i) +{ + TraceCostType* ct = i ? ((CostTypeItem*) i)->costType() : 0; + if (ct) + selectedCostType(ct); +} + +void CostTypeView::activatedSlot(QListViewItem * i) +{ + TraceCostType* ct = i ? ((CostTypeItem*) i)->costType() : 0; + if (ct) + selectedCostType2(ct); +} + +TraceItem* CostTypeView::canShow(TraceItem* i) +{ + if (!i) return 0; + + switch(i->type()) { + case TraceCost::Object: + case TraceCost::Class: + case TraceCost::File: + case TraceCost::Call: + case TraceCost::FunctionCycle: + case TraceCost::Function: + break; + default: + return 0; + } + return i; +} + +void CostTypeView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) return; + + if (changeType == costType2Changed) return; + + if (changeType == groupTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + ((CostTypeItem*)item)->setGroupType(_groupType); + + return; + } + + if (changeType == costTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + if ( ((CostTypeItem*)item)->costType() == _costType) { + setSelected(item, true); + ensureItemVisible(item); + break; + } + + return; + } + + if (changeType == partsChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + ((CostTypeItem*)item)->update(); + + return; + } + + + refresh(); +} + +void CostTypeView::refresh() +{ + clear(); + setColumnWidth(1, 50); + setColumnWidth(2, 50); + + if (!_data || !_activeItem) return; + switch(_activeItem->type()) { + case TraceCost::Object: + case TraceCost::Class: + case TraceCost::File: + case TraceCost::FunctionCycle: + case TraceCost::Function: + break; + default: + return; + } + TraceCostItem* c = (TraceCostItem*) _activeItem; + + TraceCostType* ct =0 ; + QListViewItem* item = 0; + QString sumStr, pureStr; + QListViewItem* costItem=0; + + TraceCostMapping* m = _data->mapping(); + for (int i=m->virtualCount()-1;i>=0;i--) { + ct = m->virtualType(i); + if (!ct) continue; + item = new CostTypeItem(this, c, ct, _groupType); + if (ct == _costType) costItem = item; + } + for (int i=m->realCount()-1;i>=0;i--) { + ct = m->realType(i); + item = new CostTypeItem(this, c, ct, _groupType); + if (ct == _costType) costItem = item; + } + + if (costItem) { + setSelected(costItem, true); + ensureItemVisible(costItem); + } + + if (item) setMinimumHeight(3*item->height()); +} + + +void CostTypeView::renamedSlot(QListViewItem* item,int c,const QString& t) +{ + TraceCostType* ct = item ? ((CostTypeItem*) item)->costType() : 0; + if (!ct || ct->isReal()) return; + + // search for matching known Type + int knownCount = TraceCostType::knownTypeCount(); + TraceCostType* known = 0; + for (int i=0; i<knownCount; i++) { + known = TraceCostType::knownType(i); + if (known->name() == ct->name()) break; + } + + if (c == 0) { + ct->setLongName(t); + if (known) known->setLongName(t); + } + else if (c == 3) { + ct->setName(t); + if (known) known->setName(t); + } + else if (c == 5) { + ct->setFormula(t); + if (known) known->setFormula(t); + } + else return; + + if (_topLevel) _topLevel->configChanged(); + refresh(); +} + +#include "costtypeview.moc" diff --git a/kcachegrind/kcachegrind/costtypeview.h b/kcachegrind/kcachegrind/costtypeview.h new file mode 100644 index 00000000..c5f51494 --- /dev/null +++ b/kcachegrind/kcachegrind/costtypeview.h @@ -0,0 +1,53 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Cost Type View + */ + +#ifndef COSTTYPEVIEW_H +#define COSTTYPEVIEW_H + +#include <qlistview.h> +#include "tracedata.h" +#include "traceitemview.h" + +class CostTypeView: public QListView, public TraceItemView +{ + Q_OBJECT + +public: + CostTypeView(TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + +private slots: + void context(QListViewItem*,const QPoint &, int); + void selectedSlot(QListViewItem*); + void activatedSlot(QListViewItem*); + void renamedSlot(QListViewItem*,int,const QString&); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); +}; + +#endif diff --git a/kcachegrind/kcachegrind/coverage.cpp b/kcachegrind/kcachegrind/coverage.cpp new file mode 100644 index 00000000..b928fc42 --- /dev/null +++ b/kcachegrind/kcachegrind/coverage.cpp @@ -0,0 +1,329 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Function Coverage Analysis + */ + +#include "coverage.h" + +//#define DEBUG_COVERAGE 1 + +TraceCostType* Coverage::_costType; + +const int Coverage::maxHistogramDepth = maxHistogramDepthValue; +const int Coverage::Rtti = 1; + +Coverage::Coverage() +{ +} + +void Coverage::init() +{ + _self = 0.0; + _incl = 0.0; + _callCount = 0.0; + // should always be overwritten before usage + _firstPercentage = 1.0; + _minDistance = 9999; + _maxDistance = 0; + _active = false; + _inRecursion = false; + for (int i = 0;i<maxHistogramDepth;i++) { + _selfHisto[i] = 0.0; + _inclHisto[i] = 0.0; + } + + _valid = true; +} + +int Coverage::inclusiveMedian() +{ + double maxP = _inclHisto[0]; + int medD = 0; + for (int i = 1;i<maxHistogramDepth;i++) + if (_inclHisto[i]>maxP) { + maxP = _inclHisto[i]; + medD = i; + } + + return medD; +} + +int Coverage::selfMedian() +{ + double maxP = _selfHisto[0]; + int medD = 0; + for (int i = 1;i<maxHistogramDepth;i++) + if (_selfHisto[i]>maxP) { + maxP = _selfHisto[i]; + medD = i; + } + + return medD; +} + +TraceFunctionList Coverage::coverage(TraceFunction* f, CoverageMode m, + TraceCostType* ct) +{ + invalidate(f->data(), Coverage::Rtti); + + _costType = ct; + + // function f takes ownership over c! + Coverage* c = new Coverage(); + c->setFunction(f); + c->init(); + + TraceFunctionList l; + + if (m == Caller) + c->addCallerCoverage(l, 1.0, 0); + else + c->addCallingCoverage(l, 1.0, 1.0, 0); + + return l; +} + +void Coverage::addCallerCoverage(TraceFunctionList& fList, + double pBack, int d) +{ + TraceCallList cList; + TraceCall* call; + Coverage* c; + + if (_inRecursion) return; + + double incl; + incl = (double) (_function->inclusive()->subCost(_costType)); + + if (_active) { +#ifdef DEBUG_COVERAGE + qDebug("CallerCov: D %d, %s (was active, incl %f, self %f): newP %f", d, + _function->prettyName().ascii(), _incl, _self, pBack); +#endif + _inRecursion = true; + } + else { + _active = true; + + // only add cost if this is no recursion + + _incl += pBack; + _firstPercentage = pBack; + + if (_minDistance > d) _minDistance = d; + if (_maxDistance < d) _maxDistance = d; + if (d<maxHistogramDepth) { + _inclHisto[d] += pBack; + } + else { + _inclHisto[maxHistogramDepth-1] += pBack; + } + +#ifdef DEBUG_COVERAGE + qDebug("CallerCov: D %d, %s (now active, new incl %f): newP %f", + d, _function->prettyName().ascii(), _incl, pBack); +#endif + } + + double callVal, pBackNew; + + cList = _function->callers(); + for (call=cList.first();call;call=cList.next()) { + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (call->subCost(_costType)>0) { + TraceFunction* caller = call->caller(); + + c = (Coverage*) caller->assoziation(rtti()); + if (!c) { + c = new Coverage(); + c->setFunction(caller); + } + if (!c->isValid()) { + c->init(); + fList.append(caller); + } + + if (c->isActive()) continue; + if (c->inRecursion()) continue; + + callVal = (double) call->subCost(_costType); + pBackNew = pBack * (callVal / incl); + + // FIXME ?!? + + if (!c->isActive()) { + if (d>=0) + c->callCount() += (double)call->callCount(); + else + c->callCount() += _callCount; + } + else { + // adjust pNew by sum of geometric series of recursion factor. + // Thus we can avoid endless recursion here + pBackNew *= 1.0 / (1.0 - pBackNew / c->firstPercentage()); + } + + // Limit depth + if (pBackNew > 0.0001) + c->addCallerCoverage(fList, pBackNew, d+1); + } + } + + if (_inRecursion) + _inRecursion = false; + else if (_active) + _active = false; +} + +/** + * pForward is time on percent used, + * pBack is given to allow for calculation of call counts + */ +void Coverage::addCallingCoverage(TraceFunctionList& fList, + double pForward, double pBack, int d) +{ + TraceCallList cList; + TraceCall* call; + Coverage* c; + + if (_inRecursion) return; + +#ifdef DEBUG_COVERAGE + static const char* spaces = " "; +#endif + + double self, incl; + incl = (double) (_function->inclusive()->subCost(_costType)); + +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s - %s (incl %f, self %f): forward %f, back %f", + spaces+strlen(spaces)-d, + _function->prettyName().ascii(), _incl, _self, pForward, pBack); +#endif + + + if (_active) { + _inRecursion = true; + +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s < %s: STOP (is active)", + spaces+strlen(spaces)-d, + _function->prettyName().ascii()); +#endif + + } + else { + _active = true; + + // only add cost if this is no recursion + self = pForward * (_function->subCost(_costType)) / incl; + _incl += pForward; + _self += self; + _firstPercentage = pForward; + + if (_minDistance > d) _minDistance = d; + if (_maxDistance < d) _maxDistance = d; + if (d<maxHistogramDepth) { + _inclHisto[d] += pForward; + _selfHisto[d] += self; + } + else { + _inclHisto[maxHistogramDepth-1] += pForward; + _selfHisto[maxHistogramDepth-1] += self; + } + +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s < %s (incl %f, self %f)", + spaces+strlen(spaces)-d, + _function->prettyName().ascii(), _incl, _self); +#endif + } + + double callVal, pForwardNew, pBackNew; + + cList = _function->callings(); + for (call=cList.first();call;call=cList.next()) { + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (call->subCost(_costType)>0) { + TraceFunction* calling = call->called(); + + c = (Coverage*) calling->assoziation(rtti()); + if (!c) { + c = new Coverage(); + c->setFunction(calling); + } + if (!c->isValid()) { + c->init(); + fList.append(calling); + } + + if (c->isActive()) continue; + if (c->inRecursion()) continue; + + callVal = (double) call->subCost(_costType); + pForwardNew = pForward * (callVal / incl); + pBackNew = pBack * (callVal / + calling->inclusive()->subCost(_costType)); + + if (!c->isActive()) { + c->callCount() += pBack * call->callCount(); + +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s > %s: forward %f, back %f, calls %f -> %f, now %f", + spaces+strlen(spaces)-d, + calling->prettyName().ascii(), + pForwardNew, pBackNew, + (double)call->callCount(), + pBack * call->callCount(), + c->callCount()); +#endif + } + else { + // adjust pNew by sum of geometric series of recursion factor. + // Thus we can avoid endless recursion here + double fFactor = 1.0 / (1.0 - pForwardNew / c->firstPercentage()); + double bFactor = 1.0 / (1.0 - pBackNew); +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s Recursion - origP %f, actP %f => factor %f, newP %f", + spaces+strlen(spaces)-d, + c->firstPercentage(), pForwardNew, + fFactor, pForwardNew * fFactor); +#endif + pForwardNew *= fFactor; + pBackNew *= bFactor; + + } + + // Limit depth + if (pForwardNew > 0.0001) + c->addCallingCoverage(fList, pForwardNew, pBackNew, d+1); + } + } + + if (_inRecursion) + _inRecursion = false; + else if (_active) + _active = false; +} + diff --git a/kcachegrind/kcachegrind/coverage.h b/kcachegrind/kcachegrind/coverage.h new file mode 100644 index 00000000..b6a5107e --- /dev/null +++ b/kcachegrind/kcachegrind/coverage.h @@ -0,0 +1,102 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Function Coverage Analysis + */ + +#ifndef COVERAGE_H +#define COVERAGE_H + +#include "tracedata.h" + +/** + * Coverage of a function. + * When analysis is done, every function involved will have a + * pointer to an object of this class. + * + * This function also holds the main routine for coverage analysis, + * Coverage::coverage(), as static method. + */ +class Coverage : public TraceAssoziation +{ +public: + /* Direction of coverage analysis */ + enum CoverageMode { Caller, Called }; + + // max depth for distance histogram +#define maxHistogramDepthValue 40 + static const int maxHistogramDepth; + + static const int Rtti; + + Coverage(); + + virtual int rtti() { return Rtti; } + void init(); + + TraceFunction* function() { return _function; } + double self() { return _self; } + double inclusive() { return _incl; } + double firstPercentage() { return _firstPercentage; } + double& callCount() { return _callCount; } + int minDistance() { return _minDistance; } + int maxDistance() { return _maxDistance; } + int inclusiveMedian(); + int selfMedian(); + double* selfHistogram() { return _selfHisto; } + double* inclusiveHistogram() { return _inclHisto; } + bool isActive() { return _active; } + bool inRecursion() { return _inRecursion; } + + void setSelf(float p) { _self = p; } + void setInclusive(float p) { _incl = p; } + void setCallCount(float cc) { _callCount = cc; } + void setActive(bool a) { _active = a; } + void setInRecursion(bool r) { _inRecursion = r; } + + /** + * Calculate coverage of all functions based on function f. + * If mode is Called, the coverage of functions called by + * f is calculated, otherwise that of functions calling f. + * SubCost type ct is used for the analysis. + * Self values are undefined for Caller mode. + * + * Returns list of functions covered. + * Coverage degree of returned functions can be get + * with function->coverage()->percentage() + */ + static TraceFunctionList coverage(TraceFunction* f, CoverageMode m, + TraceCostType* ct); + +private: + void addCallerCoverage(TraceFunctionList& l, double, int d); + void addCallingCoverage(TraceFunctionList& l, double, double, int d); + + double _self, _incl, _firstPercentage, _callCount; + int _minDistance, _maxDistance; + bool _active, _inRecursion; + double _selfHisto[maxHistogramDepthValue]; + double _inclHisto[maxHistogramDepthValue]; + + // temporary set for one coverage analysis + static TraceCostType* _costType; +}; + +#endif + diff --git a/kcachegrind/kcachegrind/coverageitem.cpp b/kcachegrind/kcachegrind/coverageitem.cpp new file mode 100644 index 00000000..d3542a95 --- /dev/null +++ b/kcachegrind/kcachegrind/coverageitem.cpp @@ -0,0 +1,343 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of coverage view. + */ + +#include <qpixmap.h> +#include <klocale.h> + +#include "configuration.h" +#include "listutils.h" +#include "coverage.h" +#include "coverageitem.h" + + +// CallerCoverageItem + + +CallerCoverageItem::CallerCoverageItem(QListView* parent, Coverage* c, + TraceFunction* base, + TraceCostType* ct, + TraceCost::CostType gt) + : QListViewItem(parent) +{ + _skipped = 0; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = TraceCost::NoCostType; + + setText(3, _function->prettyNameWithLocation()); + + setCostType(ct); + setGroupType(gt); +} + +CallerCoverageItem::CallerCoverageItem(QListView* parent, int skipped, Coverage* c, + TraceFunction* base, + TraceCostType* ct, + TraceCost::CostType gt) + : QListViewItem(parent) +{ + _skipped = skipped; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = TraceCost::NoCostType; + + setText(3, i18n("(%n function skipped)", "(%n functions skipped)", _skipped)); + + setCostType(ct); + setGroupType(gt); +} + +void CallerCoverageItem::setGroupType(TraceCost::CostType gt) +{ + if (_skipped) return; + if (_groupType == gt) return; + _groupType = gt; + + QColor c = Configuration::functionColor(_groupType, _function); + setPixmap(3, colorPixmap(10, 10, c)); +} + +void CallerCoverageItem::setCostType(TraceCostType* ct) +{ + _costType = ct; + update(); +} + +void CallerCoverageItem::update() +{ + if (!_coverage) { + setText(0, QString::null); + setText(1, QString::null); + return; + } + + _pSum = 100.0 * _coverage->inclusive(); + SubCost realSum = _base->inclusive()->subCost(_costType); + _sum = SubCost(realSum * _coverage->inclusive()); + QString str; + if (Configuration::showPercentage()) + str = QString("%1").arg(_pSum, 0, 'f', Configuration::percentPrecision()); + else + str = _sum.pretty(); + + if (_skipped) { + setText(0, QString("< %1").arg(str)); + return; + } + + setText(0, str); + setPixmap(0, partitionPixmap(25, 10, _coverage->inclusiveHistogram(), 0, + Coverage::maxHistogramDepth, false)); + + // call count + _cc = SubCost(_coverage->callCount()); + setText(2, _cc ? _cc.pretty() : QString("(0)")); + + // distance (min/max/median) + _distance = _coverage->inclusiveMedian(); + QString distString; + if (_coverage->minDistance() == _coverage->maxDistance()) + distString = QString::number(_distance); + else + distString = QString("%1-%2 (%3)") + .arg(_coverage->minDistance()) + .arg(_coverage->maxDistance()) + .arg(_distance); + setText(1, distString); +} + + +int CallerCoverageItem::compare(QListViewItem * i, + int col, bool ascending ) const +{ + const CallerCoverageItem* ci1 = this; + const CallerCoverageItem* ci2 = (CallerCoverageItem*) i; + + // we always want descending order + if (ascending) { + ci1 = ci2; + ci2 = this; + } + + // a skip entry is always sorted last + if (ci1->_skipped) return -1; + if (ci2->_skipped) return 1; + + if (col==0) { + if (ci1->_pSum < ci2->_pSum) return -1; + if (ci1->_pSum > ci2->_pSum) return 1; + + // for same percentage (e.g. all 100%), use distance info + if (ci1->_distance < ci2->_distance) return -1; + if (ci1->_distance > ci2->_distance) return 1; + return 0; + } + + if (col==1) { + if (ci1->_distance < ci2->_distance) return -1; + if (ci1->_distance > ci2->_distance) return 1; + return 0; + } + + if (col==2) { + if (ci1->_cc < ci2->_cc) return -1; + if (ci1->_cc > ci2->_cc) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} + + +// CalleeCoverageItem + + +CalleeCoverageItem::CalleeCoverageItem(QListView* parent, Coverage* c, + TraceFunction* base, + TraceCostType* ct, + TraceCost::CostType gt) + : QListViewItem(parent) +{ + _skipped = 0; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = TraceCost::NoCostType; + + setText(4, _function->prettyNameWithLocation()); + + setCostType(ct); + setGroupType(gt); +} + +CalleeCoverageItem::CalleeCoverageItem(QListView* parent, int skipped, Coverage* c, + TraceFunction* base, + TraceCostType* ct, + TraceCost::CostType gt) + : QListViewItem(parent) +{ + _skipped = skipped; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = TraceCost::NoCostType; + + setText(4, i18n("(%n function skipped)", "(%n functions skipped)", _skipped)); + + setCostType(ct); + setGroupType(gt); +} + +void CalleeCoverageItem::setGroupType(TraceCost::CostType gt) +{ + if (_skipped) return; + if (_groupType == gt) return; + _groupType = gt; + + QColor c = Configuration::functionColor(_groupType, _function); + setPixmap(4, colorPixmap(10, 10, c)); +} + +void CalleeCoverageItem::setCostType(TraceCostType* ct) +{ + _costType = ct; + update(); +} + +void CalleeCoverageItem::update() +{ + if (!_coverage) { + setText(0, QString::null); + setText(1, QString::null); + setText(2, QString::null); + return; + } + + _pSum = 100.0 * _coverage->inclusive(); + + // pSum/pSelf are percentages of inclusive cost of base + SubCost realSum = _base->inclusive()->subCost(_costType); + _sum = SubCost(realSum * _coverage->inclusive()); + + + QString str; + if (Configuration::showPercentage()) + str = QString("%1").arg(_pSum, 0, 'f', Configuration::percentPrecision()); + else + str = _sum.pretty(); + + if (_skipped) { + str = QString("< %1").arg(str); + setText(0, str); + setText(1, str); + return; + } + setText(0, str); + + _pSelf = 100.0 * _coverage->self(); + _self = SubCost(realSum * _coverage->self()); + + if (Configuration::showPercentage()) { + setText(1, QString("%1") + .arg(_pSelf, 0, 'f', Configuration::percentPrecision())); + } + else { + setText(1, _self.pretty()); + } + + setPixmap(0, partitionPixmap(25, 10, _coverage->inclusiveHistogram(), 0, + Coverage::maxHistogramDepth, false)); + setPixmap(1, partitionPixmap(25, 10, _coverage->selfHistogram(), 0, + Coverage::maxHistogramDepth, false)); + + + _cc = SubCost(_coverage->callCount()); + setText(3, _cc ? _cc.pretty() : QString("(0)")); + + // for comparations + _distance = _coverage->inclusiveMedian(); + QString distString; + if (_coverage->minDistance() == _coverage->maxDistance()) + distString = QString::number(_distance); + else { + int sMed = _coverage->selfMedian(); + QString med; + if (_distance == sMed) + med = QString::number(_distance); + else + med = QString("%1/%2").arg(_distance).arg(sMed); + + distString = QString("%1-%2 (%3)") + .arg(_coverage->minDistance()) + .arg(_coverage->maxDistance()) + .arg(med); + } + setText(2, distString); +} + + +int CalleeCoverageItem::compare(QListViewItem * i, + int col, bool ascending ) const +{ + CalleeCoverageItem* ci = (CalleeCoverageItem*) i; + + // a skip entry is always sorted last + if (_skipped) return -1; + if (ci->_skipped) return 1; + + if (col==0) { + if (_pSum < ci->_pSum) return -1; + if (_pSum > ci->_pSum) return 1; + + // for same percentage (e.g. all 100%), use distance info + if (_distance < ci->_distance) return -1; + if (_distance > ci->_distance) return 1; + return 0; + } + + if (col==1) { + if (_pSelf < ci->_pSelf) return -1; + if (_pSelf > ci->_pSelf) return 1; + + // for same percentage (e.g. all 100%), use distance info + if (_distance < ci->_distance) return -1; + if (_distance > ci->_distance) return 1; + return 0; + } + + if (col==2) { + // we want to sort the distance in contra direction to costs + if (_distance < ci->_distance) return 1; + if (_distance > ci->_distance) return -1; + return 0; + } + + if (col==3) { + if (_cc < ci->_cc) return -1; + if (_cc > ci->_cc) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} + + diff --git a/kcachegrind/kcachegrind/coverageitem.h b/kcachegrind/kcachegrind/coverageitem.h new file mode 100644 index 00000000..99d77625 --- /dev/null +++ b/kcachegrind/kcachegrind/coverageitem.h @@ -0,0 +1,82 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of coverage view. + */ + +#ifndef COVERAGEITEM_H +#define COVERAGEITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class Coverage; + +class CallerCoverageItem: public QListViewItem +{ +public: + CallerCoverageItem(QListView* parent, Coverage* c, TraceFunction* base, + TraceCostType* ct, TraceCost::CostType gt); + CallerCoverageItem(QListView* parent, int skipped, Coverage* c, TraceFunction* base, + TraceCostType* ct, TraceCost::CostType gt); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceFunction* function() { return (_skipped) ? 0 : _function; } + void setCostType(TraceCostType* ct); + void setGroupType(TraceCost::CostType); + void update(); + +private: + float _pSum; + SubCost _sum; + TraceCostType* _costType; + TraceCost::CostType _groupType; + SubCost _cc; + int _distance, _skipped; + TraceFunction *_function, *_base; + Coverage* _coverage; +}; + + +class CalleeCoverageItem: public QListViewItem +{ +public: + CalleeCoverageItem(QListView* parent, Coverage* c, TraceFunction* base, + TraceCostType* ct, TraceCost::CostType gt); + CalleeCoverageItem(QListView* parent, int skipped, Coverage* c, TraceFunction* base, + TraceCostType* ct, TraceCost::CostType gt); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceFunction* function() { return (_skipped) ? 0 : _function; } + void setCostType(TraceCostType* ct); + void setGroupType(TraceCost::CostType); + void update(); + +private: + float _pSum, _pSelf; + SubCost _sum, _self; + TraceCostType* _costType; + TraceCost::CostType _groupType; + SubCost _cc; + int _distance, _skipped; + TraceFunction *_function, *_base; + Coverage* _coverage; +}; + +#endif diff --git a/kcachegrind/kcachegrind/coverageview.cpp b/kcachegrind/kcachegrind/coverageview.cpp new file mode 100644 index 00000000..e6f0a14f --- /dev/null +++ b/kcachegrind/kcachegrind/coverageview.cpp @@ -0,0 +1,321 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Coverage Views + */ + +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <klocale.h> + +#include "configuration.h" +#include "coverageitem.h" +#include "coverage.h" +#include "coverageview.h" + + + +// +// CoverageView +// + + +CoverageView::CoverageView(bool showCallers, TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + _showCallers = showCallers; + + + addColumn( i18n( "Incl." ) ); + if (_showCallers) { + addColumn( i18n( "Distance" ) ); + addColumn( i18n( "Called" ) ); + addColumn( i18n( "Caller" ) ); + } + else { + addColumn( i18n( "Self" ) ); + addColumn( i18n( "Distance" ) ); + addColumn( i18n( "Calling" ) ); + addColumn( i18n( "Callee" ) ); + setColumnAlignment(3, Qt::AlignRight); + } + + setSorting(0,false); + setColumnAlignment(0, Qt::AlignRight); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + setAllColumnsShowFocus(true); + setResizeMode(QListView::LastColumn); + setMinimumHeight(50); + + connect( this, + SIGNAL( selectionChanged(QListViewItem*) ), + SLOT( selectedSlot(QListViewItem*) ) ); + + connect( this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + connect(this, + SIGNAL(doubleClicked(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(returnPressed(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + QWhatsThis::add( this, whatsThis() ); +} + +QString CoverageView::whatsThis() const +{ + return _showCallers ? + i18n( "<b>List of all Callers</b>" + "<p>This list shows all functions calling the " + "current selected one, either directly or with " + "several functions in-between on the stack; the " + "number of functions in-between plus one " + "is called the <em>Distance</em> (e.g. " + "for function A,B,C there exists a call from " + "A to C when A calls B and B calls C, i.e. " + "A => B => C. The distance here is 2).</p>" + + "<p>Absolute cost shown is the cost spent in the " + "selected function while a listed function is active; " + "relative cost is the percentage of all cost spent in " + "the selected function while the listed one is " + "active. The cost graphic shows logarithmic " + "percentage with a different color for each " + "distance.</p>" + + "<p>As there can be many calls from the same function, " + "the distance column sometimes shows " + "the range of distances for all " + "calls happening; then, in parentheses, there is the " + "medium distance, i.e. the distance where most of the " + "call costs happened.</p>" + + "<p>Selecting a function makes it the current selected " + "one of this information panel. " + "If there are two panels (Split mode), the " + "function of the other panel is changed instead.</p>") : + + i18n( "<b>List of all Callees</b>" + "<p>This list shows all functions called by the " + "current selected one, either directly or with " + "several function in-between on the stack; the " + "number of function in-between plus one " + "is called the <em>Distance</em> (e.g. " + "for function A,B,C there exists a call from " + "A to C when A calls B and B calls C, i.e. " + "A => B => C. The distance here is 2).</p>" + + "<p>Absolute cost shown is the cost spent in the " + "listed function while the selected is active; " + "relative cost is the percentage of all cost spent in " + "the listed function while the selected one is active. " + "The cost graphic always shows logarithmic " + "percentage with a different color for each " + "distance.</p>" + + "<p>As there can be many calls to the same function, " + "the distance column sometimes shows " + "the range of distances for all " + "calls happening; then, in parentheses, there is the " + "medium distance, i.e. the distance where most of the " + "call costs happened.</p>" + + "<p>Selecting a function makes it the current selected " + "one of this information panel. " + "If there are two panels (Split mode), the " + "function of the other panel is changed instead.</p>"); +} + +void CoverageView::context(QListViewItem* i, const QPoint & p, int c) +{ + QPopupMenu popup; + + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + if (f) { + QString name = f->name(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + popup.insertItem(i18n("Go to '%1'").arg(name), 93); + popup.insertSeparator(); + } + + if ((c == 0) || (!_showCallers && c == 1)) { + addCostMenu(&popup, false); + popup.insertSeparator(); + } + addGoMenu(&popup); + + int r = popup.exec(p); + if (r == 93) activated(f); +} + +void CoverageView::selectedSlot(QListViewItem * i) +{ + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + if (f) { + _selectedItem = f; + selected(f); + } +} + +void CoverageView::activatedSlot(QListViewItem * i) +{ + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + if (f) activated(f); +} + +TraceItem* CoverageView::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CoverageView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + TraceFunction* f = 0; + QListViewItem* i = QListView::selectedItem(); + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + if (f == _selectedItem) return; + + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) { + f = _showCallers ? + ((CallerCoverageItem*)item)->function() : + ((CalleeCoverageItem*)item)->function(); + if (f == _selectedItem) { + ensureItemVisible(item); + setCurrentItem(item); + break; + } + } + return; + } + + if (changeType == groupTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) { + if (_showCallers) + ((CallerCoverageItem*)item)->setGroupType(_groupType); + else + ((CalleeCoverageItem*)item)->setGroupType(_groupType); + } + return; + } + + refresh(); +} + +void CoverageView::refresh() +{ + clear(); + setColumnWidth(0, 50); + if (!_showCallers) + setColumnWidth(1, 50); + + if (!_data || !_activeItem) return; + + TraceItem::CostType t = _activeItem->type(); + TraceFunction* f = 0; + if (t == TraceItem::Function) f = (TraceFunction*) _activeItem; + if (t == TraceItem::FunctionCycle) f = (TraceFunction*) _activeItem; + if (!f) return; + + TraceFunction* ff; + TraceFunctionList l; + + _hc.clear(Configuration::maxListCount()); + SubCost realSum = f->inclusive()->subCost(_costType); + + if (_showCallers) + l = Coverage::coverage(f, Coverage::Caller, _costType); + else + l = Coverage::coverage(f, Coverage::Called, _costType); + + for (ff=l.first();ff;ff=l.next()) { + Coverage* c = (Coverage*) ff->assoziation(Coverage::Rtti); + if (c && (c->inclusive()>0.0)) + _hc.addCost(ff, SubCost(realSum * c->inclusive())); + } + + for(int i=0;i<_hc.realCount();i++) { + ff = (TraceFunction*) _hc[i]; + Coverage* c = (Coverage*) ff->assoziation(Coverage::Rtti); + if (_showCallers) + new CallerCoverageItem(this, c, f, _costType, _groupType); + else + new CalleeCoverageItem(this, c, f, _costType, _groupType); + } + if (_hc.hasMore()) { + // a placeholder for all the functions skipped ... + ff = (TraceFunction*) _hc[_hc.maxSize()-1]; + Coverage* c = (Coverage*) ff->assoziation(Coverage::Rtti); + if (_showCallers) + new CallerCoverageItem(this, _hc.count() - _hc.maxSize(), + c, f, _costType, _groupType); + else + new CalleeCoverageItem(this, _hc.count() - _hc.maxSize(), + c, f, _costType, _groupType); + } +} + +#include "coverageview.moc" diff --git a/kcachegrind/kcachegrind/coverageview.h b/kcachegrind/kcachegrind/coverageview.h new file mode 100644 index 00000000..ac70ec0e --- /dev/null +++ b/kcachegrind/kcachegrind/coverageview.h @@ -0,0 +1,56 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Coverage Views + */ + +#ifndef COVERAGEVIEW_H +#define COVERAGEVIEW_H + +#include <qlistview.h> +#include "tracedata.h" +#include "traceitemview.h" +#include "listutils.h" + +class CoverageView: public QListView, public TraceItemView +{ + Q_OBJECT + +public: + CoverageView(bool showCallers, TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + +private slots: + void context(QListViewItem*,const QPoint &, int); + void selectedSlot(QListViewItem*); + void activatedSlot(QListViewItem*); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); + + HighestCostList _hc; + bool _showCallers; +}; + +#endif diff --git a/kcachegrind/kcachegrind/dumpmanager.cpp b/kcachegrind/kcachegrind/dumpmanager.cpp new file mode 100644 index 00000000..6dda67e6 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpmanager.cpp @@ -0,0 +1,50 @@ +/** + * DumpManager + * Part of KCachegrind + * 2003, Josef Weidendorfer (GPL V2) + */ + +#include "dumpmanager.h" + + +// +// Dump +// + +Dump::Dump(QString file) +{ + _filename = file; +} + + +// +// DumpManager +// + +DumpManager* DumpManager::_self = 0; + + +DumpManager::DumpManager() +{ +} + +DumpManager* DumpManager::self() +{ + if (!_self) + _self = new DumpManager(); + + return _self; +} + + +DumpList DumpManager::loadableDumps() +{ + DumpList res; + + return res; +} + +TraceData* DumpManager::load(Dump*) +{ + return 0; +} diff --git a/kcachegrind/kcachegrind/dumpmanager.h b/kcachegrind/kcachegrind/dumpmanager.h new file mode 100644 index 00000000..3e2ff9e1 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpmanager.h @@ -0,0 +1,59 @@ +/** + * DumpManager + * Part of KCachegrind + * 2003, Josef Weidendorfer (GPL V2) + * + * DumpManager is a Singleton. + * - Has List of current loaded dumps / loadable dumps + * - Does "communication" with current running profiles + * for dump selection dockable + */ + +#ifndef DUMPMANAGER_H +#define DUMPMANAGER_H + +#include <qstring.h> +#include <qptrlist.h> + +class Dump; +class TraceData; + +typedef QPtrList<Dump> DumpList; + + +/** + * A loadable profile Dump + */ +class Dump +{ +public: + Dump(QString); + + QString filename() { return _filename; } + +private: + QString _filename; +}; + + +/* + * TODO: + * - Everything + * + */ + +class DumpManager +{ +public: + DumpManager(); + + DumpManager* self(); + + DumpList loadableDumps(); + TraceData* load(Dump*); + +private: + static DumpManager* _self; +}; + +#endif diff --git a/kcachegrind/kcachegrind/dumpselection.cpp b/kcachegrind/kcachegrind/dumpselection.cpp new file mode 100644 index 00000000..c16470a7 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselection.cpp @@ -0,0 +1,33 @@ +/** + * DumpSelection Dockable + * Part of KCachegrind + * 2003, Josef Weidendorfer (GPL V2) + * + * - Fast Selection of dumps to load/activate/use for comparing + * - Start a profile run from GUI (current supported: Callgrind) + * - View state of running profile runs. + * + */ + +#include "dumpselection.h" + +/* + * TODO: + * - Everything !! + * - Request State info on current function.. + * + */ + + +DumpSelection::DumpSelection( TopLevel* top, + QWidget* parent, const char* name) + : DumpSelectionBase(parent, name), TraceItemView(0, top) +{ +} + +DumpSelection::~DumpSelection() +{} + + +#include "dumpselection.moc" + diff --git a/kcachegrind/kcachegrind/dumpselection.h b/kcachegrind/kcachegrind/dumpselection.h new file mode 100644 index 00000000..8c59d3e4 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselection.h @@ -0,0 +1,29 @@ +/** + * DumpSelection Dockable + * Part of KCachegrind + * 2003, Josef Weidendorfer (GPL V2) + * + * - Fast Selection of dumps to load/activate/use for comparing + * - Start a profile run from GUI (current supported: Callgrind) + * - View state of running profile runs. + * + */ + +#ifndef DUMPSELECTION_H +#define DUMPSELECTION_H + +#include "dumpselectionbase.h" +#include "traceitemview.h" + +class DumpSelection : public DumpSelectionBase, public TraceItemView +{ + Q_OBJECT + +public: + DumpSelection( TopLevel*, QWidget* parent = 0, const char* name = 0); + virtual ~DumpSelection(); + + QWidget* widget() { return this; } +}; + +#endif diff --git a/kcachegrind/kcachegrind/dumpselectionbase.ui b/kcachegrind/kcachegrind/dumpselectionbase.ui new file mode 100644 index 00000000..37bc1c46 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselectionbase.ui @@ -0,0 +1,1082 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>DumpSelectionBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>DumpSelectionBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>349</width> + <height>832</height> + </rect> + </property> + <property name="caption"> + <string>Profile Dumps</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSplitter"> + <property name="name"> + <cstring>splitter1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <widget class="QListView"> + <column> + <property name="text"> + <string>Target</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string></string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Time</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Path</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>listView1</cstring> + </property> + </widget> + <widget class="QTabWidget"> + <property name="name"> + <cstring>tabWidget2</cstring> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Options</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Target command:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>lineEdit1</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Profiler options:</string> + </property> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>Option</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <item> + <property name="text"> + <string>Trace</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Jumps</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Instructions</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Events</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Full Cache</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Custom</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Collect</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>At Startup</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>While In</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Skip</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>PLT</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Function</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Dump Profile</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Every BBs</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>On Entering</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>On Leaving</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Zero Events</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>On Entering</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Separate</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Threads</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Recursions</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Call Chain</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <property name="name"> + <cstring>listView3</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Custom profiler options:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>lineEdit1_2</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton2</cstring> + </property> + <property name="text"> + <string>Run New Profile</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Info</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel8</cstring> + </property> + <property name="text"> + <string>Dump reason:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>lineEdit3</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Event summary:</string> + </property> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>Name</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Sum</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>listView4</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel7</cstring> + </property> + <property name="text"> + <string>Miscellaneous:</string> + </property> + </widget> + <widget class="QTextEdit"> + <property name="name"> + <cstring>textEdit2</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>50</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton6</cstring> + </property> + <property name="text"> + <string>Show</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton5</cstring> + </property> + <property name="text"> + <string>Compare</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>State</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton1</cstring> + </property> + <property name="text"> + <string>Update</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>checkBox1</cstring> + </property> + <property name="text"> + <string>Every [s]:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>lineEdit3_2</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>Counter</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <item> + <property name="text"> + <string>Dumps Done</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Is Collecting</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Executed</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Basic Blocks</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Calls</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Jumps</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Events</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>Ir</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Distinct</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <item> + <property name="text"> + <string>ELF Objects</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Functions</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <item> + <property name="text"> + <string>Contexts</string> + </property> + <property name="text"> + <string></string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + </item> + <property name="name"> + <cstring>listView4_3</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Stack trace:</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>checkBox2</cstring> + </property> + <property name="text"> + <string>Sync.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>#</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Incl.</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Called</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Function</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Location</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>listView7</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton7</cstring> + </property> + <property name="text"> + <string>Start</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton6_2</cstring> + </property> + <property name="text"> + <string>Zero</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton4</cstring> + </property> + <property name="text"> + <string>Dump</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Messages</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QTextEdit"> + <property name="name"> + <cstring>textEdit2_2</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton9</cstring> + </property> + <property name="text"> + <string>Kill Run</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer4</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>pushButton8</cstring> + </property> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </widget> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kcachegrind/kcachegrind/fixcost.cpp b/kcachegrind/kcachegrind/fixcost.cpp new file mode 100644 index 00000000..41029265 --- /dev/null +++ b/kcachegrind/kcachegrind/fixcost.cpp @@ -0,0 +1,174 @@ +/* + * Part of KCacheGrind + * + * 2003, Josef Weidendorfer + */ + +#include "fixcost.h" +#include "utils.h" + + +// FixCost + +FixCost::FixCost(TracePart* part, FixPool* pool, + TraceFunctionSource* functionSource, + PositionSpec& pos, + TracePartFunction* partFunction, + FixString& s) +{ + int maxCount = part->fixSubMapping()->count(); + + _part = part; + _functionSource = functionSource; + _pos = pos; + + _cost = (SubCost*) pool->reserve(sizeof(SubCost) * maxCount); + s.stripSpaces(); + int i = 0; + while(i<maxCount) { + if (!s.stripUInt64(_cost[i])) break; + i++; + } + _count = i; + + if (!pool->allocateReserved(sizeof(SubCost) * _count)) + _count = 0; + + _nextCostOfPartFunction = partFunction ? + partFunction->setFirstFixCost(this) : 0; +} + +void* FixCost::operator new(size_t size, FixPool* pool) +{ + return pool->allocate(size); +} + +void FixCost::addTo(TraceCost* c) +{ + TraceSubMapping* sm = _part->fixSubMapping(); + + int i, realIndex; + + for(i=0; i<_count; i++) { + realIndex = sm->realIndex(i); + c->addCost(realIndex, _cost[i]); + } +} + + + +// FixCallCost + +FixCallCost::FixCallCost(TracePart* part, FixPool* pool, + TraceFunctionSource* functionSource, + unsigned int line, Addr addr, + TracePartCall* partCall, + SubCost callCount, FixString& s) +{ + if (0) qDebug("Got FixCallCost (addr 0x%s, line %d): calls %s", + addr.toString().ascii(), line, + callCount.pretty().ascii()); + + int maxCount = part->fixSubMapping()->count(); + + _part = part; + _functionSource = functionSource; + _line = line; + _addr = addr; + + _cost = (SubCost*) pool->reserve(sizeof(SubCost) * (maxCount+1)); + s.stripSpaces(); + int i = 0; + while(i<maxCount) { + if (!s.stripUInt64(_cost[i])) break; + i++; + } + _count = i; + + if (!pool->allocateReserved(sizeof(SubCost) * (_count+1) )) + _count = 0; + else + _cost[_count] = callCount; + + _nextCostOfPartCall = partCall ? partCall->setFirstFixCallCost(this) : 0; +} + +void* FixCallCost::operator new(size_t size, FixPool* pool) +{ + return pool->allocate(size); +} + +void FixCallCost::addTo(TraceCallCost* c) +{ + TraceSubMapping* sm = _part->fixSubMapping(); + + int i, realIndex; + + for(i=0; i<_count; i++) { + realIndex = sm->realIndex(i); + c->addCost(realIndex, _cost[i]); + } + c->addCallCount(_cost[_count]); + + if (0) qDebug("Adding from (addr 0x%s, ln %d): calls %s", + _addr.toString().ascii(), _line, + _cost[_count].pretty().ascii()); +} + +void FixCallCost::setMax(TraceCost* c) +{ + TraceSubMapping* sm = _part->fixSubMapping(); + + int i, realIndex; + + for(i=0; i<_count; i++) { + realIndex = sm->realIndex(i); + c->maxCost(realIndex, _cost[i]); + } +} + + +// FixJump + +FixJump::FixJump(TracePart* part, FixPool* pool, + unsigned int line, Addr addr, + TracePartFunction* partFunction, + TraceFunctionSource* source, + unsigned int targetLine, Addr targetAddr, + TraceFunction* targetFunction, + TraceFunctionSource* targetSource, + bool isCondJump, + SubCost executed, SubCost followed) +{ + _part = part; + _source = source; + _line = line; + _addr = addr; + + _targetFunction = targetFunction; + _targetSource = targetSource; + _targetLine = targetLine; + _targetAddr = targetAddr; + + _isCondJump = isCondJump; + + int size = (isCondJump ? 2 : 1) * sizeof(SubCost); + _cost = (SubCost*) pool->allocate(size); + _cost[0] = executed; + if (isCondJump) _cost[1] = followed; + + _nextJumpOfPartFunction = partFunction ? + partFunction->setFirstFixJump(this) : 0; +} + +void* FixJump::operator new(size_t size, FixPool* pool) +{ + return pool->allocate(size); +} + +void FixJump::addTo(TraceJumpCost* jc) +{ + jc->addExecutedCount(_cost[0]); + if (_isCondJump) + jc->addFollowedCount(_cost[1]); +} diff --git a/kcachegrind/kcachegrind/fixcost.h b/kcachegrind/kcachegrind/fixcost.h new file mode 100644 index 00000000..7e90fb40 --- /dev/null +++ b/kcachegrind/kcachegrind/fixcost.h @@ -0,0 +1,171 @@ +/* + * Part of KCacheGrind + * + * 2003, Josef Weidendorfer + */ + +#ifndef FIXCOST_H +#define FIXCOST_H + +/** + * Setting USE_FIXCOST to 1 enables a memory space hack: + * For some data, build up internal data model lazy by using + * the Fix*Cost classes, which are simple copies from input data. + */ +#define USE_FIXCOST 1 + +#include "tracedata.h" +#include "pool.h" + +class PositionSpec +{ + public: + PositionSpec() + { fromLine = 0, toLine = 0, fromAddr = 0, toAddr = 0; } + PositionSpec(uint l1, uint l2, Addr a1, Addr a2) + { fromLine = l1, toLine = l2, fromAddr = a1, toAddr = a2; } + + bool isLineRegion() const { return (fromLine != toLine); } + bool isAddrRegion() const { return (fromAddr != toAddr); } + + uint fromLine, toLine; + Addr fromAddr, toAddr; +}; + +/** + * A class holding an unchangable cost item of an input file. + * + * As there can be a lot of such cost items, we use our own + * allocator which uses FixPool + */ +class FixCost +{ + + public: + FixCost(TracePart*, FixPool*, + TraceFunctionSource*, + PositionSpec&, + TracePartFunction*, + FixString&); + + void *operator new(size_t size, FixPool*); + + void addTo(TraceCost*); + + TracePart* part() const { return _part; } + bool isLineRegion() const { return _pos.isLineRegion(); } + bool isAddrRegion() const { return _pos.isAddrRegion(); } + uint fromLine() const { return _pos.fromLine; } + uint line() const { return _pos.fromLine; } + uint toLine() const { return _pos.toLine; } + Addr fromAddr() const { return _pos.fromAddr; } + Addr addr() const { return _pos.fromAddr; } + Addr toAddr() const { return _pos.toAddr; } + TraceFunctionSource* functionSource() const { return _functionSource; } + + FixCost* nextCostOfPartFunction() const + { return _nextCostOfPartFunction; } + + private: + int _count; + SubCost* _cost; + PositionSpec _pos; + + TracePart* _part; + TraceFunctionSource* _functionSource; + FixCost *_nextCostOfPartFunction; +}; + +/** + * A FixCallCost will be inserted into a + * - TracePartCall to keep source/target function info + * - TraceFunctionSourceFile to keep file info of call source + */ +class FixCallCost +{ + + public: + FixCallCost(TracePart*, FixPool*, + TraceFunctionSource*, + unsigned int line, + Addr addr, + TracePartCall*, + SubCost, FixString&); + + void *operator new(size_t size, FixPool*); + + void addTo(TraceCallCost*); + void setMax(TraceCost*); + + TracePart* part() const { return _part; } + unsigned int line() const { return _line; } + Addr addr() const { return _addr; } + SubCost callCount() const { return _cost[_count]; } + TraceFunctionSource* functionSource() const { return _functionSource; } + FixCallCost* nextCostOfPartCall() const + { return _nextCostOfPartCall; } + + private: + // we use 1 SubCost more than _count: _cost[_count] is the call count + int _count; + SubCost* _cost; + unsigned int _line; + Addr _addr; + + TracePart* _part; + TraceFunctionSource* _functionSource; + FixCallCost* _nextCostOfPartCall; +}; + +/** + * A class holding a jump (mostly) inside of a function + */ +class FixJump +{ + + public: + FixJump(TracePart*, FixPool*, + /* source position */ + unsigned int line, Addr addr, + TracePartFunction*, TraceFunctionSource*, + /* target position */ + unsigned int targetLine, Addr targetAddr, + TraceFunction*, TraceFunctionSource*, + bool isCondJump, + SubCost, SubCost); + + void *operator new(size_t size, FixPool*); + + void addTo(TraceJumpCost*); + + TracePart* part() const { return _part; } + unsigned int line() const { return _line; } + Addr addr() const { return _addr; } + TraceFunctionSource* source() const { return _source; } + TraceFunction* targetFunction() const { return _targetFunction; } + unsigned int targetLine() const { return _targetLine; } + Addr targetAddr() const { return _targetAddr; } + TraceFunctionSource* targetSource() const { return _targetSource; } + bool isCondJump() { return _isCondJump; } + SubCost executedCount() const { return _cost[0]; } + SubCost followedCount() const + { return _isCondJump ? _cost[1] : SubCost(0); } + + FixJump* nextJumpOfPartFunction() const + { return _nextJumpOfPartFunction; } + + private: + bool _isCondJump; + SubCost* _cost; + unsigned int _line, _targetLine; + Addr _addr, _targetAddr; + + TracePart* _part; + TraceFunctionSource *_source, *_targetSource; + TraceFunction* _targetFunction; + FixJump *_nextJumpOfPartFunction; +}; + +#endif + + diff --git a/kcachegrind/kcachegrind/functionitem.cpp b/kcachegrind/kcachegrind/functionitem.cpp new file mode 100644 index 00000000..9d62b2eb --- /dev/null +++ b/kcachegrind/kcachegrind/functionitem.cpp @@ -0,0 +1,236 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * List Item for the FunctionSelection list + */ + + +//#include <math.h> + +//#include <qpainter.h> +//#include <qregexp.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kapplication.h> + +#include "listutils.h" +#include "functionitem.h" +#include "configuration.h" + + +// FunctionItem + +FunctionItem::FunctionItem(QListView* parent, TraceFunction* f, + TraceCostType* ct, TraceCost::CostType gt) + :QListViewItem(parent) +{ +#if 0 + _costPixValid = false; + _groupPixValid = false; +#endif + + _function = f; + _skipped = 0; + _groupType = TraceCost::NoCostType; + setGroupType(gt); + setCostType(ct); + + setText(3, f->prettyName()); + setText(4, f->prettyLocation()); +} + +FunctionItem::FunctionItem(QListView* parent, int skipped, + TraceFunction* f, TraceCostType* ct) + :QListViewItem(parent) +{ +#if 0 + _costPixValid = false; + _groupPixValid = false; +#endif + _skipped = skipped; + _function = f; + _groupType = TraceCost::NoCostType; + setCostType(ct); + + setText(3, i18n("(%n function skipped)", "(%n functions skipped)", skipped)); +} + +#if 0 +const QPixmap* FunctionItem::pixmap(int column) const +{ + if (column == 3) { + if (!_groupPixValid) { + QColor c = Configuration::functionColor(_groupType, _function); + _groupPix = colorPixmap(10, 10, c); + _groupPixValid = true; + } + return &_groupPix; + } + if (column == 1) { + if (!_costPixValid) { + _costPix = colorPixmap(10, 10, c); + _costPixValid = true; + } + return &_costPix; + } + return 0; +} +#endif + +void FunctionItem::setGroupType(TraceCost::CostType gt) +{ + if (_skipped) return; + if (_groupType == gt) return; + _groupType = gt; + + +#if 0 + _groupPixValid = false; + viewList()->repaint(); +#else + QColor c = Configuration::functionColor(_groupType, _function); + setPixmap(3, colorPixmap(10, 10, c)); +#endif +} + +void FunctionItem::setCostType(TraceCostType* c) +{ + _costType = c; + update(); +} + +void FunctionItem::update() +{ + double inclTotal = _function->data()->subCost(_costType); + QString str; + + TraceCost* selfCost = _function->data(); + if (Configuration::showExpanded()) { + switch(_groupType) { + case TraceCost::Object: selfCost = _function->object(); break; + case TraceCost::Class: selfCost = _function->cls(); break; + case TraceCost::File: selfCost = _function->file(); break; + default: break; + } + } + double selfTotal = selfCost->subCost(_costType); + + if (_skipped) { + // special handling for skip entries... + + // only text updates of incl./self + + // for all skipped functions, cost is below the given function + _sum = _function->inclusive()->subCost(_costType); + double incl = 100.0 * _sum / inclTotal; + if (Configuration::showPercentage()) + str = QString("%1").arg(incl, 0, 'f', Configuration::percentPrecision()); + else + str = _function->inclusive()->prettySubCost(_costType); + str = "< " + str; + setText(0, str); + setText(1, str); + return; + } + + // Call count... + if (_function->calledCount() >0) + str = _function->prettyCalledCount(); + else { + if (_function == _function->cycle()) + str = QString("-"); + else + str = QString("(0)"); + } + setText(2, str); + + // Incl. cost + _sum = _function->inclusive()->subCost(_costType); + if (inclTotal == 0.0) { + setPixmap(0, QPixmap()); + setText(0, "-"); + } + else { + double incl = 100.0 * _sum / inclTotal; + if (Configuration::showPercentage()) + setText(0, QString("%1") + .arg(incl, 0, 'f', Configuration::percentPrecision())); + else + setText(0, _function->inclusive()->prettySubCost(_costType)); + + setPixmap(0, costPixmap(_costType, _function->inclusive(), inclTotal, false)); + } + + // self + _pure = _function->subCost(_costType); + if (selfTotal == 0.0) { + setPixmap(1, QPixmap()); + setText(1, "-"); + } + else { + double self = 100.0 * _pure / selfTotal; + + if (Configuration::showPercentage()) + setText(1, QString("%1") + .arg(self, 0, 'f', Configuration::percentPrecision())); + else + setText(1, _function->prettySubCost(_costType)); + + setPixmap(1, costPixmap(_costType, _function, selfTotal, false)); + } +} + + +int FunctionItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + const FunctionItem* fi1 = this; + const FunctionItem* fi2 = (FunctionItem*) i; + + // we always want descending order + if (ascending) { + fi1 = fi2; + fi2 = this; + } + + // a skip entry is always sorted last + if (fi1->_skipped) return -1; + if (fi2->_skipped) return 1; + + if (col==0) { + if (fi1->_sum < fi2->_sum) return -1; + if (fi1->_sum > fi2->_sum) return 1; + return 0; + } + if (col==1) { + if (fi1->_pure < fi2->_pure) return -1; + if (fi1->_pure > fi2->_pure) return 1; + return 0; + } + if (col==2) { + if (fi1->_function->calledCount() < + fi2->_function->calledCount()) return -1; + if (fi1->_function->calledCount() > + fi2->_function->calledCount()) return 1; + return 0; + } + + return QListViewItem::compare(i, col, ascending); +} + diff --git a/kcachegrind/kcachegrind/functionitem.h b/kcachegrind/kcachegrind/functionitem.h new file mode 100644 index 00000000..94545dcb --- /dev/null +++ b/kcachegrind/kcachegrind/functionitem.h @@ -0,0 +1,58 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * List Item for the FunctionSelection list + */ + +#ifndef FUNCTIONITEM_H +#define FUNCTIONITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class FunctionItem: public QListViewItem +{ +public: + FunctionItem(QListView* parent, TraceFunction* function, + TraceCostType* ct, TraceCost::CostType gt); + // constructor for a "Skipped ... " entry + FunctionItem(QListView* parent, int skipped, + TraceFunction* function, TraceCostType* ct); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceFunction* function() { return (_skipped) ? 0 : _function; } + void setCostType(TraceCostType* ct); + void setGroupType(TraceCost::CostType); + void update(); + +#if 0 + const QPixmap* pixmap (int column) const; + bool _costPixValid, _groupPixValid; + QPixMap _costPix, _groupPix; +#endif + +private: + SubCost _sum, _pure; + TraceCostType* _costType; + TraceCost::CostType _groupType; + TraceFunction* _function; + int _skipped; +}; + +#endif diff --git a/kcachegrind/kcachegrind/functionselection.cpp b/kcachegrind/kcachegrind/functionselection.cpp new file mode 100644 index 00000000..8c4e8a5a --- /dev/null +++ b/kcachegrind/kcachegrind/functionselection.cpp @@ -0,0 +1,871 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * For function selection, to be put into a QDockWindow + */ + +#include <qtimer.h> +#include <qlistview.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcombobox.h> +#include <qlineedit.h> +#include <qregexp.h> +#include <qpopupmenu.h> + +#include <klocale.h> + +#include "traceitemview.h" +#include "stackbrowser.h" +#include "functionselection.h" +#include "partgraph.h" +#include "functionitem.h" +#include "costlistitem.h" +#include "configuration.h" +#include "toplevel.h" + +FunctionSelection::FunctionSelection( TopLevel* top, + QWidget* parent, const char* name) + : FunctionSelectionBase(parent, name), TraceItemView(0, top) +{ + _group = 0; + _inSetGroup = false; + _inSetFunction = false; + + QStringList args; + args << i18n("(No Grouping)") + << TraceCost::i18nTypeName(TraceItem::Object) + << TraceCost::i18nTypeName(TraceItem::File) + << TraceCost::i18nTypeName(TraceItem::Class) + << TraceCost::i18nTypeName(TraceItem::FunctionCycle); + + groupBox->insertStringList(args); + // this needs same order of grouptype actionlist! + connect(groupBox, SIGNAL(activated(int)), + top, SLOT(groupTypeSelected(int))); + + // search while typing... + connect(searchEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(searchChanged(const QString&))); + connect(&_searchTimer, SIGNAL(timeout()), + this, SLOT(queryDelayed())); + // select first matching group/function on return + connect(searchEdit, SIGNAL(returnPressed()), + this, SLOT(searchReturnPressed())); + searchEdit->setMinimumWidth(50); + + // we start with desending cost sorting + functionList->setSorting(0,false); + functionList->setColumnAlignment(0, Qt::AlignRight); + functionList->setColumnAlignment(1, Qt::AlignRight); + functionList->setColumnAlignment(2, Qt::AlignRight); + functionList->setAllColumnsShowFocus(true); + // functionList->setShowSortIndicator(true); + // we can have very long function and location names + functionList->setColumnWidthMode(3, QListView::Manual); + functionList->setColumnWidth(3, 200); + functionList->setColumnWidthMode(4, QListView::Manual); + functionList->setColumnWidth(4, 200); + + groupList->setSorting(0,false); + groupList->setColumnAlignment(0, Qt::AlignRight); + groupList->setAllColumnsShowFocus(true); + // groupList->setShowSortIndicator(true); + groupList->setResizeMode(QListView::LastColumn); + +#if 0 + // single click press activation + connect(functionList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(functionActivated(QListViewItem*))); + connect(functionList, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + this, SLOT(functionContext(QListViewItem*, const QPoint &, int))); +#else + // single click release activation + connect(functionList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(functionSelected(QListViewItem*))); + connect(functionList, SIGNAL(clicked(QListViewItem*)), + this, SLOT(functionActivated(QListViewItem*))); + connect(functionList, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(functionActivated(QListViewItem*))); + connect(functionList, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + this, SLOT(functionContext(QListViewItem*, const QPoint &, int))); +#endif + + connect(groupList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(groupSelected(QListViewItem*))); + connect(groupList, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(groupDoubleClicked(QListViewItem*))); + connect(groupList, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(groupDoubleClicked(QListViewItem*))); + connect(groupList, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + this, SLOT(groupContext(QListViewItem*, const QPoint &, int))); + + // start hidden + groupList->hide(); +} + +FunctionSelection::~FunctionSelection() +{ +} + +void FunctionSelection::searchReturnPressed() +{ + query(searchEdit->text()); + + QListViewItem* item; + if (_groupType != TraceItem::Function) { + // if current group not matching, select first matching group + item = groupList->currentItem(); + if (!item || !item->isVisible()) { + item = groupList->firstChild(); + for (;item;item = item->nextSibling()) + if (item->isVisible()) break; + if (!item) return; + + setGroup(((CostListItem*)item)->costItem()); + return; + } + } + + functionActivated(functionList->firstChild()); +} + +// trigger the query after some delay, dependent on length +void FunctionSelection::searchChanged(const QString& q) +{ + _searchDelayed = q; + int ms = 100; + if (q.length()<5) ms = 200; + if (q.length()<2) ms = 300; + _searchTimer.start(ms,true); +} + +void FunctionSelection::queryDelayed() +{ + query(_searchDelayed); +} + +void FunctionSelection::functionContext(QListViewItem* i, + const QPoint & p, int c) +{ + QPopupMenu popup; + TraceFunction* f = 0; + + if (i) { + f = ((FunctionItem*) i)->function(); + if (f) { + popup.insertItem(i18n("Go to %1").arg(f->prettyName()), 93); + popup.insertSeparator(); + } + } + + if ((c == 0) || (c == 1)) { + addCostMenu(&popup,false); + popup.insertSeparator(); + } + addGroupMenu(&popup); + popup.insertSeparator(); + addGoMenu(&popup); + + int r = popup.exec(p); + if (r == 93) activated(f); +} + +void FunctionSelection::groupContext(QListViewItem* /*i*/, + const QPoint & p, int c) +{ + QPopupMenu popup; + +#if 0 + TraceCostItem* g = 0; + if (i) { + g = ((CostListItem*) i)->costItem(); + if (!g) { + popup.insertItem(i18n("Show All Items"), 93); + popup.insertSeparator(); + } + } +#endif + if (c == 0) { + addCostMenu(&popup,false); + popup.insertSeparator(); + } + addGroupMenu(&popup); + popup.insertSeparator(); + addGoMenu(&popup); + + popup.exec(p); +} + + +void FunctionSelection::addGroupMenu(QPopupMenu* popup) +{ + QPopupMenu *popup1 = new QPopupMenu(popup); + popup1->setCheckable(true); + + if (_groupType != TraceItem::Function) { + popup1->insertItem(i18n("No Grouping"),0); + popup1->insertSeparator(); + } + popup1->insertItem(TraceCost::i18nTypeName(TraceItem::Object),1); + popup1->insertItem(TraceCost::i18nTypeName(TraceItem::File),2); + popup1->insertItem(TraceCost::i18nTypeName(TraceItem::Class),3); + popup1->insertItem(TraceCost::i18nTypeName(TraceItem::FunctionCycle),4); + switch(_groupType) { + case TraceItem::Object: popup1->setItemChecked(1, true); break; + case TraceItem::File: popup1->setItemChecked(2, true); break; + case TraceItem::Class: popup1->setItemChecked(3, true); break; + case TraceItem::FunctionCycle: popup1->setItemChecked(4, true); break; + default: break; + } + connect(popup1,SIGNAL(activated(int)), + _topLevel,SLOT(groupTypeSelected(int))); + + popup->insertItem(i18n("Grouping"), popup1); +} + + +TraceItem* FunctionSelection::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Object: + case TraceItem::File: + case TraceItem::Class: + break; + + case TraceItem::Instr: + i = ((TraceInstr*)i)->function(); + break; + + case TraceItem::Line: + i = ((TraceLine*)i)->functionSource()->function(); + break; + + default: + i = 0; + break; + } + return i; +} + + +void FunctionSelection::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) return; + + // we don't show cost 2 at all... + if (changeType == costType2Changed) return; + + if (changeType == activeItemChanged) { + if (_activeItem ==0) { + functionList->clearSelection(); + return; + } + switch(_activeItem->type()) { + case TraceItem::Object: + case TraceItem::File: + case TraceItem::Class: + setGroup((TraceCostItem*)_activeItem); + return; + default: break; + } + + // active item is a function + TraceFunction* f = (TraceFunction*) _activeItem; + + // if already current, nothing to do + QListViewItem* i = functionList->currentItem(); + if (i && (((FunctionItem*)i)->function() == f)) { + functionList->setSelected(i,true); + return; + } + + // reset searchEdit (as not activated from this view) + _searchString = QString::null; + query(QString::null); + + // select cost item group of function + switch(_groupType) { + case TraceItem::Object: setGroup(f->object()); break; + case TraceItem::Class: setGroup(f->cls()); break; + case TraceItem::File: setGroup(f->file()); break; + case TraceItem::FunctionCycle: setGroup(f->cycle()); break; + default: + break; + } + + QListViewItem* item = functionList->firstChild(); + for (;item;item = item->nextSibling()) + if (((FunctionItem*)item)->function() == f) + break; + + if (!item) + item = new FunctionItem(functionList, f, _costType, _groupType); + + functionList->ensureItemVisible(item); + // prohibit signalling of a function selection + _inSetFunction = true; + functionList->setSelected(item, true); + _inSetFunction = false; + + return; + } + + if (changeType & groupTypeChanged) { + if (_activeItem && (_activeItem->type() == TraceItem::Function)) { + TraceFunction* f = (TraceFunction*) _activeItem; + + // select cost item group of function + switch(_groupType) { + case TraceItem::Object: _group = f->object(); break; + case TraceItem::Class: _group = f->cls(); break; + case TraceItem::File: _group = f->file(); break; + case TraceItem::FunctionCycle: _group = f->cycle(); break; + default: + _group = 0; + break; + } + } + + int id; + switch(_groupType) { + case TraceItem::Object: id = 1; break; + case TraceItem::File: id = 2; break; + case TraceItem::Class: id = 3; break; + case TraceItem::FunctionCycle: id = 4; break; + default: id = 0; break; + } + groupBox->setCurrentItem(id); + + if (_groupType == TraceItem::Function) + groupList->hide(); + else + groupList->show(); + } + + // reset searchEdit + _searchString = QString::null; + query(QString::null); + + refresh(); +} + + +/* + * This set/selects a group of the set available within the + * current group type + */ +void FunctionSelection::setGroup(TraceCostItem* g) +{ + if (!g) return; + if (g->type() != _groupType) return; + if (g == _group) return; + _group = g; + + QListViewItem* item = groupList->firstChild(); + for (;item;item = item->nextSibling()) + if (((CostListItem*)item)->costItem() == g) + break; + + if (item) { + groupList->ensureItemVisible(item); + // prohibit signalling of a group selection + _inSetGroup = true; + groupList->setSelected(item, true); + _inSetGroup = false; + } + else + groupList->clearSelection(); +} + + +void FunctionSelection::refresh() +{ + groupList->setUpdatesEnabled(false); + groupList->clear(); + + // make cost columns as small as possible: + // the new functions make them as wide as needed + groupList->setColumnWidth(0, 50); + + groupList->setColumnText(1, TraceItem::i18nTypeName(_groupType)); + + if (!_data || _data->parts().count()==0) { + functionList->clear(); + groupList->setUpdatesEnabled(true); + groupList->repaint(); + + // this clears all other lists + functionList->setSelected(functionList->firstChild(), true); + return; + } + + /* + qDebug("FunctionSelection::fillLists (%s)", + _data->command().ascii()); + */ + + TraceObjectMap::Iterator oit; + TraceClassMap::Iterator cit; + TraceFileMap::Iterator fit; + QListViewItem *i = 0, *item = 0, *fitem = 0; + + // Fill up group list. + // Always show group of current function, even if cost below low limit. + // + + _hc.clear(Configuration::maxListCount()); + + TraceCostItem *group; + + // update group from _activeItem if possible + if (_activeItem && (_activeItem->type() == _groupType)) + _group = (TraceCostItem*) _activeItem; + + switch(_groupType) { + case TraceItem::Object: + + for ( oit = _data->objectMap().begin(); + oit != _data->objectMap().end(); ++oit ) + _hc.addCost(&(*oit), (*oit).subCost(_costType)); + break; + + case TraceItem::Class: + + for ( cit = _data->classMap().begin(); + cit != _data->classMap().end(); ++cit ) + _hc.addCost(&(*cit), (*cit).subCost(_costType)); + break; + + case TraceItem::File: + + for ( fit = _data->fileMap().begin(); + fit != _data->fileMap().end(); ++fit ) + _hc.addCost(&(*fit), (*fit).subCost(_costType)); + break; + + case TraceItem::FunctionCycle: + { + // add all cycles + TraceFunctionCycleList l = _data->functionCycles(); + for (group=l.first();group;group=l.next()) + _hc.addCost(group, group->subCost(_costType)); + } + + break; + + default: + { + QListViewItem* oldItem = functionList->selectedItem(); + TraceFunction* oldFunction = 0; + int oldPos = 0; + if (oldItem) { + oldFunction = ((FunctionItem*)oldItem)->function(); + oldPos = oldItem->itemPos(); + oldPos -= functionList->contentsY(); + if (oldPos < 0 || oldPos > functionList->height()) + oldFunction = 0; + } + + // switching off QListView updates is buggy with some QT versions... + //functionList->setUpdatesEnabled(false); + functionList->clear(); + setCostColumnWidths(); + + if (0) qDebug("Function %s at %d, Item %p", + oldFunction ? oldFunction->name().ascii() : "-", + oldPos, (void*)oldItem); + + TraceFunctionMap::Iterator it; + TraceFunction *f; + i = 0; + fitem = 0; + for ( it = _data->functionMap().begin(); + it != _data->functionMap().end(); ++it ) + _hc.addCost(&(*it), (*it).inclusive()->subCost(_costType)); + + TraceFunctionCycleList l = _data->functionCycles(); + for (f=l.first();f;f=l.next()) + _hc.addCost(f, f->inclusive()->subCost(_costType)); + + if (_activeItem && + ((_activeItem->type() == TraceItem::Function) || + (_activeItem->type() == TraceItem::FunctionCycle))) + fitem = new FunctionItem(functionList, (TraceFunction*)_activeItem, + _costType, _groupType); + + for(int i=0;i<_hc.realCount();i++) { + f = (TraceFunction*)_hc[i]; + if (f == _activeItem) continue; + new FunctionItem(functionList, f, _costType, _groupType); + } + if (_hc.hasMore()) { + // a placeholder for all the cost items skipped ... + new FunctionItem(functionList, _hc.count() - _hc.maxSize(), + (TraceFunction*)_hc[_hc.maxSize()-1], _costType); + } + functionList->sort(); + + if (fitem && oldFunction) { + _inSetFunction = true; + functionList->setSelected(fitem, true); + _inSetFunction = false; + int newPos = functionList->itemPos(fitem) - functionList->contentsY(); + functionList->scrollBy(0, newPos-oldPos); + } + else if (fitem) { + functionList->ensureItemVisible(fitem); + _inSetFunction = true; + functionList->setSelected(fitem, true); + _inSetFunction = false; + } + else + functionList->clearSelection(); + + //functionList->setUpdatesEnabled(true); + //functionList->repaint(); + groupList->setUpdatesEnabled(true); + groupList->repaint(); + return; + } + } + + // we always put group of active item in list, even if + // it would be skipped because of small costs + if (_group) + item = new CostListItem(groupList, _group, _costType); + + for(int i=0;i<_hc.realCount();i++) { + group = (TraceCostItem*)_hc[i]; + // don't put group of active item twice into list + if (group == _group) continue; + new CostListItem(groupList, group, _costType); + } + if (_hc.hasMore()) { + // a placeholder for all the cost items skipped ... + new CostListItem(groupList, _hc.count() - _hc.maxSize(), + (TraceCostItem*)_hc[_hc.maxSize()-1], _costType); + } + groupList->sort(); + if (item) { + groupList->ensureItemVisible(item); + _inSetGroup = true; + groupList->setSelected(item, true); + _inSetGroup = false; + } + else + groupList->clearSelection(); + + groupList->setUpdatesEnabled(true); + groupList->repaint(); +} + + +void FunctionSelection::groupSelected(QListViewItem* i) +{ + if (!i) return; + if (!_data) return; + + TraceCostItem* g = ((CostListItem*) i)->costItem(); + if (!g) return; + + _group = g; + + TraceFunctionList list; + + switch(g->type()) { + case TraceItem::Object: + list = ((TraceObject*)g)->functions(); + break; + case TraceItem::Class: + list = ((TraceClass*)g)->functions(); + break; + case TraceItem::File: + list = ((TraceFile*)g)->functions(); + break; + case TraceItem::FunctionCycle: + list = ((TraceFunctionCycle*)g)->members(); + break; + default: + return; + } + + // switching off QListView updates is buggy with some QT versions... + //functionList->setUpdatesEnabled(false); + + functionList->clear(); + setCostColumnWidths(); + + double total; + if (Configuration::showExpanded()) + total = (double) g->subCost(_costType); + else + total = (double) _data->subCost(_costType); +#if 0 + if (total == 0.0) { + functionList->setUpdatesEnabled(true); + functionList->repaint(); + return; + } +#endif + + QRegExp re(_searchString, false, true); + + FunctionItem* fitem = 0; + TraceFunction *f; + _hc.clear(Configuration::maxListCount()); + for (f=list.first();f;f=list.next()) { + if (re.search(f->prettyName())<0) continue; + + _hc.addCost(f, f->inclusive()->subCost(_costType)); + if (_activeItem == f) + fitem = new FunctionItem(functionList, (TraceFunction*)_activeItem, + _costType, _groupType); + } + + for(int i=0;i<_hc.realCount();i++) { + if (_activeItem == (TraceFunction*)_hc[i]) continue; + new FunctionItem(functionList, (TraceFunction*)_hc[i], + _costType, _groupType); + } + + if (_hc.hasMore()) { + // a placeholder for all the functions skipped ... + new FunctionItem(functionList, _hc.count() - _hc.maxSize(), + (TraceFunction*)_hc[_hc.maxSize()-1], _costType); + } + functionList->sort(); + + if (fitem) { + functionList->ensureItemVisible(fitem); + _inSetFunction = true; + functionList->setSelected(fitem, true); + _inSetFunction = false; + } + + //functionList->setUpdatesEnabled(true); + //functionList->repaint(); + + // Don't emit signal if cost item was changed programatically + if (!_inSetGroup) { + _selectedItem = g; + selected(g); + } +} + +void FunctionSelection::groupDoubleClicked(QListViewItem* i) +{ + if (!i) return; + if (!_data) return; + TraceCostItem* g = ((CostListItem*) i)->costItem(); + + if (!g) return; + // group must be selected first + if (g != _group) return; + + activated(g); +} + + +TraceCostItem* FunctionSelection::group(QString s) +{ + QListViewItem *item; + item = groupList->firstChild(); + for(;item;item = item->nextSibling()) + if (((CostListItem*)item)->costItem()->name() == s) + return ((CostListItem*)item)->costItem(); + + return 0; +} + + + +void FunctionSelection::functionSelected(QListViewItem* i) +{ + if (!i) return; + if (!_data) return; + + TraceFunction* f = ((FunctionItem*) i)->function(); + if (!f) return; + + //qDebug("FunctionSelection::functionSelected %s", f->name().ascii()); + + // Don't emit signal if function was changed programatically + if (!_inSetFunction) { + _selectedItem = f; + selected(f); + } +} + +void FunctionSelection::functionActivated(QListViewItem* i) +{ + if (!i) return; + if (!_data) return; + TraceFunction* f = ((FunctionItem*) i)->function(); + + if (!f) return; + + if (!_inSetFunction) + activated(f); +} + +void FunctionSelection::updateGroupSizes(bool hideEmpty) +{ + QListViewItem* item = groupList->firstChild(); + for (;item;item = item->nextSibling()) { + CostListItem* i = (CostListItem*)item; + int size = (_groupSize.contains(i->costItem())) ? + _groupSize[i->costItem()] : -1; + i->setSize(size); + i->setVisible(!hideEmpty || (size>0)); + } +} + +void FunctionSelection::query(QString query) +{ + if (searchEdit->text() != query) + searchEdit->setText(query); + if (_searchString == query) { + // when resetting query, get rid of group sizes + if (query.isEmpty()) { + _groupSize.clear(); + updateGroupSizes(false); + } + return; + } + _searchString = query; + + QRegExp re(query, false, true); + _groupSize.clear(); + + TraceFunction* f = 0; + TraceFunctionList list2; + + _hc.clear(Configuration::maxListCount()); + + TraceFunctionMap::Iterator it; + for ( it = _data->functionMap().begin(); + it != _data->functionMap().end(); ++it ) { + f = &(*it); + if (re.search(f->prettyName())>=0) { + if (_group) { + if (_groupType==TraceItem::Object) { + if (_groupSize.contains(f->object())) + _groupSize[f->object()]++; + else + _groupSize[f->object()] = 1; + if (f->object() != _group) continue; + } + else if (_groupType==TraceItem::Class) { + if (_groupSize.contains(f->cls())) + _groupSize[f->cls()]++; + else + _groupSize[f->cls()] = 1; + if (f->cls() != _group) continue; + } + else if (_groupType==TraceItem::File) { + if (_groupSize.contains(f->file())) + _groupSize[f->file()]++; + else + _groupSize[f->file()] = 1; + if (f->file() != _group) continue; + } + else if (_groupType==TraceItem::FunctionCycle) { + if (_groupSize.contains(f->cycle())) + _groupSize[f->cycle()]++; + else + _groupSize[f->cycle()] = 1; + if (f->cycle() != _group) continue; + } + } + _hc.addCost(f, f->inclusive()->subCost(_costType)); + } + } + + updateGroupSizes(true); + + FunctionItem *fi, *item = 0; + + functionList->clear(); + setCostColumnWidths(); + + for(int i=0;i<_hc.realCount();i++) { + fi = new FunctionItem(functionList, (TraceFunction*)_hc[i], + _costType, _groupType); + if (_activeItem == f) item = fi; + } + if (_hc.hasMore()) { + // a placeholder for all the functions skipped ... + new FunctionItem(functionList, _hc.count() - _hc.maxSize(), + (TraceFunction*)_hc[_hc.maxSize()-1], _costType); + } + + functionList->sort(); + + + if (item) { + functionList->ensureItemVisible(item); + _inSetFunction = true; + functionList->setSelected(item, true); + _inSetFunction = false; + } + else { + // this emits a function selection + functionList->setSelected(functionList->firstChild(), true); + } +} + +bool FunctionSelection::setTopFunction() +{ + QListViewItem* i = functionList->firstChild(); + // this emits a function selection + functionList->setSelected(i, true); + functionActivated(i); + return i!=0; +} + +void FunctionSelection::setCostColumnWidths() +{ + if (_costType && (_costType->subCost(_data->callMax())>0) ) { + functionList->setColumnWidthMode(0, QListView::Maximum); + functionList->setColumnWidth(0,50); + functionList->setColumnWidthMode(2, QListView::Maximum); + functionList->setColumnWidth(2,50); + } + else { + functionList->setColumnWidthMode(0, QListView::Manual); + functionList->setColumnWidth(0,0); + functionList->setColumnWidthMode(2, QListView::Manual); + functionList->setColumnWidth(2,0); + } + + functionList->setColumnWidth(1, 50); +} + + + +#include "functionselection.moc" diff --git a/kcachegrind/kcachegrind/functionselection.h b/kcachegrind/kcachegrind/functionselection.h new file mode 100644 index 00000000..84e637aa --- /dev/null +++ b/kcachegrind/kcachegrind/functionselection.h @@ -0,0 +1,85 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * For function selection, to be put into a QDockWindow + */ + +#ifndef FUNCTIONSELECTION_H +#define FUNCTIONSELECTION_H + +#include "functionselectionbase.h" +#include "traceitemview.h" +#include "tracedata.h" +#include "listutils.h" + +class QPopupMenu; + +class TraceFunction; +class TraceData; +class StackBrowser; +class NestedAreaItem; + +class FunctionSelection : public FunctionSelectionBase, public TraceItemView +{ + Q_OBJECT + +public: + FunctionSelection( TopLevel*, QWidget* parent = 0, const char* name = 0); + ~FunctionSelection(); + + TraceCostItem* group(QString); + void setGroup(TraceCostItem*); + void query(QString); + bool setTopFunction(); + + QWidget* widget() { return this; } + + void addGroupMenu(QPopupMenu*); + +public slots: + void searchReturnPressed(); + void searchChanged(const QString&); + void queryDelayed(); + void groupDoubleClicked( QListViewItem* ); + void functionActivated( QListViewItem* ); + void groupSelected( QListViewItem* ); + void functionSelected( QListViewItem* ); + void functionContext(QListViewItem*, const QPoint &, int); + void groupContext(QListViewItem*, const QPoint &, int); + +private: + TraceItem* canShow(TraceItem* i); + void doUpdate(int); + void selectFunction(); + void refresh(); + void setCostColumnWidths(); + void updateGroupSizes(bool hideEmpty); + + TraceCostItem* _group; + + QString _searchString, _searchDelayed; + QTimer _searchTimer; + QMap<TraceCostItem*,int> _groupSize; + + HighestCostList _hc; + // when setting a + bool _inSetGroup, _inSetFunction; +}; + +#endif diff --git a/kcachegrind/kcachegrind/functionselectionbase.ui b/kcachegrind/kcachegrind/functionselectionbase.ui new file mode 100644 index 00000000..e59dc26c --- /dev/null +++ b/kcachegrind/kcachegrind/functionselectionbase.ui @@ -0,0 +1,163 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>FunctionSelectionBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>FunctionSelectionBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>223</width> + <height>485</height> + </rect> + </property> + <property name="caption"> + <string>Function Profile</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>searchLabel</cstring> + </property> + <property name="text"> + <string>&Search:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>searchEdit</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>searchEdit</cstring> + </property> + </widget> + <widget class="QComboBox"> + <property name="name"> + <cstring>groupBox</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>Self</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Group</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>groupList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>32767</width> + <height>150</height> + </size> + </property> + </widget> + <widget class="QListView"> + <column> + <property name="text"> + <string>Incl.</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Self</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Called</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Function</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Location</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>functionList</cstring> + </property> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kcachegrind/kcachegrind/hi32-app-kcachegrind.png b/kcachegrind/kcachegrind/hi32-app-kcachegrind.png Binary files differnew file mode 100644 index 00000000..bd41daec --- /dev/null +++ b/kcachegrind/kcachegrind/hi32-app-kcachegrind.png diff --git a/kcachegrind/kcachegrind/hi48-app-kcachegrind.png b/kcachegrind/kcachegrind/hi48-app-kcachegrind.png Binary files differnew file mode 100644 index 00000000..58c2efdb --- /dev/null +++ b/kcachegrind/kcachegrind/hi48-app-kcachegrind.png diff --git a/kcachegrind/kcachegrind/instritem.cpp b/kcachegrind/kcachegrind/instritem.cpp new file mode 100644 index 00000000..85705f64 --- /dev/null +++ b/kcachegrind/kcachegrind/instritem.cpp @@ -0,0 +1,469 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of instruction view. + */ + +#include <qpixmap.h> +#include <qpainter.h> + +#include <klocale.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kdebug.h> + +#include "configuration.h" +#include "listutils.h" +#include "instritem.h" +#include "instrview.h" + + +// InstrItem + +// for messages +InstrItem::InstrItem(InstrView* iv, QListView* parent, + Addr addr, const QString& msg) + : QListViewItem(parent) +{ + _view = iv; + _addr = addr; + _instr = 0; + _instrCall = 0; + _instrJump = 0; + _inside = false; + + setText(0, addr.pretty()); + setText(6, msg); + + updateGroup(); + updateCost(); +} + +// for code lines +InstrItem::InstrItem(InstrView* iv, QListView* parent, + Addr addr, bool inside, + const QString& code, const QString& cmd, + const QString& args, TraceInstr* instr) + : QListViewItem(parent) +{ + _view = iv; + _addr = addr; + _instr = instr; + _instrCall = 0; + _instrJump = 0; + _inside = inside; + + if (args == "...") + setText(0, args); + else + setText(0, addr.pretty()); + setText(4, code); + setText(5, cmd); + setText(6, args); + + TraceLine* l; + if (instr && (l = instr->line())) + setText(7, l->name()); + + updateGroup(); + updateCost(); +} + +// for call lines +InstrItem::InstrItem(InstrView* iv, QListViewItem* parent, Addr addr, + TraceInstr* instr, TraceInstrCall* instrCall) + : QListViewItem(parent) +{ + _view = iv; + _addr = addr; + _instr = instr; + _instrCall = instrCall; + _instrJump = 0; + _inside = true; + + //qDebug("InstrItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().ascii()); + + SubCost cc = _instrCall->callCount(); + QString templ = " "; + if (cc==0) + templ += i18n("Active call to '%1'"); + else + templ += i18n("%n call to '%1'", "%n calls to '%1'", cc); + + QString callStr = templ.arg(_instrCall->call()->calledName()); + TraceFunction* calledF = _instrCall->call()->called(); + calledF->addPrettyLocation(callStr); + + setText(6, callStr); + + updateGroup(); + updateCost(); +} + +// for jump lines +InstrItem::InstrItem(InstrView* iv, QListViewItem* parent, Addr addr, + TraceInstr* instr, TraceInstrJump* instrJump) + : QListViewItem(parent) +{ + _view = iv; + _addr = addr; + _inside = true; + _instr = instr; + _instrCall = 0; + _instrJump = instrJump; + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().ascii()); + + QString jStr; + if (_instrJump->isCondJump()) + jStr = i18n("Jump %1 of %2 times to 0x%3") + .arg(_instrJump->followedCount().pretty()) + .arg(_instrJump->executedCount().pretty()) + .arg(_instrJump->instrTo()->addr().toString()); + else + jStr = i18n("Jump %1 times to 0x%2") + .arg(_instrJump->executedCount().pretty()) + .arg(_instrJump->instrTo()->addr().toString()); + + setText(6, jStr); + + updateGroup(); + updateCost(); +} + + +void InstrItem::updateGroup() +{ + if (!_instrCall) return; + + TraceFunction* f = _instrCall->call()->called(); + QColor c = Configuration::functionColor(_view->groupType(), f); + setPixmap(6, colorPixmap(10, 10, c)); +} + +void InstrItem::updateCost() +{ + _pure = SubCost(0); + _pure2 = SubCost(0); + + if (!_instr) return; + if (_instrJump) return; + + TraceCost* instrCost = _instrCall ? + (TraceCost*)_instrCall : (TraceCost*)_instr; + + // don't show any cost inside of cycles + if (_instrCall && + ((_instrCall->call()->inCycle()>0) || + (_instrCall->call()->isRecursion()>0))) { + QString str; + QPixmap p; + + QString icon = "undo"; + KIconLoader* loader = KApplication::kApplication()->iconLoader(); + p= loader->loadIcon(icon, KIcon::Small, 0, + KIcon::DefaultState, 0, true); + if (p.isNull()) + str = i18n("(cycle)"); + + setText(1, str); + setPixmap(1, p); + setText(2, str); + setPixmap(2, p); + return; + } + + TraceCost* totalCost; + if (Configuration::showExpanded()) + totalCost = _instr->function()->inclusive(); + else + totalCost = _instr->function()->data(); + + TraceCostType *ct = _view->costType(); + _pure = ct ? instrCost->subCost(ct) : SubCost(0); + if (_pure == 0) { + setText(1, QString::null); + setPixmap(1, QPixmap()); + } + else { + double total = totalCost->subCost(ct); + double pure = 100.0 * _pure / total; + + if (Configuration::showPercentage()) + setText(1, QString("%1") + .arg(pure, 0, 'f', Configuration::percentPrecision())); + else + setText(1, _pure.pretty()); + + setPixmap(1, costPixmap(ct, instrCost, total, false)); + } + + TraceCostType *ct2 = _view->costType2(); + _pure2 = ct2 ? instrCost->subCost(ct2) : SubCost(0); + if (_pure2 == 0) { + setText(2, QString::null); + setPixmap(2, QPixmap()); + } + else { + double total = totalCost->subCost(ct2); + double pure = 100.0 * _pure2 / total; + + if (Configuration::showPercentage()) + setText(2, QString("%1") + .arg(pure, 0, 'f', Configuration::percentPrecision())); + else + setText(2, _pure2.pretty()); + + setPixmap(2, costPixmap(ct2, instrCost, total, false)); + } +} + + +int InstrItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + const InstrItem* ii1 = this; + const InstrItem* ii2 = (InstrItem*) i; + + // we always want descending order + if (((col>0) && ascending) || + ((col==0) && !ascending) ) { + ii1 = ii2; + ii2 = this; + } + + if (col==1) { + if (ii1->_pure < ii2->_pure) return -1; + if (ii1->_pure > ii2->_pure) return 1; + return 0; + } + if (col==2) { + if (ii1->_pure2 < ii2->_pure2) return -1; + if (ii1->_pure2 > ii2->_pure2) return 1; + return 0; + } + if (col==0) { + if (ii1->_addr < ii2->_addr) return -1; + if (ii1->_addr > ii2->_addr) return 1; + + // Same address: code gets above calls/jumps + if (!ii1->_instrCall && !ii1->_instrJump) return -1; + if (!ii2->_instrCall && !ii2->_instrJump) return 1; + + // calls above jumps + if (ii1->_instrCall && !ii2->_instrCall) return -1; + if (ii2->_instrCall && !ii1->_instrCall) return 1; + + if (ii1->_instrCall && ii2->_instrCall) { + // Two calls: desending sort according costs + if (ii1->_pure < ii2->_pure) return 1; + if (ii1->_pure > ii2->_pure) return -1; + + // Two calls: sort according function names + TraceFunction* f1 = ii1->_instrCall->call()->called(); + TraceFunction* f2 = ii2->_instrCall->call()->called(); + if (f1->prettyName() > f2->prettyName()) return 1; + return -1; + } + + // Two jumps: descending sort according target address + if (ii1->_instrJump->instrTo()->addr() < + ii2->_instrJump->instrTo()->addr()) + return -1; + if (ii1->_instrJump->instrTo()->addr() > + ii2->_instrJump->instrTo()->addr()) + return 1; + return 0; + + } + return QListViewItem::compare(i, col, ascending); +} + +void InstrItem::paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int alignment ) +{ + QColorGroup _cg( cg ); + + if ( !_inside || ((column==1) || column==2)) + _cg.setColor( QColorGroup::Base, cg.button() ); + else if ((_instrCall || _instrJump) && column>2) + _cg.setColor( QColorGroup::Base, cg.midlight() ); + + if (column == 3) + paintArrows(p, _cg, width); + else + QListViewItem::paintCell( p, _cg, column, width, alignment ); +} + +void InstrItem::setJumpArray(const QMemArray<TraceInstrJump*>& a) +{ + _jump.duplicate(a); +} + +void InstrItem::paintArrows(QPainter *p, const QColorGroup &cg, int width) +{ + QListView *lv = listView(); + if ( !lv ) return; + InstrView* iv = (InstrView*) lv; + + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole + = QPalette::backgroundRoleFromMode( bgmode ); + if ( cg.brush( crole ) != lv->colorGroup().brush( crole ) ) + p->fillRect( 0, 0, width, height(), cg.brush( crole ) ); + else + iv->paintEmptyArea( p, QRect( 0, 0, width, height() ) ); + + if ( isSelected() && lv->allColumnsShowFocus() ) + p->fillRect( 0, 0, width, height(), cg.brush( QColorGroup::Highlight ) ); + + int marg = lv->itemMargin(); + int yy = height()/2, y1, y2; + QColor c; + + int start = -1, end = -1; + + // draw line borders, detect start/stop of a line + for(int i=0;i< (int)_jump.size();i++) { + if (_jump[i] == 0) continue; + + y1 = 0; + y2 = height(); + if ((_instrJump == _jump[i]) && + (_jump[i]->instrFrom()->addr() == _addr)) { + + //kdDebug() << "InstrItem " << _addr.toString() << ": start " << i << endl; + if (start<0) start = i; + if (_jump[i]->instrTo()->addr() <= _addr) + y2 = yy; + else + y1 = yy; + } + else if (!_instrJump && !_instrCall && + (_jump[i]->instrTo()->addr() == _addr)) { + + //kdDebug() << "InstrItem " << _addr.toString() << ": end " << i << endl; + if (end<0) end = i; + if (_jump[i]->instrFrom()->addr() < _addr) + y2 = yy; + else + y1 = yy; + } + + c = _jump[i]->isCondJump() ? red : blue; +#if 0 + if (_jump[i] == ((TraceItemView*)_view)->selectedItem()) { + p->fillRect( marg + 6*i-2, (y1==0) ? y1: y1-2, + 8, (y2-y1==height())? y2:y2+2, + cg.brush( QColorGroup::Highlight ) ); + c = lv->colorGroup().highlightedText(); + } +#endif + p->fillRect( marg + 6*i, y1, 4, y2, c); + p->setPen(c.light()); + p->drawLine( marg + 6*i, y1, marg + 6*i, y2); + p->setPen(c.dark()); + p->drawLine( marg + 6*i +3, y1, marg + 6*i +3, y2); + } + + // draw start/stop horizontal line + int x, y = yy-2, w, h = 4; + if (start >= 0) { +#if 0 + if (_jump[start] == ((TraceItemView*)_view)->selectedItem()) { + c = lv->colorGroup().highlightedText(); + } +#endif + c = _jump[start]->isCondJump() ? red : blue; + x = marg + 6*start; + w = 6*(iv->arrowLevels() - start) + 10; + p->fillRect( x, y, w, h, c); + p->setPen(c.light()); + p->drawLine(x, y, x+w-1, y); + p->drawLine(x, y, x, y+h-1); + p->setPen(c.dark()); + p->drawLine(x+w-1, y, x+w-1, y+h-1); + p->drawLine(x+1, y+h-1, x+w-1, y+h-1); + } + if (end >= 0) { + c = _jump[end]->isCondJump() ? red : blue; + x = marg + 6*end; + w = 6*(iv->arrowLevels() - end) + 10; + + QPointArray a; + a.putPoints(0, 7, x, y+h, + x,y, x+w-8, y, x+w-8, y-2, + x+w, yy, + x+w-8, y+h+2, x+w-8, y+h); + p->setBrush(c); + p->drawConvexPolygon(a); + + p->setPen(c.light()); + p->drawPolyline(a, 0, 5); + p->setPen(c.dark()); + p->drawPolyline(a, 4, 2); + p->setPen(c.light()); + p->drawPolyline(a, 5, 2); + p->setPen(c.dark()); + p->drawPolyline(a, 6, 2); + } + + // draw inner vertical line for start/stop + // this overwrites borders of horizontal line + for(int i=0;i< (int)_jump.size();i++) { + if (_jump[i] == 0) continue; + + c = _jump[i]->isCondJump() ? red : blue; + + if (_jump[i]->instrFrom()->addr() == _addr) { + bool drawUp = true; + if (_jump[i]->instrTo()->addr() == _addr) + if (start<0) drawUp=false; + if (_jump[i]->instrTo()->addr() > _addr) drawUp=false; + if (drawUp) + p->fillRect( marg + 6*i +1, 0, 2, yy, c); + else + p->fillRect( marg + 6*i +1, yy, 2, height()-yy, c); + } + else if (_jump[i]->instrTo()->addr() == _addr) { + if (end<0) end = i; + if (_jump[i]->instrFrom()->addr() < _addr) + p->fillRect( marg + 6*i +1, 0, 2, yy, c); + else + p->fillRect( marg + 6*i +1, yy, 2, height()-yy, c); + } + } + +} + +int InstrItem::width( const QFontMetrics& fm, + const QListView* lv, int c ) const +{ + if (c != 3) return QListViewItem::width(fm, lv, c); + + InstrView* iv = (InstrView*) lv; + int levels = iv->arrowLevels(); + + if (levels == 0) return 0; + + // 10 pixels for the arrow + return 10 + 6*levels + lv->itemMargin() * 2; +} + diff --git a/kcachegrind/kcachegrind/instritem.h b/kcachegrind/kcachegrind/instritem.h new file mode 100644 index 00000000..50cc26e1 --- /dev/null +++ b/kcachegrind/kcachegrind/instritem.h @@ -0,0 +1,86 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of instruction view. + */ + +#ifndef INSTRITEM_H +#define INSTRITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class InstrView; + +class InstrItem: public QListViewItem +{ + +public: + // for messages + InstrItem(InstrView* iv, QListView* parent, + Addr addr, const QString&); + + // for instruction lines + InstrItem(InstrView* iv, QListView* parent, + Addr addr, bool inside, + const QString&, const QString&, const QString&, + TraceInstr* instr); + + // for call instr + InstrItem(InstrView* iv, QListViewItem* parent, Addr addr, + TraceInstr* instr, TraceInstrCall* instrCall); + + // for jump lines + InstrItem(InstrView* iv, QListViewItem* parent, Addr addr, + TraceInstr* instr, TraceInstrJump* instrJump); + + Addr addr() const { return _addr; } + TraceInstr* instr() const { return _instr; } + TraceInstrCall* instrCall() const { return _instrCall; } + TraceInstrJump* instrJump() const { return _instrJump; } + + int compare(QListViewItem * i, int col, bool ascending ) const; + + void paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int alignment ); + int width( const QFontMetrics& fm, + const QListView* lv, int c ) const; + + void updateGroup(); + void updateCost(); + + // arrow lines + void setJumpArray(const QMemArray<TraceInstrJump*>& a); + +protected: + void paintArrows(QPainter *p, const QColorGroup &cg, int width); + QMemArray<TraceInstrJump*> _jump; + +private: + InstrView* _view; + SubCost _pure, _pure2; + Addr _addr; + TraceInstr* _instr; + TraceInstrJump* _instrJump; + TraceInstrCall* _instrCall; + bool _inside; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/instrview.cpp b/kcachegrind/kcachegrind/instrview.cpp new file mode 100644 index 00000000..7bb8c2c1 --- /dev/null +++ b/kcachegrind/kcachegrind/instrview.cpp @@ -0,0 +1,949 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Instruction View + */ + +#include <qfile.h> +#include <qregexp.h> +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <klocale.h> +#include <kconfig.h> +#include <kdebug.h> + +#include "configuration.h" +#include "instritem.h" +#include "instrview.h" + +// InstrView defaults + +#define DEFAULT_SHOWHEXCODE true + + +// Helpers for parsing output of 'objdump' + +static Addr parseAddr(char* buf) +{ + Addr addr; + uint pos = 0; + + // check for instruction line: <space>* <hex address> ":" <space>* + while(buf[pos]==' ' || buf[pos]=='\t') pos++; + + int digits = addr.set(buf + pos); + if ((digits==0) || (buf[pos+digits] != ':')) return Addr(0); + + return addr; +} + + +static bool parseLine(char* buf, Addr& addr, + uint& pos1, uint& pos2, uint& pos3) +{ + // check for instruction line: <space>* <hex address> ":" <space>* + + pos1 = 0; + while(buf[pos1]==' ' || buf[pos1]=='\t') pos1++; + + int digits = addr.set(buf + pos1); + pos1 += digits; + if ((digits==0) || (buf[pos1] != ':')) return false; + + // further parsing of objdump output... + pos1++; + while(buf[pos1]==' ' || buf[pos1]=='\t') pos1++; + + // skip code, pattern "xx "* + pos2 = pos1; + while(1) { + if (! ((buf[pos2]>='0' && buf[pos2]<='9') || + (buf[pos2]>='a' && buf[pos2]<='f')) ) break; + if (! ((buf[pos2+1]>='0' && buf[pos2+1]<='9') || + (buf[pos2+1]>='a' && buf[pos2+1]<='f')) ) break; + if (buf[pos2+2] != ' ') break; + pos2 += 3; + } + buf[pos2-1]=0; + while(buf[pos2]==' '|| buf[pos2]=='\t') pos2++; + + // skip mnemonic + pos3 = pos2; + while(buf[pos3] && buf[pos3]!=' ' && buf[pos3]!='\t') pos3++; + if (buf[pos3] != 0) { + buf[pos3] = 0; + pos3++; + while(buf[pos3]==' '|| buf[pos3]=='\t') pos3++; + } + + // maximal 50 chars + if (strlen(buf+pos2) > 50) + strcpy(buf+pos2+47, "..."); + + if (0) qDebug("For 0x%s: Code '%s', Mnc '%s', Args '%s'", + addr.toString().ascii(), buf+pos1, buf+pos2, buf+pos3); + + return true; +} + + + + +// +// InstrView +// + + +InstrView::InstrView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + _showHexCode = DEFAULT_SHOWHEXCODE; + _lastHexCodeWidth = 50; + + _inSelectionUpdate = false; + _arrowLevels = 0; + _lowList.setSortLow(true); + _highList.setSortLow(false); + + addColumn( i18n( "#" ) ); + addColumn( i18n( "Cost" ) ); + addColumn( i18n( "Cost 2" ) ); + addColumn( "" ); + addColumn( i18n( "Hex" ) ); + addColumn( "" ); // Instruction + addColumn( i18n( "Assembler" ) ); + addColumn( i18n( "Source Position" ) ); + + setAllColumnsShowFocus(true); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + + connect(this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + connect(this, SIGNAL(selectionChanged(QListViewItem*)), + SLOT(selectedSlot(QListViewItem*))); + + connect(this, + SIGNAL(doubleClicked(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(returnPressed(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + QWhatsThis::add( this, whatsThis()); +} + +void InstrView::paintEmptyArea( QPainter * p, const QRect & r) +{ + QListView::paintEmptyArea(p, r); +} + +QString InstrView::whatsThis() const +{ + return i18n( "<b>Annotated Assembler</b>" + "<p>The annotated assembler list shows the " + "machine code instructions of the current selected " + "function together with (self) cost spent while " + "executing an instruction. If this is a call " + "instruction, lines with details on the " + "call happening are inserted into the source: " + "the cost spent inside of the call, the " + "number of calls happening, and the call destination.</p>" + "<p>The disassembler output shown is generated with " + "the 'objdump' utility from the 'binutils' package.</p>" + "<p>Select a line with call information to " + "make the destination function of this call current.</p>"); +} + +void InstrView::context(QListViewItem* i, const QPoint & p, int c) +{ + QPopupMenu popup; + + TraceInstrCall* ic = i ? ((InstrItem*) i)->instrCall() : 0; + TraceInstrJump* ij = i ? ((InstrItem*) i)->instrJump() : 0; + TraceFunction* f = ic ? ic->call()->called() : 0; + TraceInstr* instr = ij ? ij->instrTo() : 0; + + if (f) { + QString name = f->name(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + popup.insertItem(i18n("Go to '%1'").arg(name), 93); + popup.insertSeparator(); + } + else if (instr) { + popup.insertItem(i18n("Go to Address %1").arg(instr->name()), 93); + popup.insertSeparator(); + } + + if ((c == 1) || (c == 2)) { + addCostMenu(&popup); + popup.insertSeparator(); + } + addGoMenu(&popup); + + popup.insertSeparator(); + popup.setCheckable(true); + popup.insertItem(i18n("Hex Code"), 94); + if (_showHexCode) popup.setItemChecked(94,true); + + int r = popup.exec(p); + if (r == 93) { + if (f) activated(f); + if (instr) activated(instr); + } + else if (r == 94) { + _showHexCode = !_showHexCode; + // remember width when hiding + if (!_showHexCode) + _lastHexCodeWidth = columnWidth(4); + setColumnWidths(); + } +} + + +void InstrView::selectedSlot(QListViewItem * i) +{ + if (!i) return; + // programatically selected items are not signalled + if (_inSelectionUpdate) return; + + TraceInstrCall* ic = ((InstrItem*) i)->instrCall(); + TraceInstrJump* ij = ((InstrItem*) i)->instrJump(); + + if (!ic && !ij) { + TraceInstr* instr = ((InstrItem*) i)->instr(); + if (instr) { + _selectedItem = instr; + selected(instr); + } + return; + } + + if (ic) { + _selectedItem = ic; + selected(ic); + } + else if (ij) { + _selectedItem = ij; + selected(ij); + } +} + +void InstrView::activatedSlot(QListViewItem * i) +{ + if (!i) return; + TraceInstrCall* ic = ((InstrItem*) i)->instrCall(); + TraceInstrJump* ij = ((InstrItem*) i)->instrJump(); + + if (!ic && !ij) { + TraceInstr* instr = ((InstrItem*) i)->instr(); + if (instr) activated(instr); + return; + } + + if (ic) { + TraceFunction* f = ic->call()->called(); + if (f) activated(f); + } + else if (ij) { + TraceInstr* instr = ij->instrTo(); + if (instr) activated(instr); + } +} + + +TraceItem* InstrView::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + TraceFunction* f = 0; + + switch(t) { + case TraceItem::Function: + f = (TraceFunction*) i; + break; + + case TraceItem::Instr: + f = ((TraceInstr*)i)->function(); + select(i); + break; + + case TraceItem::Line: + f = ((TraceLine*)i)->functionSource()->function(); + select(i); + break; + + default: + break; + } + + return f; +} + + +void InstrView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + InstrItem *ii = (InstrItem*)QListView::selectedItem(); + if (ii) { + if ((ii->instr() == _selectedItem) || + (ii->instr() && (ii->instr()->line() == _selectedItem))) return; + } + + QListViewItem *item, *item2; + for (item = firstChild();item;item = item->nextSibling()) { + ii = (InstrItem*)item; + if ((ii->instr() == _selectedItem) || + (ii->instr() && (ii->instr()->line() == _selectedItem))) { + ensureItemVisible(item); + _inSelectionUpdate = true; + setCurrentItem(item); + _inSelectionUpdate = false; + break; + } + item2 = item->firstChild(); + for (;item2;item2 = item2->nextSibling()) { + ii = (InstrItem*)item2; + if (!ii->instrCall()) continue; + if (ii->instrCall()->call()->called() == _selectedItem) { + ensureItemVisible(item2); + _inSelectionUpdate = true; + setCurrentItem(item2); + _inSelectionUpdate = false; + break; + } + } + if (item2) break; + } + return; + } + + if (changeType == groupTypeChanged) { + QListViewItem *item, *item2; + for (item = firstChild();item;item = item->nextSibling()) + for (item2 = item->firstChild();item2;item2 = item2->nextSibling()) + ((InstrItem*)item2)->updateGroup(); + return; + } + + refresh(); +} + +void InstrView::setColumnWidths() +{ + if (_showHexCode) { + setColumnWidthMode(4, QListView::Maximum); + setColumnWidth(4, _lastHexCodeWidth); + } + else { + setColumnWidthMode(4, QListView::Manual); + setColumnWidth(4, 0); + } +} + +void InstrView::refresh() +{ + _arrowLevels = 0; + + // reset to automatic sizing to get column width + setColumnWidthMode(4, QListView::Maximum); + + clear(); + setColumnWidth(0, 20); + setColumnWidth(1, 50); + setColumnWidth(2, _costType2 ? 50:0); + setColumnWidth(3, 0); // arrows, defaults to invisible + setColumnWidth(4, 0); // hex code column + setColumnWidth(5, 20); // command column + setColumnWidth(6, 200); // arg column + setSorting(0); // always reset to address number sort + if (_costType) + setColumnText(1, _costType->name()); + if (_costType2) + setColumnText(2, _costType2->name()); + + if (!_data || !_activeItem) return; + + TraceItem::CostType t = _activeItem->type(); + TraceFunction* f = 0; + if (t == TraceItem::Function) f = (TraceFunction*) _activeItem; + if (t == TraceItem::Instr) { + f = ((TraceInstr*)_activeItem)->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + if (t == TraceItem::Line) { + f = ((TraceLine*)_activeItem)->functionSource()->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + + if (!f) return; + + // Allow resizing of column 2 + setColumnWidthMode(2, QListView::Maximum); + + // check for instruction map + TraceInstrMap::Iterator itStart, it, tmpIt, itEnd; + TraceInstrMap* instrMap = f->instrMap(); + if (instrMap) { + it = instrMap->begin(); + itEnd = instrMap->end(); + // get first instruction with cost of selected type + while(it != itEnd) { + if ((*it).hasCost(_costType)) break; + if (_costType2 && (*it).hasCost(_costType2)) break; + ++it; + } + } + if (!instrMap || (it == itEnd)) { + new InstrItem(this, this, 1, + i18n("There is no instruction info in the profile data file.")); + new InstrItem(this, this, 2, + i18n("For the Valgrind Calltree Skin, rerun with option")); + new InstrItem(this, this, 3, i18n(" --dump-instr=yes")); + new InstrItem(this, this, 4, i18n("To see (conditional) jumps, additionally specify")); + new InstrItem(this, this, 5, i18n(" --trace-jump=yes")); + return; + } + + // initialisation for arrow drawing + // create sorted list of jumps (for jump arrows) + _lowList.clear(); + _highList.clear(); + itStart = it; + while(1) { + TraceInstrJumpList jlist = (*it).instrJumps(); + TraceInstrJump* ij; + for (ij=jlist.first();ij;ij=jlist.next()) { + if (ij->executedCount()==0) continue; + _lowList.append(ij); + _highList.append(ij); + } + ++it; + while(it != itEnd) { + if ((*it).hasCost(_costType)) break; + if (_costType2 && (*it).hasCost(_costType2)) break; + ++it; + } + if (it == itEnd) break; + } + _lowList.sort(); + _highList.sort(); + _lowList.first(); // iterators to list start + _highList.first(); + _arrowLevels = 0; + _jump.resize(0); + + + // do multiple calls to 'objdump' if there are large gaps in addresses + it = itStart; + while(1) { + itStart = it; + while(1) { + tmpIt = it; + ++it; + while(it != itEnd) { + if ((*it).hasCost(_costType)) break; + if (_costType2 && (*it).hasCost(_costType2)) break; + ++it; + } + if (it == itEnd) break; + if (!(*it).addr().isInRange( (*tmpIt).addr(),10000) ) break; + } + + // tmpIt is always last instruction with cost + if (!fillInstrRange(f, itStart, ++tmpIt)) break; + if (it == itEnd) break; + } + + _lastHexCodeWidth = columnWidth(4); + setColumnWidths(); + + if (!_costType2) { + setColumnWidthMode(2, QListView::Manual); + setColumnWidth(2, 0); + } +} + +/* This is called after adding instrItems, for each of them in + * address order. _jump is the global array of valid jumps + * for a line while we iterate downwards. + * The existing jumps, sorted in lowList according lower address, + * is iterated in the same way. + */ +void InstrView::updateJumpArray(Addr addr, InstrItem* ii, + bool ignoreFrom, bool ignoreTo) +{ + TraceInstrJump* ij; + Addr lowAddr, highAddr; + int iEnd = -1, iStart = -1; + + if (0) qDebug("updateJumpArray(addr 0x%s, jump to %s)", + addr.toString().ascii(), + ii->instrJump() + ? ii->instrJump()->instrTo()->name().ascii() : "?" ); + + // check for new arrows starting from here downwards + ij=_lowList.current(); + while(ij) { + lowAddr = ij->instrFrom()->addr(); + if (ij->instrTo()->addr() < lowAddr) + lowAddr = ij->instrTo()->addr(); + + if (lowAddr > addr) break; + + // if target is downwards but we draw no source, break + if (ignoreFrom && (lowAddr < ij->instrTo()->addr())) break; + // if source is downward but we draw no target, break + if (ignoreTo && (lowAddr < ij->instrFrom()->addr())) break; + // if this is another jump start, break + if (ii->instrJump() && (ij != ii->instrJump())) break; + +#if 0 + for(iStart=0;iStart<_arrowLevels;iStart++) + if (_jump[iStart] && + (_jump[iStart]->instrTo() == ij->instrTo())) break; +#else + iStart = _arrowLevels; +#endif + + if (iStart==_arrowLevels) { + for(iStart=0;iStart<_arrowLevels;iStart++) + if (_jump[iStart] == 0) break; + if (iStart==_arrowLevels) { + _arrowLevels++; + _jump.resize(_arrowLevels); + } + if (0) qDebug(" new start at %d for %s", iStart, ij->name().ascii()); + _jump[iStart] = ij; + } + ij=_lowList.next(); + } + + ii->setJumpArray(_jump); + + // check for active arrows ending here + ij=_highList.current(); + while(ij) { + highAddr = ij->instrFrom()->addr(); + if (ij->instrTo()->addr() > highAddr) { + highAddr = ij->instrTo()->addr(); + if (ignoreTo) break; + } + else if (ignoreFrom) break; + + if (highAddr > addr) break; + + for(iEnd=0;iEnd<_arrowLevels;iEnd++) + if (_jump[iEnd] == ij) break; + if (iEnd==_arrowLevels) { + kdDebug() << "InstrView: no jump start for end at 0x" + << highAddr.toString() << " ?" << endl; + iEnd = -1; + } + + if (0 && (iEnd>=0)) + qDebug(" end %d (%s to %s)", + iEnd, + _jump[iEnd]->instrFrom()->name().ascii(), + _jump[iEnd]->instrTo()->name().ascii()); + + if (0 && ij) qDebug("next end: %s to %s", + ij->instrFrom()->name().ascii(), + ij->instrTo()->name().ascii()); + + ij=_highList.next(); + if (highAddr > addr) + break; + else { + if (iEnd>=0) _jump[iEnd] = 0; + iEnd = -1; + } + } + if (iEnd>=0) _jump[iEnd] = 0; +} + + + +/** + * Fill up with instructions from cost range [it;itEnd[ + */ +bool InstrView::fillInstrRange(TraceFunction* function, + TraceInstrMap::Iterator it, + TraceInstrMap::Iterator itEnd) +{ + Addr costAddr, nextCostAddr, objAddr, addr; + Addr dumpStartAddr, dumpEndAddr; + TraceInstrMap::Iterator costIt; + + // shouldn't happen + if (it == itEnd) return false; + + // calculate address range for call to objdump + TraceInstrMap::Iterator tmpIt = itEnd; + --tmpIt; + nextCostAddr = (*it).addr(); + dumpStartAddr = (nextCostAddr<20) ? Addr(0) : nextCostAddr -20; + dumpEndAddr = (*tmpIt).addr() +20; + + // generate command + QString popencmd, objfile; + objfile = function->object()->name(); + objfile = objfile.replace(QRegExp("[\"']"), ""); // security... + popencmd = QString("objdump -C -d " + "--start-address=0x%1 --stop-address=0x%2 \"%3\"") + .arg(dumpStartAddr.toString()).arg(dumpEndAddr.toString()) + .arg(objfile); + if (1) qDebug("Running '%s'...", popencmd.ascii()); + + // and run... + FILE* iFILE = popen(QFile::encodeName( popencmd ), "r"); + if (iFILE == 0) { + new InstrItem(this, this, 1, + i18n("There is an error trying to execute the command")); + new InstrItem(this, this, 2, ""); + new InstrItem(this, this, 3, popencmd); + new InstrItem(this, this, 4, ""); + new InstrItem(this, this, 5, + i18n("Check that you have installed 'objdump'.")); + new InstrItem(this, this, 6, + i18n("This utility can be found in the 'binutils' package.")); + return false; + } + QFile file; + file.open(IO_ReadOnly, iFILE); + +#define BUF_SIZE 256 + + char buf[BUF_SIZE]; + bool inside = false, skipLineWritten = true; + int readBytes = -1; + int objdumpLineno = 0, dumpedLines = 0, noAssLines = 0; + SubCost most = 0; + TraceInstr* currInstr; + InstrItem *ii, *ii2, *item = 0, *first = 0, *selected = 0; + QString code, cmd, args; + bool needObjAddr = true, needCostAddr = true; + + costAddr = 0; + objAddr = 0; + + while (1) { + + if (needObjAddr) { + needObjAddr = false; + + // read next objdump line + while (1) { + readBytes=file.readLine(buf, BUF_SIZE); + if (readBytes<=0) { + objAddr = 0; + break; + } + + objdumpLineno++; + if (readBytes == BUF_SIZE) { + qDebug("ERROR: Line %d of '%s' too long\n", + objdumpLineno, popencmd.ascii()); + } + else if ((readBytes>0) && (buf[readBytes-1] == '\n')) + buf[readBytes-1] = 0; + + objAddr = parseAddr(buf); + if ((objAddr<dumpStartAddr) || (objAddr>dumpEndAddr)) + objAddr = 0; + if (objAddr != 0) break; + } + + if (0) kdDebug() << "Got ObjAddr: 0x" << objAddr.toString() << endl; + } + + // try to keep objAddr in [costAddr;nextCostAddr] + if (needCostAddr && + (nextCostAddr > 0) && + ((objAddr == Addr(0)) || (objAddr >= nextCostAddr)) ) { + needCostAddr = false; + + costIt = it; + ++it; + while(it != itEnd) { + if ((*it).hasCost(_costType)) break; + if (_costType2 && (*it).hasCost(_costType2)) break; + ++it; + } + costAddr = nextCostAddr; + nextCostAddr = (it == itEnd) ? Addr(0) : (*it).addr(); + + if (0) kdDebug() << "Got nextCostAddr: 0x" << nextCostAddr.toString() + << ", costAddr 0x" << costAddr.toString() << endl; + } + + // if we have no more address from objdump, stop + if (objAddr == 0) break; + + if ((nextCostAddr==0) || (costAddr == 0) || + (objAddr < nextCostAddr)) { + // next line is objAddr + + uint pos1, pos2, pos3; + + // this sets addr + parseLine(buf, addr, pos1, pos2, pos3); + code = QString(buf + pos1); + cmd = QString(buf + pos2); + args = QString(buf + pos3); + + if (costAddr == objAddr) { + currInstr = &(*costIt); + needCostAddr = true; + } + else + currInstr = 0; + + needObjAddr = true; + + if (0) kdDebug() << "Dump Obj Addr: 0x" << addr.toString() + << " [" << cmd << " " << args << "], cost (0x" + << costAddr.toString() << ", next 0x" + << nextCostAddr.toString() << ")" << endl; + } + else { + addr = costAddr; + code = cmd = QString(); + args = i18n("(No Assembler)"); + + currInstr = &(*costIt); + needCostAddr = true; + + noAssLines++; + if (0) kdDebug() << "Dump Cost Addr: 0x" << addr.toString() + << " (no ass), objAddr 0x" << objAddr.toString() << endl; + } + + // update inside + if (!inside) { + if (currInstr) inside = true; + } + else { + if (0) kdDebug() << "Check if 0x" << addr.toString() << " is in ]0x" + << costAddr.toString() << ",0x" + << (nextCostAddr - 3*Configuration::noCostInside()).toString() + << "[" << endl; + + // Suppose a average instruction len of 3 bytes + if ( (addr > costAddr) && + ((nextCostAddr==0) || + (addr < nextCostAddr - 3*Configuration::noCostInside()) )) + inside = false; + } + + int context = Configuration::context(); + + if ( ((costAddr==0) || (addr > costAddr + 3*context)) && + ((nextCostAddr==0) || (addr < nextCostAddr - 3*context)) ) { + + // the very last skipLine can be ommitted + if ((it == itEnd) && + (itEnd == function->instrMap()->end())) skipLineWritten=true; + + if (!skipLineWritten) { + skipLineWritten = true; + // a "skipping" line: print "..." instead of a line number + code = cmd = QString(); + args = QString("..."); + } + else + continue; + } + else + skipLineWritten = false; + + + ii = new InstrItem(this, this, addr, inside, + code, cmd, args, currInstr); + dumpedLines++; + if (0) kdDebug() << "Dumped 0x" << addr.toString() << " " + << (inside ? "Inside " : "Outside") + << (currInstr ? "Cost" : "") << endl; + + // no calls/jumps if we have no cost for this line + if (!currInstr) continue; + + if (!selected && + (currInstr == _selectedItem) || + (currInstr->line() == _selectedItem)) selected = ii; + + if (!first) first = ii; + + if (currInstr->subCost(_costType) > most) { + item = ii; + most = currInstr->subCost(_costType); + } + + ii->setOpen(true); + TraceInstrCallList list = currInstr->instrCalls(); + TraceInstrCall* ic; + for (ic=list.first();ic;ic=list.next()) { + if ((ic->subCost(_costType)==0) && + (ic->subCost(_costType2)==0)) continue; + + if (ic->subCost(_costType) > most) { + item = ii; + most = ic->subCost(_costType); + } + + ii2 = new InstrItem(this, ii, addr, currInstr, ic); + + if (!selected && (ic->call()->called() == _selectedItem)) + selected = ii2; + } + + TraceInstrJumpList jlist = currInstr->instrJumps(); + TraceInstrJump* ij; + for (ij=jlist.first();ij;ij=jlist.next()) { + if (ij->executedCount()==0) continue; + + new InstrItem(this, ii, addr, currInstr, ij); + } + } + + if (selected) item = selected; + if (item) first = item; + if (first) { + ensureItemVisible(first); + _inSelectionUpdate = true; + setCurrentItem(first); + _inSelectionUpdate = false; + } + + file.close(); + pclose(iFILE); + + // for arrows: go down the list according to list sorting + sort(); + QListViewItem *item1, *item2; + for (item1=firstChild();item1;item1 = item1->nextSibling()) { + ii = (InstrItem*)item1; + updateJumpArray(ii->addr(), ii, true, false); + + for (item2=item1->firstChild();item2;item2 = item2->nextSibling()) { + ii2 = (InstrItem*)item2; + if (ii2->instrJump()) + updateJumpArray(ii->addr(), ii2, false, true); + else + ii2->setJumpArray(_jump); + } + } + + if (arrowLevels()) + setColumnWidth(3, 10 + 6*arrowLevels() + itemMargin() * 2); + else + setColumnWidth(3, 0); + + + if (noAssLines > 1) { + // trace cost not machting code + + new InstrItem(this, this, 1, + i18n("There is %n cost line without assembler code.", + "There are %n cost lines without assembler code.", noAssLines)); + new InstrItem(this, this, 2, + i18n("This happens because the code of")); + new InstrItem(this, this, 3, QString(" %1").arg(objfile)); + new InstrItem(this, this, 4, + i18n("does not seem to match the profile data file.")); + new InstrItem(this, this, 5, ""); + new InstrItem(this, this, 6, + i18n("Are you using an old profile data file or is the above mentioned")); + new InstrItem(this, this, 7, + i18n("ELF object from an updated installation/another machine?")); + new InstrItem(this, this, 8, ""); + return false; + } + + if (dumpedLines == 0) { + // no matching line read from popen + new InstrItem(this, this, 1, + i18n("There seems to be an error trying to execute the command")); + new InstrItem(this, this, 2, ""); + new InstrItem(this, this, 3, popencmd); + new InstrItem(this, this, 4, ""); + new InstrItem(this, this, 5, + i18n("Check that the ELF object used in the command exists.")); + new InstrItem(this, this, 6, + i18n("Check that you have installed 'objdump'.")); + new InstrItem(this, this, 7, + i18n("This utility can be found in the 'binutils' package.")); + return false; + } + + return true; +} + + +void InstrView::updateInstrItems() +{ + InstrItem* ii; + QListViewItem* item = firstChild(); + for (;item;item = item->nextSibling()) { + ii = (InstrItem*)item; + TraceInstr* instr = ii->instr(); + if (!instr) continue; + + ii->updateCost(); + + QListViewItem *next, *i = ii->firstChild(); + for (;i;i = next) { + next = i->nextSibling(); + ((InstrItem*)i)->updateCost(); + } + } +} + +void InstrView::readViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup* g = configGroup(c, prefix, postfix); + + if (0) qDebug("InstrView::readViewConfig"); + + _showHexCode = g->readBoolEntry("ShowHexCode", DEFAULT_SHOWHEXCODE); + + delete g; +} + +void InstrView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + writeConfigEntry(&g, "ShowHexCode", _showHexCode, DEFAULT_SHOWHEXCODE); +} + +#include "instrview.moc" diff --git a/kcachegrind/kcachegrind/instrview.h b/kcachegrind/kcachegrind/instrview.h new file mode 100644 index 00000000..9a2f81cf --- /dev/null +++ b/kcachegrind/kcachegrind/instrview.h @@ -0,0 +1,82 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Instruction View + */ + +#ifndef INSTRVIEW_H +#define INSTRVIEW_H + +#include <qlistview.h> +#include "traceitemview.h" + +class InstrItem; + +class InstrView : public QListView, public TraceItemView +{ + friend class InstrItem; + + Q_OBJECT + +public: + InstrView(TraceItemView* parentView, + QWidget* parent = 0, const char* name = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + + void readViewConfig(KConfig*, QString prefix, QString postfix, bool); + void saveViewConfig(KConfig*, QString prefix, QString postfix, bool); + +protected: + int arrowLevels() { return _arrowLevels; } + void paintEmptyArea( QPainter *, const QRect & ); + +private slots: + void context(QListViewItem*, const QPoint &, int); + void selectedSlot(QListViewItem *); + void activatedSlot(QListViewItem *); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); + void setColumnWidths(); + void fillInstr(); + void updateJumpArray(Addr,InstrItem*,bool,bool); + bool fillInstrRange(TraceFunction*, + TraceInstrMap::Iterator,TraceInstrMap::Iterator); + void updateInstrItems(); + + bool _inSelectionUpdate; + + // arrows + int _arrowLevels; + // temporary needed on creation... + QMemArray<TraceInstrJump*> _jump; + TraceInstrJumpList _lowList, _highList; + + // remember width of hex code column if hidden + int _lastHexCodeWidth; + + // widget options + bool _showHexCode; +}; + +#endif diff --git a/kcachegrind/kcachegrind/kcachegrind.desktop b/kcachegrind/kcachegrind/kcachegrind.desktop new file mode 100644 index 00000000..84088653 --- /dev/null +++ b/kcachegrind/kcachegrind/kcachegrind.desktop @@ -0,0 +1,103 @@ +# KDE Config File +[Desktop Entry] +Type=Application +Exec=kcachegrind -caption "%c" %i %m %u +MimeType=application/x-kcachegrind; +Icon=kcachegrind +DocPath=kcachegrind/index.html +Terminal=false +Name=KCachegrind +Name[hi]=के-केश-ग्रिंड +Name[sv]=Kcachegrind +Name[ta]= இடைமாற்றகட்டம் +GenericName=Profiler Frontend +GenericName[bs]=Profiler frontend +GenericName[ca]=Interfície de Profiler +GenericName[cs]=Rozhraní pro profilaci +GenericName[cy]=Blaen-wyneb Proffilydd +GenericName[da]=Grænseflade til profilering +GenericName[de]=Profiler Oberfläche +GenericName[el]=Πρόγραμμα προφίλ +GenericName[eo]=Fasado de Profililo +GenericName[es]=Interfaz para Profiler +GenericName[et]=Profileerimisrakendus +GenericName[eu]=Profilatzailearen interfazea +GenericName[fa]=پایانۀ گزارشگیر +GenericName[fi]=Profiloijan käyttöliittymä +GenericName[fr]=Interface de profilage +GenericName[ga]=Comhéadan ar Phróifíleoir +GenericName[gl]=Interface para o profiler +GenericName[hi]=प्रोफ़ाइलर फ्रन्टएण्ड +GenericName[hu]=Profilozó +GenericName[is]=Myndrænt viðmót á afkastakönnuð +GenericName[it]=Interfaccia a profiler +GenericName[ja]=プロファイラフロントエンド +GenericName[ka]=პროფილერის Frontend +GenericName[kk]=Профильдеткіштің интерфейсі +GenericName[lt]=Profiliuoklio naudotojo sąsaja +GenericName[nb]=Grensesnitt for profilvisning +GenericName[nds]=Profiler-Böversiet +GenericName[ne]=प्रोफाइलर फ्रन्टइन्ड +GenericName[nl]=Profiler-hulpprogramma +GenericName[nn]=Grensesnitt for profilvising +GenericName[pa]=ਪਰੋਫਾਇਲਰ ਮੁੱਖ ਭੂਮੀ +GenericName[pl]=Interfejs do profilera +GenericName[pt]=Interface de Profiler +GenericName[pt_BR]=Interface para o Profiler +GenericName[ru]=Профилировщик +GenericName[sk]=Rozhranie pre profiler +GenericName[sl]=Vmesnik profilnika +GenericName[sr]=Графички интерфејс за профајлер +GenericName[sr@Latn]=Grafički interfejs za profajler +GenericName[sv]=Profileringsgränssnitt +GenericName[ta]= விவரக்குறிப்பு முன்பகுதி +GenericName[tg]=Интерфейс ба профилкунанда +GenericName[tr]=Profil Önyüzü +GenericName[uk]=Інтерфейс до Profiler +GenericName[zh_CN]=个性数据前端 +GenericName[zh_TW]=分析器前端 +Comment=Visualization of Performance Profiling Data +Comment[bg]=Визуализация на данните за производителност +Comment[bs]=Vizualizacija podataka za profiliranje performansi +Comment[ca]=Visualizació de dades de perfilat de rendiment +Comment[cs]=Vizualizace profilovacích dat výkonu +Comment[da]=Visualisering af profileringsdata +Comment[de]=Visualisierung von Daten des Laufzeitverhaltens eines Programmes +Comment[el]=Αναπαράσταση δεδομένων ταχύτητας προφίλ +Comment[en_GB]=Visualisation of Performance Profiling Data +Comment[es]=Visualización de datos de análisis de rendimiento +Comment[et]=Jõudluse profileerimise andmete visualiseerimise vahend +Comment[eu]=Errendimendu profil datuen bistaratzea +Comment[fa]=تجسم کارایی گزارش دادهها +Comment[fi]=Visualisointi tehokkuusprofiloinnin tiedoista +Comment[fr]=Visualisation des données de performance de profilage +Comment[gl]=Visualización dos datos da análise de rendimento +Comment[hi]=परफार्मेस प्रोफाइलिंग डाटा का विजुअलाइज़ेशन +Comment[hu]=Teljesítményprofil-adatok megjelenítése +Comment[is]=Sjónræn framsetning gagna úr afkastakönnun +Comment[it]=Visualizzazione dei dati di profiling delle prestazioni +Comment[ja]=パフォーマンスプロファイルデータを視覚化 +Comment[ka]=წარმადობის მაპროფფილებელი მონაცემების ვიზუალიზაცია +Comment[kk]=Деректерді профильдеудің визуализациясы +Comment[lt]=Veikimo profiliavimo duomenų vizualizacija +Comment[nb]=Vis informasjon om ytelse. +Comment[nds]=Visualiseren vun Programmleisten-Looptietdaten +Comment[ne]=सम्पादन प्रोफाइलिङ डाटाको दृष्टिकरण +Comment[nl]=Visualisatie van Performance Profiling Data +Comment[nn]=Vis informasjon om yting +Comment[pl]=Wizualizacja danych profilowania wydajności +Comment[pt]=Visualização dos Dados de Análise de Performance +Comment[pt_BR]=Visualização de Dados de Perfil de Desempenho +Comment[ru]=Утилита для визуального профилирования приложений +Comment[sk]=Vizualizácia dát o výkone +Comment[sl]=Vizualizacija podatkov profilnih zmogljivosti +Comment[sr]=Визуелизација података о профилисању перформанси +Comment[sr@Latn]=Vizuelizacija podataka o profilisanju performansi +Comment[sv]=Åskådliggörande av profileringsdata för prestanda +Comment[ta]= விவர தகவலை செயல்பாட்டு காட்சியாளிப்பு +Comment[tg]=Утилита барои гузориши профили визуалӣ +Comment[uk]=Візуалізація даних профілювання швидкодії +Comment[zh_CN]=性能个性数据的可视化表现 +Comment[zh_TW]=效能分析資料視覺化 +X-DCOP-ServiceType=Multi +Categories=Qt;KDE;Development; diff --git a/kcachegrind/kcachegrind/kcachegrindui.rc b/kcachegrind/kcachegrind/kcachegrindui.rc new file mode 100644 index 00000000..af9b8dcd --- /dev/null +++ b/kcachegrind/kcachegrind/kcachegrindui.rc @@ -0,0 +1,57 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kcachegrind" version="4"> + <MenuBar> + <Menu name="file"><text>&File</text> + <Action name="file_add" append="open_merge"/> + <Action name="reload" append="revert_merge"/> + <Action name="dump" append="revert_merge"/> + <Action name="export"/> + </Menu> + <Menu name="view"><text>&View</text> + <Action name="view_cost_type"/> + <Action name="view_cost_type2"/> + <Action name="view_group_type"/> + <Separator/> + <Menu name="layouts"><text>&Layout</text> + <Action name="layout_next"/> + <Action name="layout_previous"/> + <Action name="layout_duplicate"/> + <Action name="layout_remove"/> + <Separator/> + <Action name="layout_restore"/> + <Action name="layout_save"/> + </Menu> + <Action name="view_split"/> + <Action name="view_split_dir"/> + <Separator/> + <Action name="view_percentage"/> + <Action name="view_expanded"/> + <Action name="view_cycles"/> + </Menu> + <Menu name="settings"> + <Menu append="show_toolbar_merge"><text>Sidebars</text> + <Action name="settings_show_dumpdock"/> + <Action name="settings_show_partdock"/> + <Action name="settings_show_stackdock"/> + <Action name="settings_show_profiledock"/> + </Menu> + </Menu> + </MenuBar> + + <ToolBar name="mainToolBar" noMerge="1"><text>Main Toolbar</text> + <Action name="file_open"/> + <Action name="reload"/> + <Action name="dump"/> + <Separator/> + <Action name="go_up"/> + <Action name="go_back"/> + <Action name="go_forward"/> + <Separator/> + <Action name="view_percentage"/> + <Action name="view_expanded"/> + <Action name="view_cycles"/> + </ToolBar> + <ToolBar name="stateToolBar" noMerge="1"><text>State Toolbar</text> + <Action name="view_cost_type"/> + </ToolBar> +</kpartgui> diff --git a/kcachegrind/kcachegrind/listutils.cpp b/kcachegrind/kcachegrind/listutils.cpp new file mode 100644 index 00000000..7a14e552 --- /dev/null +++ b/kcachegrind/kcachegrind/listutils.cpp @@ -0,0 +1,266 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Some helper functions for QListViewItem derivates + */ + +#include <qpainter.h> +#include "listutils.h" + +#define COSTPIX_WIDTH 25 + +QPixmap colorPixmap(int w, int h, QColor c) +{ + static QPixmap* pixs[37]; + static QColor cols[37]; + static bool inited = false; + + if (!inited) { + for (int i=0;i<37;i++) pixs[i]=0; + inited = true; + } + int hash = (w+h+c.red()+c.green()+c.blue()) % 37; + if (pixs[hash]) { + if ((pixs[hash]->width() == w) && + (pixs[hash]->height() == h) && + (cols[hash] == c)) + return *pixs[hash]; + + delete pixs[hash]; + } + + + QPixmap* pix = new QPixmap(w, h); + pix->fill(c); + QPainter p(pix); + p.setPen(c.light()); + p.drawLine(0, 0, w-1, 0); + p.drawLine(0, 0, 0, h-1); + p.setPen(c.dark()); + p.drawLine(w-1, 0, w-1, h-1); + p.drawLine(0, h-1, w-1, h-1); + + pixs[hash] = pix; + cols[hash] = c; + return *pix; +} + +/** + * Create a percentage pixmap with a filling rate of p percent (0-100). + * When withFrame==false, the pixmap is truncated to only the filled portion. + */ +QPixmap percentagePixmap(int w, int h, int percent, QColor c, bool framed) +{ + int iw, ix1, ix2, ih, iy1, iy2; + + // inner rectangle to fill with bar + if (framed) { + iw = w-2, ix1 = 1, ix2 = w-2; + ih = h-2, iy1 = 1, iy2 = h-2; + } + else { + iw = w; ix1 = 0; ix2 = w-1; + ih = h; iy1 = 0; iy2 = h-1; + } + + /* Limit bar to 100% */ + int filled = (percent>100) ? iw+1 : iw*percent/100+1; + if (!framed) w=filled-1; + if (w<3) return QPixmap(); + + QPixmap pix(w, h); + pix.fill(Qt::white); + QPainter p(&pix); + p.setPen(Qt::black); + if (framed) + p.drawRect(0, 0, w, h); + + // inside + p.setPen(Qt::NoPen); + p.setBrush(c); + p.drawRect(ix1, iy1, filled-1,ih); + + // frame + ix2 = ix1+filled-2; + p.setPen(c.light()); + p.drawLine(ix1, iy1, ix2, iy1); + p.drawLine(ix1, iy1, ix1, iy2); + p.setPen(c.dark()); + p.drawLine(ix1+1, iy2, ix2, iy2); + p.drawLine(ix2, iy1, ix2, iy2); + + return pix; +} + +inline QColor partitionColor(int d, int max) +{ + return QColor( (720*d/max) % 360, + 255-(128*d/max), 192, QColor::Hsv); +} + + +QPixmap partitionPixmap(int w, int h, + double* hist, QColor* cArray, int maxIndex, bool framed) +{ + int lastPos = 0, nextPos; + double val=0.0, sum=0.0; + int d, dmin=maxIndex, dmax=0; + for (d = 0;d<maxIndex;d++) + if (hist[d]>0.0) { + sum += hist[d]; + if (dmin>d) dmin = d; + if (dmax<d) dmax = d; + } + + // inner rectangle to fill with bar + int iw, ix1, ix2, ih, iy1, iy2; + if (framed) { + iw = w-2, ix1 = 1, ix2 = w-2; + ih = h-2, iy1 = 1, iy2 = h-2; + } + else { + iw = w; ix1 = 0; ix2 = w-1; + ih = h; iy1 = 0; iy2 = h-1; + } + + int filled = (int)(iw*sum+1); + if (!framed && (filled < w)) w=filled; + if (w<3) return QPixmap(); + + QPixmap pix(w, h); + pix.fill(Qt::white); + QPainter p(&pix); + p.setPen(Qt::black); + if (framed) + p.drawRect(0, 0, w, h); + + //qDebug("Sum %f, dw %d", sum,dw); + + QColor c, cLast; + bool leftDrawn = false; + int x1, x2=0; + int lastDiff=0, diff; + d=dmin; + while (d<dmax+1) { + val += hist[d]; + nextPos = (int)(filled * val/sum); + + //qDebug(" hist[%d] %f, val %f, nextPos %d", d, hist[d], val, nextPos); + + diff = nextPos-lastPos; + if (diff==0) { d++; continue; } + + c = cArray ? cArray[d] : partitionColor(d,maxIndex); + + x1 = ix1+lastPos; + x2 = ix1+nextPos; + if (x2>=iw) x2=iw-1; + + // inside + p.setPen(Qt::NoPen); + p.setBrush(c); + p.drawRect(x1, iy1, x2-x1+1, ih); + + // lighter top border + p.setPen(c.light()); + p.drawLine(x1, iy1, x2-1, iy1); + + // when width for last and current distance >2, draw full 3D effect... + if (!leftDrawn) { + p.drawLine(x1, iy1+1, x1, iy2); + leftDrawn = true; + } + + // darker bottom border + p.setPen(c.dark()); + p.drawLine(x1, iy2, x2-1, iy2); + + lastPos = nextPos; + lastDiff = diff; + cLast = c; + d++; + } + + // right border (in last color) + if (x2>0) + p.drawLine(x2, iy1, x2, iy2); + + return pix; +} + + +QPixmap costPixmap(TraceCostType* ct, TraceCost* cost, double total, bool framed) +{ + if (ct->isReal()) { + QColor color = ct->color(); + double p = 100.0 * cost->subCost(ct) / total; + return percentagePixmap(COSTPIX_WIDTH, 10, (int)(p+.5), color, framed); + } + + int maxIndex; + double h[MaxRealIndexValue]; + QColor* cs = ct->mapping()->realColors(); + maxIndex = ct->histCost(cost, total, h); + + if (maxIndex ==0) return QPixmap(); + return partitionPixmap(COSTPIX_WIDTH, 10, h, cs, maxIndex, framed); +} + + + +// HighestCostList + +HighestCostList::HighestCostList() +{ + _maxSize = 0; + _count = 0; + _costType = 0; +} + +void HighestCostList::clear(int maxSize) +{ + _maxSize = maxSize; + _count = 0; + _item.resize(maxSize); + _cost.resize(maxSize); +} + +void HighestCostList::addCost(TraceCost* c, SubCost cost) +{ + int i; + + _count++; + if (_count > _maxSize) { + if (_cost[_maxSize-1] >= cost) return; + i = _maxSize-1; + } + else i = _count-1; + + for(; i>0; i--) { + if (_cost[i-1] >= cost) break; + else { + _cost[i] = _cost[i-1]; + _item[i] = _item[i-1]; + } + } + _cost[i] = cost; + _item[i] = c; +} + + diff --git a/kcachegrind/kcachegrind/listutils.h b/kcachegrind/kcachegrind/listutils.h new file mode 100644 index 00000000..abf7c723 --- /dev/null +++ b/kcachegrind/kcachegrind/listutils.h @@ -0,0 +1,65 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Some helper functions for QListViewItem derivates + */ + +#ifndef LISTUTILS_H +#define LISTUTILS_H + +#include <qpixmap.h> +#include <qstring.h> +#include <qcolor.h> +#include "tracedata.h" + +QString bigNum(SubCost); +QPixmap colorPixmap(int w, int h, QColor c); +QPixmap percentagePixmap(int w, int h, int percent, QColor c, bool framed); +QPixmap partitionPixmap(int w, int h, double* hist, QColor*, + int maxIndex, bool framed); +QPixmap costPixmap(TraceCostType* ct, TraceCost* cost, double total, bool framed); + +/** + * A class to calculate the <maxSize> TraceCost items + * with highest cost. + */ + +class HighestCostList +{ + public: + HighestCostList(); + + void clear(int maxSize); + void addCost(TraceCost*, SubCost); + int count() { return _count; } + int realCount() { return (_count > _maxSize) ? _maxSize:_count; } + int maxSize() { return _maxSize; } + bool hasMore() { return _count > _maxSize; } + TraceCost* operator[] (int i) + { return (i>=0 && i<_count && i<_maxSize) ? _item[i] : 0; } + + private: + TraceCostList _list; + int _maxSize, _count; + TraceCostType* _costType; + QMemArray<TraceCost*> _item; + QMemArray<SubCost> _cost; +}; + +#endif diff --git a/kcachegrind/kcachegrind/lo16-app-kcachegrind.png b/kcachegrind/kcachegrind/lo16-app-kcachegrind.png Binary files differnew file mode 100644 index 00000000..0985586b --- /dev/null +++ b/kcachegrind/kcachegrind/lo16-app-kcachegrind.png diff --git a/kcachegrind/kcachegrind/lo32-app-kcachegrind.png b/kcachegrind/kcachegrind/lo32-app-kcachegrind.png Binary files differnew file mode 100644 index 00000000..12542c8a --- /dev/null +++ b/kcachegrind/kcachegrind/lo32-app-kcachegrind.png diff --git a/kcachegrind/kcachegrind/loader.cpp b/kcachegrind/kcachegrind/loader.cpp new file mode 100644 index 00000000..2b373851 --- /dev/null +++ b/kcachegrind/kcachegrind/loader.cpp @@ -0,0 +1,85 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Base class for loaders of profiling data. + */ + +#include "loader.h" + + +/// Loader + +LoaderList Loader::_loaderList; + +Loader::Loader(QString name, QString desc) +{ + _name = name; + _description = desc; +} + +Loader::~Loader() +{} + +bool Loader::canLoadTrace(QFile*) +{ + return false; +} + +bool Loader::loadTrace(TracePart*) +{ + return false; +} + +Loader* Loader::matchingLoader(QFile* file) +{ + Loader* l; + for (l=_loaderList.first(); l; l = _loaderList.next()) + if (l->canLoadTrace(file)) + return l; + + return 0; +} + +Loader* Loader::loader(QString name) +{ + Loader* l; + for (l=_loaderList.first(); l; l = _loaderList.next()) + if (l->name() == name) + return l; + + return 0; +} + +// factories of available loaders +Loader* createCachegrindLoader(); + +void Loader::initLoaders() +{ + _loaderList.append(createCachegrindLoader()); + //_loaderList.append(GProfLoader::createLoader()); +} + +void Loader::deleteLoaders() +{ + _loaderList.setAutoDelete(true); + _loaderList.clear(); +} + + +#include "loader.moc" diff --git a/kcachegrind/kcachegrind/loader.h b/kcachegrind/kcachegrind/loader.h new file mode 100644 index 00000000..70b98c48 --- /dev/null +++ b/kcachegrind/kcachegrind/loader.h @@ -0,0 +1,79 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Base class for loaders of profiling data. + */ + +#ifndef LOADER_H +#define LOADER_H + +#include <qobject.h> +#include <qptrlist.h> +#include <qstring.h> + +class QFile; +class TraceData; +class TracePart; +class Loader; + + +typedef QPtrList<Loader> LoaderList; + +/** + * To implement a new loader, inherit from the Loader class + * and implement canLoadTrace(), loadTrace() and if a trace in + * this format can consist out of multiple parts, implement + * isPartOfTrace(), too. + * For registration, put into the static initLoaders() function + * of this base class a _loaderList.append(new MyLoader()). + * + * KCachegrind will use the first matching loader. + */ + +class Loader: public QObject +{ + Q_OBJECT + +public: + Loader(QString name, QString desc); + virtual ~Loader(); + + virtual bool canLoadTrace(QFile* file); + virtual bool loadTrace(TracePart*); + + static Loader* matchingLoader(QFile* file); + static Loader* loader(QString name); + static void initLoaders(); + static void deleteLoaders(); + + QString name() const { return _name; } + QString description() const { return _description; } + +signals: + void updateStatus(QString, int); + +private: + QString _name, _description; + + static LoaderList _loaderList; +}; + + + +#endif diff --git a/kcachegrind/kcachegrind/main.cpp b/kcachegrind/kcachegrind/main.cpp new file mode 100644 index 00000000..299d485a --- /dev/null +++ b/kcachegrind/kcachegrind/main.cpp @@ -0,0 +1,95 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * KCachegrind startup + */ + +// for KCACHEGRIND_VERSION +#include "../version.h" + +#include <qfile.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kaboutdata.h> +#include <klocale.h> + +#include "toplevel.h" +#include "tracedata.h" +#include "loader.h" + +static KCmdLineOptions options[] = +{ + { "r <exec>", I18N_NOOP("Run <exec> under cachegrind"), 0 }, + { "+[trace]", I18N_NOOP("Show information of this trace"), 0 }, + KCmdLineLastOption // End of options. +}; + +int main( int argc, char ** argv ) +{ + KAboutData aboutData("kcachegrind", + I18N_NOOP("KCachegrind"), + KCACHEGRIND_VERSION, + I18N_NOOP("KDE Frontend for Cachegrind"), + KAboutData::License_GPL, + I18N_NOOP("(C) 2002, 2003, 2004"), 0, + "http://kcachegrind.sf.net"); + aboutData.addAuthor("Josef Weidendorfer", + I18N_NOOP("Author/Maintainer"), + "Josef.Weidendorfer@gmx.de"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication a; + TopLevel* t; + Loader::initLoaders(); + + if (a.isRestored()){ + int n = 1; + while (KMainWindow::canBeRestored(n)){ + (new TopLevel())->restore(n); + n++; + } + } + else { + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if (args->count()>0) { + for(int i = 0; i < args->count(); i++) { + t = new TopLevel(); + t->show(); + t->loadDelayed(QFile::decodeName(args->arg(i))); + } + } + else { + // load trace in current dir + t = new TopLevel(); + t->show(); + t->loadDelayed("."); + } + } + + a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) ); + int res = a.exec(); + + // to make leak checking in valgrind happy... + Loader::deleteLoaders(); + TraceItem::cleanup(); + + return res; +} diff --git a/kcachegrind/kcachegrind/multiview.cpp b/kcachegrind/kcachegrind/multiview.cpp new file mode 100644 index 00000000..09dfcd85 --- /dev/null +++ b/kcachegrind/kcachegrind/multiview.cpp @@ -0,0 +1,224 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * MultiView, enclosing multiple TabView's with a user choosable + * active view (i.e. focus), separated by a splitter. + * Selection of the active view is shown in the next to the right view + * (with wrap around). + */ + +#include <qobjectlist.h> +#include <kconfig.h> +#include <kdebug.h> + +#include "multiview.h" +#include "tabview.h" + +// +// MultiView +// + +MultiView::MultiView(TopLevel* top, QWidget* parent, const char* name) + : QSplitter(parent, name), TraceItemView(0, top) +{ + // default + setOrientation(Qt::Horizontal); + + appendView(); + _active = _views.first(); + _active->setActive(true); +} + +void MultiView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + TabView* tv; + for(tv=_views.first(); tv; tv=_views.next()) + tv->setData(d); +} + +void MultiView::setChildCount(int n) +{ + while(n< (int)_views.count()) removeView(); + while(n> (int)_views.count()) appendView(); +} + +void MultiView::appendView() +{ + int n = _views.count()+1; + + TabView* tv = new TabView(this, this, + QString("TabView-%1").arg(n).ascii()); + connect(tv, SIGNAL(activated(TabView*)), + this, SLOT(tabActivated(TabView*)) ); + _views.append(tv); + tv->show(); + + // set same attributes as in active view + tv->set(0, _data, _costType, _costType2, + _groupType, _partList, _activeItem, 0); + tv->updateView(); + + if (0) kdDebug() << "MultiView::appendView, now " + << _views.count() << endl; +} + +void MultiView::removeView() +{ + if (_views.count()<=1) return; + + TabView* last = _views.last(); + + // if last tab is active, make first active + if (last == _active) { + TabView* newActive = _views.first(); + newActive->setActive(true); + tabActivated(newActive); + } + + _views.removeRef(last); + delete last; + + if (0) kdDebug() << "MultiView::removeView, now " + << _views.count() << endl; +} + + +void MultiView::tabActivated(TabView* newActiveTab) +{ + if (_active == newActiveTab) return; + + if (0) kdDebug() << "MultiView::tabActivated " + << newActiveTab->name() << endl; + + TraceItem* oldActiveItem = 0; + if (_active) { + oldActiveItem = _active->activeItem(); + _active->setActive(false); + } + _active = newActiveTab; + + // make the active item of the new TabView active + if (_active && (oldActiveItem != _active->activeItem())) + TraceItemView::activated(_active->activeItem()); +} + +void MultiView::selected(TraceItemView* sender, TraceItem* i) +{ + if (0) kdDebug() << "MultiView::selected " << i->name() + << ", sender " << sender->widget()->name() << endl; + + // we react only on selection changes of the active TabView + if (sender != (TraceItemView*)_active) return; + + _views.findRef(_active); + TabView* next = _views.next(); + if (!next) next = _views.first(); + + // don't change item of active tab + if (next == _active) return; + + next->activate(i); + next->updateView(); +} + +void MultiView::activated(TraceItemView* sender, TraceItem* i) +{ + if (0) kdDebug() << "MultiView::activated " << i->name() + << ", sender " << sender->widget()->name() << endl; + + // we react only on selection changes of the active TabView + if (sender != (TraceItemView*)_active) return; + + TraceItemView::activated(sender,i); +} + +void MultiView::doUpdate(int changeType) +{ + TabView* tv; + for(tv=_views.first(); tv; tv=_views.next()) { + tv->set(changeType, _data, _costType, _costType2, + _groupType, _partList, + (tv == _active) ? _activeItem : tv->activeItem(), + tv->selectedItem()); + tv->notifyChange(changeType); + if (tv->isViewVisible()) + tv->updateView(); + } +} + + +void MultiView::readViewConfig(KConfig* c, + QString prefix, QString postfix, + bool withOptions) +{ + if (0) qDebug("%s::readConfig(%s%s)", name(), + prefix.ascii(), postfix.ascii()); + + QString active; + KConfigGroup* g = configGroup(c, prefix, postfix); + int n = g->readNumEntry("Panels", 1); + setChildCount(n); + setOrientation( (g->readEntry("Orientation") == QString("Horizontal")) ? + Qt::Horizontal : Qt::Vertical ); + + setSizes(g->readIntListEntry("PanelSizes")); + + active = g->readEntry("ActivePanel", ""); + delete g; + + TabView* tv, *activeTV = 0; + for(tv=_views.first();tv;tv=_views.next()) { + if (tv->name() == active) activeTV=tv; + tv->readViewConfig(c, QString("%1-%2").arg(prefix).arg(tv->name()), + postfix, withOptions); + } + + // activate panel after restoring + if (!activeTV) activeTV = _views.first(); + + if (_active == activeTV) + TraceItemView::activated(_active->activeItem()); + else + activeTV->setActive(true); +} + +void MultiView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, + bool withOptions) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + g.writeEntry("Panels", childCount()); + g.writeEntry("Orientation", + (orientation() == Qt::Horizontal) ? + "Horizontal" : "Vertical"); + + g.writeEntry("PanelSizes", sizes()); + g.writeEntry("ActivePanel", _active ? _active->name() : "none"); + + TabView* tv; + for(tv=_views.first();tv;tv=_views.next()) + tv->saveViewConfig(c, QString("%1-%2").arg(prefix).arg(tv->name()), + postfix, withOptions); +} + + +#include "multiview.moc" diff --git a/kcachegrind/kcachegrind/multiview.h b/kcachegrind/kcachegrind/multiview.h new file mode 100644 index 00000000..d4c56b19 --- /dev/null +++ b/kcachegrind/kcachegrind/multiview.h @@ -0,0 +1,66 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * MultiView, enclosing multiple (default: 2) TabView's with a user + * choosable active view (i.e. focus). This is a splitter itself. + * Selection of the active view is shown in the next to the right view + * (with wrap around). + */ + +#ifndef MULTIVIEW_H +#define MULTIVIEW_H + +#include <qsplitter.h> +#include <qptrlist.h> +#include "traceitemview.h" +#include "tabview.h" // because of QPtrList<TabView> + +class MultiView : public QSplitter, public TraceItemView +{ + Q_OBJECT + +public: + MultiView(TopLevel* top, QWidget* parent = 0, const char* name = 0); + + QWidget* widget() { return this; } + TabView* activeTabView() const { return _active; } + void setData(TraceData*); + + void appendView(); + void removeView(); + void setChildCount(int); + int childCount() { return _views.count(); } + + void selected(TraceItemView*, TraceItem*); + void activated(TraceItemView*, TraceItem*); + + void readViewConfig(KConfig*, QString prefix, QString postfix, bool); + void saveViewConfig(KConfig*, QString prefix, QString postfix, bool); + +public slots: + void tabActivated(TabView*); + + private: + void doUpdate(int); + + TabView* _active; + QPtrList<TabView> _views; +}; + +#endif diff --git a/kcachegrind/kcachegrind/partgraph.cpp b/kcachegrind/kcachegrind/partgraph.cpp new file mode 100644 index 00000000..f4fa72a3 --- /dev/null +++ b/kcachegrind/kcachegrind/partgraph.cpp @@ -0,0 +1,534 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * TracePart as Nested Area + */ + +#include <klocale.h> + +#include "partgraph.h" +#include "configuration.h" +#include "listutils.h" + + +// PartAreaWidget + +PartAreaWidget::PartAreaWidget(QWidget* parent, const char* name) + : TreeMapWidget(new BasePartItem(), parent, name) +{ + _data = 0; + _function = 0; + + _costType = 0; + _groupType = TraceCost::NoCostType; + _visualisation = NoVisualisation; + _zoomFunction = false; + _callLevels = 1; +} + +void PartAreaWidget::setData(TraceData* data) +{ + if (data == _data) return; + + _data = data; + _function = 0; + _hiddenParts.clear(); + + ((BasePartItem*)base())->setData(data); +} + +void PartAreaWidget::changeHidden(const TracePartList& list) +{ + _hiddenParts = list; + base()->refresh(); +} + + +void PartAreaWidget::setCostType(TraceCostType* ct) +{ + _costType = ct; + + // this resizes items + base()->redraw(); +} + +void PartAreaWidget::setVisualisation(VisualisationMode m) +{ + _visualisation = m; + refreshParts(); +} + +void PartAreaWidget::setZoomFunction(bool zoomFunction) +{ + _zoomFunction = zoomFunction; + refreshParts(); +} + +void PartAreaWidget::setCallLevels(int callLevels) +{ + _callLevels = callLevels; + refreshParts(); +} + +void PartAreaWidget::refreshParts() +{ + // rebuild only subparts to keep part selection state + TreeMapItem* i; + TreeMapItemList* l = base()->children(); + if (l) + for (i=l->first();i;i=l->next()) + i->refresh(); + + // but resize part areas + base()->redraw(); +} + + +void PartAreaWidget::setFunction(TraceFunction* f) +{ + _function = f; + + if (_visualisation == PartAreaWidget::Inclusive) + refreshParts(); +} + +void PartAreaWidget::setGroupType(TraceCost::CostType gt) +{ + _groupType = gt; + + // rebuild hierarchy below parts. + // thus, selected parts stay selected + TreeMapItem* i; + TreeMapItemList* l = base()->children(); + if (l) + for (i=l->first();i;i=l->next()) + i->refresh(); + + base()->redraw(); +} + +bool PartAreaWidget::isHidden(TracePart* part) const +{ + return (_hiddenParts.containsRef(part)>0); +} + +QColor PartAreaWidget::groupColor(TraceFunction* f) const +{ + if (!f) + return colorGroup().button(); + + return Configuration::functionColor(_groupType, f); +} + +QString PartAreaWidget::tipString(TreeMapItem* i) const +{ + QString tip, itemTip; + int count = 0; + + //qDebug("PartAreaWidget::tipString for '%s'", i->name().ascii()); + + // first, SubPartItem's + while (i && count<Configuration::maxSymbolCount() && i->rtti() == 3) { + itemTip = i->text(0); + if ((int)itemTip.length()>Configuration::maxSymbolLength()) + itemTip = itemTip.left(Configuration::maxSymbolLength()) + "..."; + + if (!i->text(1).isEmpty()) + itemTip += " (" + i->text(1) + ")"; + + if (!tip.isEmpty()) + itemTip += "\n"; + + tip = itemTip + tip; + i = i->parent(); + count++; + } + + // skip to part + while (i && i->rtti()==3) i = i->parent(); + + if (i && i->rtti()==2) { + itemTip = i18n("Profile Part %1").arg(i->text(0)); + if (!i->text(1).isEmpty()) + itemTip += " (" + i->text(1) + ")"; + + if (!tip.isEmpty()) + itemTip += "\n"; + + tip = itemTip + tip; + } + +// qDebug("PartAreaWidget:: tip %s, itemTip %s", +// tip.ascii(), itemTip.ascii()); + + return tip; +} + + + + + +// BasePartItem + +BasePartItem::BasePartItem() + : TreeMapItem() +{ + _data = 0; + setSorting(-1); +} + +void BasePartItem::setData(TraceData* data) +{ + if (data == _data) return; + + _data = data; + refresh(); +} + +TreeMapItemList* BasePartItem::children() +{ + if (!_data) return _children; + + if (!initialized()) { +// qDebug("Create Parts (%s)", name().ascii()); + + PartAreaWidget* w = (PartAreaWidget*) widget(); + TracePart* part; + TracePartList l = _data->parts(); + for (part=l.first();part;part=l.next()) + if (!w->isHidden(part)) + addItem(new PartItem(part)); + } + + return _children; +} + +QString BasePartItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_data) + return i18n("(no trace)"); + + if (_data->parts().count() == 0) + return i18n("(no part)"); + } + return QString::null; +} + + +QColor BasePartItem::backColor() const +{ + return widget()->colorGroup().base(); +} + +double BasePartItem::value() const +{ + if (!_data) return 0; + + PartAreaWidget* w = (PartAreaWidget*) widget(); + return (double)_data->subCost(w->costType()); +} + + + + + +// PartItem + +PartItem::PartItem(TracePart* p) +{ + _p = p; + _factor=1; +} + +QString PartItem::text(int textNo) const +{ + if (textNo == 0) + return _p->prettyName(); + + if (textNo != 1) + return QString::null; + + TraceCostType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + SubCost v; + + ct = w->costType(); + v = _p->subCost(ct); + + if (Configuration::showPercentage()) { + TraceCost* t = _p->data()->totals(); + double p = 100.0 * v / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', Configuration::percentPrecision()); + } + return v.pretty(); +} + + +QPixmap PartItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + + TraceCostType* ct = ((PartAreaWidget*)widget())->costType(); + return costPixmap( ct, _p, (double) (_p->data()->totals()->subCost(ct)), false ); +} + + +double PartItem::value() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + TraceCostType* ct = w->costType(); + if ((w->visualisation() == PartAreaWidget::Inclusive) && + w->zoomFunction()) { + + // use value of zoomed function + TraceFunction* f = w->function(); + if (f) { + TracePartFunction* pf = (TracePartFunction*) f->findDepFromPart(_p); + if (pf) + return (double) pf->inclusive()->subCost(ct); + // when function is not available in part, hide part + return 0.0; + } + } + return (double) _p->subCost(ct); +} + +double PartItem::sum() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualisation() == PartAreaWidget::Inclusive) { + double s = value(); + //qDebug("PartItem::sum [part %s]: %d", _p->name().ascii(), s); + return s; + } + return 0.0; +} + +TreeMapItemList* PartItem::children() +{ + if (initialized()) return _children; + + TraceCost* c; +// qDebug("Create Part subitems (%s)", name().ascii()); + + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualisation() == PartAreaWidget::Inclusive) { + TraceFunction* f = w->function(); + if (f) { + c = f->findDepFromPart(_p); + if (c) addItem(new SubPartItem(c)); + } + + return _children; + } + + + switch( ((PartAreaWidget*)widget())->groupType() ) { + + case TraceCost::Object: + { + TraceObjectMap::Iterator it; + for ( it = _p->data()->objectMap().begin(); + it != _p->data()->objectMap().end(); ++it ) { + c = (*it).findDepFromPart(_p); + if (c) + addItem(new SubPartItem(c)); + } + } + break; + + case TraceCost::Class: + { + TraceClassMap::Iterator it; + for ( it = _p->data()->classMap().begin(); + it != _p->data()->classMap().end(); ++it ) { + c = (*it).findDepFromPart(_p); + if (c) + addItem(new SubPartItem(c)); + } + } + break; + + case TraceCost::File: + { + TraceFileMap::Iterator it; + for ( it = _p->data()->fileMap().begin(); + it != _p->data()->fileMap().end(); ++it ) { + c = (*it).findDepFromPart(_p); + if (c) + addItem(new SubPartItem(c)); + } + } + break; + + case TraceCost::Function: + { + TraceFunctionMap::Iterator it; + for ( it = _p->data()->functionMap().begin(); + it != _p->data()->functionMap().end(); ++it ) { + c = (*it).findDepFromPart(_p); + if (c) + addItem(new SubPartItem(c)); + } + } + break; + + default: + break; + } + + return _children; +} + + +QColor PartItem::backColor() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + return w->groupColor(0); +} + + +// SubPartItem + +SubPartItem::SubPartItem(TraceCost* c) +{ + _partCostItem = c; + _factor=1; +} + +QString SubPartItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_partCostItem) + return i18n("(unknown)"); + + return _partCostItem->dependant()->prettyName(); + } + + if (textNo != 1) + return QString::null; + + TraceCostType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + SubCost v; + + ct = w->costType(); + if (w->visualisation() == PartAreaWidget::Inclusive) + v = ((TracePartFunction*)_partCostItem)->inclusive()->subCost(ct); + else + v = _partCostItem->subCost(ct); + + if (Configuration::showPercentage()) { + TraceCost* t = Configuration::showExpanded() ? + _partCostItem->part() : _partCostItem->part()->data()->totals(); + double p = 100.0 * v / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', Configuration::percentPrecision()); + } + return v.pretty(); +} + +QPixmap SubPartItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + + PartAreaWidget* w = (PartAreaWidget*)widget(); + TraceCostType* ct = w->costType(); + TraceCost* t = Configuration::showExpanded() ? + _partCostItem->part() : _partCostItem->part()->data()->totals(); + TraceCost* c; + if (w->visualisation() == PartAreaWidget::Inclusive) + c = ((TracePartFunction*)_partCostItem)->inclusive(); + else + c = _partCostItem; + + return costPixmap( ct, c, (double) (t->subCost(ct)), false ); +} + +double SubPartItem::value() const +{ + TraceCostType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + + ct = w->costType(); + if (w->visualisation() == PartAreaWidget::Inclusive) + return (double) + ((TracePartFunction*)_partCostItem)->inclusive()->subCost(ct); + + return (double) _partCostItem->subCost(ct); +} + +double SubPartItem::sum() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualisation() == PartAreaWidget::Inclusive) { + double s = value(); + //qDebug("SubPartItem::sum [Cost %s]: %d", _cost->name().ascii(), s); + return s; + } + return 0.0; +} + +TreeMapItemList* SubPartItem::children() +{ + if (!initialized()) { +// qDebug("Create Part sub-subitems (%s)", name().ascii()); + + PartAreaWidget* w = (PartAreaWidget*)widget(); + + if (depth()-2 > w->callLevels()) + return _children; + + if (w->visualisation() == PartAreaWidget::Inclusive) { + TracePartCall* call; + TracePartCallList l; + + setSum(value()); + + l = ((TracePartFunction*)_partCostItem)->partCallings(); + for (call=l.first();call;call=l.next()) { + TraceFunction* called = call->call()->called(); + TraceCost* partCalled = called->findDepFromPart(call->part()); + if (partCalled) + addItem(new SubPartItem(partCalled)); + } + } + } + + return _children; +} + + +QColor SubPartItem::backColor() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualisation() == PartAreaWidget::Inclusive) + return w->groupColor((TraceFunction*)(_partCostItem->dependant())); + + return Configuration::groupColor(_partCostItem->dependant()); +} + + +#include "partgraph.moc" diff --git a/kcachegrind/kcachegrind/partgraph.h b/kcachegrind/kcachegrind/partgraph.h new file mode 100644 index 00000000..d4d5b121 --- /dev/null +++ b/kcachegrind/kcachegrind/partgraph.h @@ -0,0 +1,131 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * TracePart Graph + */ + +#ifndef PARTGRAPH_H +#define PARTGRAPH_H + +#include "treemap.h" +#include "tracedata.h" + +class PartAreaWidget: public TreeMapWidget +{ + Q_OBJECT + +public: + // Visualisation inside of trace parts + enum VisualisationMode { NoVisualisation, Partitioning, Inclusive }; + + PartAreaWidget(QWidget* parent=0, const char* name=0); + + void setData(TraceData* d); + void setCostType(TraceCostType* ct); + void setGroupType(TraceCost::CostType gt); + void setVisualisation(VisualisationMode); + void setZoomFunction(bool zoomFunction); + void setCallLevels(int callLevels); + void setFunction(TraceFunction* f); + + TraceCostType* costType() const { return _costType; } + TraceCost::CostType groupType() const { return _groupType; } + TraceFunction* function() const { return _function; } + VisualisationMode visualisation() const { return _visualisation; } + bool zoomFunction() const { return _zoomFunction; } + int callLevels() const { return _callLevels; } + + QColor groupColor(TraceFunction*) const; + QString tipString(TreeMapItem*) const; + + void changeHidden(const TracePartList& list); + bool isHidden(TracePart*) const; + +private: + void refreshParts(); + + TraceData* _data; + TraceCostType* _costType; + TraceCost::CostType _groupType; + TraceFunction* _function; + VisualisationMode _visualisation; + bool _zoomFunction; + int _callLevels; + + TracePartList _hiddenParts; +}; + +class BasePartItem: public TreeMapItem +{ +public: + BasePartItem(); + + void setData(TraceData* d); + + int rtti() const { return 1; } + double value() const; + QString text(int) const; + int borderWidth() const { return 0; } + TreeMapItemList* children(); + QColor backColor() const; + +private: + TraceData* _data; +}; + +class PartItem: public TreeMapItem +{ +public: + PartItem(TracePart* p); + int rtti() const { return 2; } + TracePart* part() { return _p; } + double value() const; + double sum() const; + int borderWidth() const { return 0; } + QString text(int) const; + QPixmap pixmap(int) const; + TreeMapItemList* children(); + QColor backColor() const; + +private: + TracePart* _p; + unsigned int _factor; +}; + +class SubPartItem: public TreeMapItem +{ +public: + SubPartItem(TraceCost*); + int rtti() const { return 3; } + TraceCost* partCostItem() { return _partCostItem; } + double value() const; + double sum() const; + SplitMode splitMode() const { return Vertical; } + QString text(int) const; + QPixmap pixmap(int) const; + TreeMapItemList* children(); + QColor backColor() const; + +private: + TraceCost* _partCostItem; + unsigned int _factor; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/partlistitem.cpp b/kcachegrind/kcachegrind/partlistitem.cpp new file mode 100644 index 00000000..4f60cf1e --- /dev/null +++ b/kcachegrind/kcachegrind/partlistitem.cpp @@ -0,0 +1,189 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <math.h> + +#include <qpainter.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kapplication.h> + +#include "listutils.h" +#include "partlistitem.h" +#include "coverage.h" +#include "configuration.h" + + +// PartListItem + +PartListItem::PartListItem(QListView* parent, TraceCostItem* costItem, + TraceCostType* ct, TraceCost::CostType gt, + TracePart* part) + :QListViewItem(parent) +{ + _partCostItem = costItem->findDepFromPart(part); + _part = part; + _groupType = gt; + _costType = ct; + +#if 0 + QString partName = QString::number(part->partNumber()); + if (part->data()->maxThreadID() >1) + partName += i18n(" (Thread %1)").arg(part->threadID()); + setText(0, partName); +#else + setText(0, _part->prettyName()); +#endif + + if (_part->trigger().isEmpty()) + setText(4,i18n("(none)")); + else + setText(4, _part->trigger()); + + update(); +} + +void PartListItem::setCostType(TraceCostType* ct) +{ + if (_costType == ct) return; + + _costType = ct; + update(); +} + +void PartListItem::setGroupType(TraceCost::CostType gt) +{ + if (_groupType == gt) return; + + _groupType = gt; + update(); +} + +void PartListItem::update() +{ + TracePartFunction* pf; + pf = !_partCostItem ? 0 : + (_partCostItem->type()==TraceCost::PartFunction) ? + ((TracePartFunction*)_partCostItem) : 0; + + double total = _part->subCost(_costType); + + TraceCost* selfTotalCost = _part; + if (pf && Configuration::showExpanded()) { + switch(_groupType) { + case TraceCost::Object: selfTotalCost = pf->partObject(); break; + case TraceCost::Class: selfTotalCost = pf->partClass(); break; + case TraceCost::File: selfTotalCost = pf->partFile(); break; + default: break; + } + } + double selfTotal = selfTotalCost->subCost(_costType); + + _pure = _partCostItem ? _partCostItem->subCost(_costType) : SubCost(0); + _sum = pf ? pf->inclusive()->subCost(_costType) : SubCost(0); + + if (selfTotal == 0 || !_partCostItem) { + setText(2, QString("-")); + setPixmap(2, QPixmap()); + } + else { + double pure = 100.0 * _pure / selfTotal; + if (Configuration::showPercentage()) { + setText(2, QString("%1") + .arg(pure, 0, 'f', Configuration::percentPrecision())); + } + else + setText(2, _partCostItem->prettySubCost(_costType)); + + setPixmap(2, costPixmap(_costType, _partCostItem, selfTotal, false)); + } + + if (total == 0 || !pf) { + setText(1, QString("-")); + setPixmap(1, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + if (Configuration::showPercentage()) { + setText(1, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + } + else + setText(1, _sum.pretty()); + + setPixmap(1, costPixmap(_costType, pf->inclusive(), total, false)); + } + + if (!pf) { + setText(3, QString("-")); + _callers = 0; + return; + } + + TracePartCall* pc; + TracePartCallList pl; + SubCost callers, callees; + QString str; + + callers = 0; + pl = pf->partCallers(); + for (pc=pl.first();pc;pc=pl.next()) { + callers += pc->callCount(); + } + + if ((callers == 0) && (pf->calledContexts()>0)) + str = i18n("(active)"); + else + str = callers.pretty(); + + _callers = callers; + setText(3, str); +} + + +int PartListItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + PartListItem* fi = (PartListItem*) i; + if (col==0) { + int mTID = _part->data()->maxThreadID()+1; + int mNum = _part->data()->maxPartNumber()+1; + + return + (_part->processID() - fi->_part->processID()) * mTID * mNum + + (_part->partNumber() - fi->_part->partNumber()) * mTID + + (_part->threadID() - fi->_part->threadID()); + } + if (col==1) { + if (_sum < fi->_sum) return -1; + if (_sum > fi->_sum) return 1; + return 0; + } + if (col==2) { + if (_pure < fi->_pure) return -1; + if (_pure > fi->_pure) return 1; + return 0; + } + if (col==3) { + if (_callers < fi->_callers) return -1; + if (_callers > fi->_callers) return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} diff --git a/kcachegrind/kcachegrind/partlistitem.h b/kcachegrind/kcachegrind/partlistitem.h new file mode 100644 index 00000000..98a674fe --- /dev/null +++ b/kcachegrind/kcachegrind/partlistitem.h @@ -0,0 +1,54 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef PARTLISTITEM_H +#define PARTLISTITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +/** + * For info tab, trace part list. + * Needs update on + * - cost type change + * + * Note: on a cost item / percentage change, the list is rebuild + */ +class PartListItem: public QListViewItem +{ +public: + PartListItem(QListView* parent, TraceCostItem* costItem, + TraceCostType* ct, TraceCost::CostType gt, TracePart* part); + + int compare(QListViewItem * i, int col, bool ascending ) const; + TraceCost* partCostItem() { return _partCostItem; } + void setCostType(TraceCostType* ct); + void setGroupType(TraceCost::CostType); + TracePart* part() { return _part; } + void update(); + +private: + SubCost _sum, _pure; + SubCost _callers; + TraceCostType* _costType; + TraceCost* _partCostItem; + TracePart* _part; + TraceCost::CostType _groupType; +}; + +#endif diff --git a/kcachegrind/kcachegrind/partselection.cpp b/kcachegrind/kcachegrind/partselection.cpp new file mode 100644 index 00000000..85a46c35 --- /dev/null +++ b/kcachegrind/kcachegrind/partselection.cpp @@ -0,0 +1,567 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * For part file selection, to be put into a QDockWindow + */ + +#include <qtimer.h> +#include <qlistview.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcombobox.h> +#include <qlineedit.h> +#include <qpopupmenu.h> +#include <qlayout.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kdebug.h> + +#include "partselection.h" +#include "partgraph.h" + +PartSelection::PartSelection( QWidget* parent, const char* name) + : PartSelectionBase(parent, name) +{ + _data = 0; + _costType = 0; + _costType2 = 0; + _groupType = TraceItem::NoCostType; + _group = 0; + _function = 0; + _inSelectionUpdate = false; + + _diagramMode = false; + _drawFrames = true; + + partAreaWidget->setAllowRotation(false); + partAreaWidget->setMaxSelectDepth(2); + partAreaWidget->setSelectionMode(TreeMapWidget::Extended); + partAreaWidget->setSplitMode(TreeMapItem::HAlternate); + partAreaWidget->setVisibleWidth(2, true); + partAreaWidget->setFieldType(0, i18n( "Name" )); + partAreaWidget->setFieldType(1, i18n( "Cost" )); + + connect(partAreaWidget, SIGNAL(selectionChanged()), + this, SLOT(selectionChanged())); + connect(partAreaWidget, SIGNAL(currentChanged(TreeMapItem*, bool)), + this, SLOT(currentChangedSlot(TreeMapItem*, bool))); + connect(partAreaWidget, SIGNAL(doubleClicked(TreeMapItem*)), + this, SLOT(doubleClicked(TreeMapItem*))); + connect(partAreaWidget, + SIGNAL(contextMenuRequested(TreeMapItem*,const QPoint &)), + this, + SLOT(contextMenuRequested(TreeMapItem*,const QPoint &))); + + _showInfo = true; + showInfo(false); +} + +PartSelection::~PartSelection() +{ +} + +void PartSelection::setData(TraceData* data) +{ + if (_data == data) return; + + _data = data; + partAreaWidget->setData(data); + fillInfo(); +} + + +void PartSelection::refresh() +{ + partAreaWidget->redraw(); + fillInfo(); +} + +void PartSelection::setCostType(TraceCostType* ct) +{ + if (ct == _costType) return; + _costType = ct; + + partAreaWidget->setCostType(ct); +} + +void PartSelection::setCostType2(TraceCostType* ct) +{ + if (ct == _costType2) return; + _costType2 = ct; + if (!_diagramMode) return; + + //TODO: get max cost(type1)/cost(type2) of shown parts + //partAreaWidget->setCostType(ct); +} + +void PartSelection::setGroupType(TraceItem::CostType gt) +{ + if (gt == _groupType) return; + _groupType = gt; + + partAreaWidget->setGroupType(gt); +} + +void PartSelection::setGroup(TraceCostItem*) +{ +} + +void PartSelection::setFunction(TraceFunction* f) +{ + if (_function == f) return; + _function = f; + + //kdDebug() << "PartSelection::setFunction " << f->name() << endl; + + // FIXME: The TreeMap shouldn't produce spurious selectionChanged events + _inSelectionUpdate = true; + partAreaWidget->setFunction(_function); + _inSelectionUpdate = false; +} + +void PartSelection::setPart(TracePart*) +{} + +void PartSelection::currentChangedSlot(TreeMapItem* i, bool kbd) +{ + if (!i) return; + if (!kbd) return; + if (i->text(0).isEmpty()) return; + + QString str = i->text(0); + if (!i->text(1).isEmpty()) + str += " (" + i->text(1) + ")"; + QString msg = i18n("Profile Part Overview: Current is '%1'").arg(str); + emit showMessage(msg, 5000); + + if (_showInfo) fillInfo(); +} + + +void PartSelection::doubleClicked(TreeMapItem* i) +{ + if (!i || i->rtti() != 3) return; + + TraceCost* c = ((SubPartItem*) i)->partCostItem(); + TraceCostItem* ci = 0; + + switch(c->type()) { + case TraceItem::PartFunction: + { + TraceFunction* f = ((TracePartFunction*)c)->function(); + if (f) + emit functionChanged(f); + } + return; + + case TraceItem::PartObject: + ci = ((TracePartObject*)c)->object(); + break; + case TraceItem::PartClass: + ci = ((TracePartClass*)c)->cls(); + break; + case TraceItem::PartFile: + ci = ((TracePartFile*)c)->file(); + break; + default: + break; + } + + if (ci) + emit groupChanged(ci); +} + + +void PartSelection::selectionChanged() +{ + if (_inSelectionUpdate) return; + + kdDebug() << "PartSelection::selectionChanged" << endl; + + bool something_changed = false; + bool nothingSelected = true; + + TracePartList pList; + TreeMapItem* i; + TracePart* part; + + // if nothing is selected, activate all parts + TreeMapItemList* list = partAreaWidget->base()->children(); + if (!list) return; + + for (i=list->first();i;i=list->next()) + if (partAreaWidget->isSelected(i)) { + nothingSelected = false; + break; + } + + for (i=list->first();i;i=list->next()) { + part = ((PartItem*)i)->part(); + bool active = nothingSelected || partAreaWidget->isSelected(i); + if (active) { + pList.append(part); + something_changed = true; + } + } + + if (something_changed) { + //qDebug("PartSelection: Something changed."); + emit activePartsChanged(pList); + } +} + +/* this makes the graph selection the same to the parts in the list */ +void PartSelection::activePartsChangedSlot(const TracePartList& list) +{ + _inSelectionUpdate = true; + + kdDebug() << "Entering PartSelection::activePartsChangedSlot" << endl; + + TreeMapItem* i; + TreeMapItemList l = *partAreaWidget->base()->children(); + // first deselect inactive, then select active (makes current active) + for (i=l.first();i;i=l.next()) { + TracePart* part = ((PartItem*)i)->part(); + bool active = (list.containsRef(part)>0); + if (!active && partAreaWidget->isSelected(i)) { +#if 0 + qDebug("PartSelection::partsChangedSlot: Part %s changed to unselected.", + ((PartItem*)i)->part()->shortName().ascii()); +#endif + + partAreaWidget->setSelected(i, false); + } + } + for (i=l.first();i;i=l.next()) { + TracePart* part = ((PartItem*)i)->part(); + bool active = (list.containsRef(part)>0); + if (active && !partAreaWidget->isSelected(i)) { +#if 0 + qDebug("PartSelection::partsChangedSlot: Part %s changed to selected.", + ((PartItem*)i)->part()->shortName().ascii()); +#endif + partAreaWidget->setSelected(i, true); + } + } + + _inSelectionUpdate = false; + + kdDebug() << "Leaving PartSelection::activePartsChangedSlot" << endl; + + fillInfo(); +} + +void PartSelection::contextMenuRequested(TreeMapItem* i, + const QPoint & p) +{ + if (!i) return; + + QPopupMenu popup; + QPopupMenu ppopup; + QPopupMenu vpopup; + + QString str; + TreeMapItem* s = 0; + + if (_data && (_data->parts().count()>1)) { + s = partAreaWidget->possibleSelection(i); + if (!s->text(0).isEmpty()) { + str = (partAreaWidget->isSelected(s)) ? + i18n("Deselect") : i18n("Select"); + str += " '" + s->text(0) + "'"; + popup.insertItem(str, 1); + } + + popup.insertItem(i18n("Select All Parts"), 2); + + popup.insertItem(i18n("Visible Parts"), &ppopup, 10); + + ppopup.insertItem(i18n("Hide Selected Parts"), 3); + ppopup.insertItem(i18n("Unhide Hidden Parts"), 4); + + popup.insertSeparator(); + } + + popup.insertItem(i18n("Go Back"), 99); + if (i->rtti() == 3) { + TreeMapItem* ni = i; + int id = 100; + while (ni && ni->rtti() == 3) { + TraceCost* c = ((SubPartItem*)ni)->partCostItem(); + if (c->type() == TraceItem::PartFunction) + if ( ((TracePartFunction*)c)->function() == _function) break; + + str = i18n("Select") + " '" + ni->text(0) + "'"; + popup.insertItem(str, id); + ni = ni->parent(); + id++; + } + } + popup.insertSeparator(); + + vpopup.setCheckable(true); + popup.insertItem(i18n("Visualization"), &vpopup, 10); + + vpopup.insertItem(i18n("Partitioning Mode"), 30); + vpopup.insertItem(i18n("Diagram Mode"), 34); + vpopup.insertItem(i18n("Zoom Function"), 31); + vpopup.insertItem(i18n("Show Direct Calls"), 32); + vpopup.insertItem(i18n("Increment Shown Call Levels"), 33); + if (partAreaWidget->visualisation() == PartAreaWidget::Partitioning) { + vpopup.setItemChecked(30, true); + vpopup.setItemEnabled(31, false); + vpopup.setItemEnabled(32, false); + vpopup.setItemEnabled(33, false); + } + else { + vpopup.setItemChecked(31, partAreaWidget->zoomFunction()); + } + vpopup.setItemChecked(34, _diagramMode); + + vpopup.insertSeparator(); + + vpopup.insertItem(i18n("Draw Names"), 20); + vpopup.insertItem(i18n("Draw Costs"), 21); + vpopup.insertItem(i18n("Ignore Proportions"), 22); + vpopup.insertItem(i18n("Draw Frames"), 24); + vpopup.insertItem(i18n("Allow Rotation"), 23); + if (!partAreaWidget->fieldVisible(0) && + !partAreaWidget->fieldVisible(1)) { + vpopup.setItemEnabled(22, false); + vpopup.setItemEnabled(23, false); + } + else { + vpopup.setItemChecked(20,partAreaWidget->fieldVisible(0)); + vpopup.setItemChecked(21,partAreaWidget->fieldVisible(1)); + vpopup.setItemChecked(22,partAreaWidget->fieldForced(0)); + vpopup.setItemChecked(23,partAreaWidget->allowRotation()); + vpopup.setItemChecked(24,_drawFrames); + } + + if (_showInfo) + popup.insertItem(i18n("Hide Info"), 40); + else + popup.insertItem(i18n("Show Info"), 41); + + int r = popup.exec(partAreaWidget->mapToGlobal(p)); + + if (r>=100) { + TreeMapItem* ci = i; + while (ci && r>100) { + ci = ci->parent(); + r--; + } + doubleClicked(ci); + return; + } + + switch(r) { + case 1: + // select/deselect part under mouse + partAreaWidget->setSelected(s, !partAreaWidget->isSelected(s)); + break; + + case 2: + // select all parts + { + TreeMapItemList list = *partAreaWidget->base()->children(); + partAreaWidget->setRangeSelection(list.first(), list.last(), true); + } + break; + + case 3: + emit partsHideSelected(); + break; + + case 4: + emit partsUnhideAll(); + break; + + case 99: + // last selected function + emit goBack(); + break; + + case 20: + partAreaWidget->setFieldVisible(0, !vpopup.isItemChecked(20)); + break; + + case 21: + partAreaWidget->setFieldVisible(1, !vpopup.isItemChecked(21)); + break; + + case 22: + partAreaWidget->setFieldForced(0, !vpopup.isItemChecked(22)); + partAreaWidget->setFieldForced(1, !vpopup.isItemChecked(22)); + break; + + case 23: partAreaWidget->setAllowRotation(!vpopup.isItemChecked(23)); break; + + case 24: + _drawFrames = !_drawFrames; + partAreaWidget->drawFrame(2,_drawFrames); + partAreaWidget->drawFrame(3,_drawFrames); + break; + + case 30: + partAreaWidget->setVisualisation(!vpopup.isItemChecked(30) ? + PartAreaWidget::Partitioning : + PartAreaWidget::Inclusive); + break; + + case 31: + // zoom/unzoom function + partAreaWidget->setZoomFunction(!vpopup.isItemChecked(31)); + break; + + case 32: + case 33: + // change call Levels + { + int l = (r==32) ? 1 : partAreaWidget->callLevels()+1; + partAreaWidget->setCallLevels(l); + } + break; + + case 34: + _diagramMode = !_diagramMode; + partAreaWidget->setTransparent(2,_diagramMode); + break; + + + case 40: + case 41: + showInfo(r==41); + break; + + default: + break; + } +} + +void PartSelection::hiddenPartsChangedSlot(const TracePartList& list) +{ + partAreaWidget->changeHidden(list); +} + +void PartSelection::readVisualisationConfig(KConfigGroup* config) +{ + bool enable; + + QString mode = config->readEntry("PartitionMode", "Inclusive"); + if (mode == "Inclusive") + partAreaWidget->setVisualisation(PartAreaWidget::Inclusive); + else + partAreaWidget->setVisualisation(PartAreaWidget::Partitioning); + + _diagramMode = config->readBoolEntry("DiagramMode", false); + partAreaWidget->setTransparent(2,_diagramMode); + + _drawFrames = config->readBoolEntry("DrawFrames", true); + partAreaWidget->drawFrame(2,_drawFrames); + partAreaWidget->drawFrame(3,_drawFrames); + + enable = config->readBoolEntry("GraphZoom", false); + partAreaWidget->setZoomFunction(enable); + + int levels = config->readNumEntry("GraphLevels", 1); + partAreaWidget->setCallLevels(levels); + + enable = config->readBoolEntry("GraphDrawName", true); + partAreaWidget->setFieldVisible(0, enable); + + enable = config->readBoolEntry("GraphDrawCost", true); + partAreaWidget->setFieldVisible(1, enable); + + enable = config->readBoolEntry("GraphForceStrings", false); + partAreaWidget->setFieldForced(0, enable); + partAreaWidget->setFieldForced(1, enable); + + enable = config->readBoolEntry("GraphAllowRotation", true); + partAreaWidget->setAllowRotation(enable); + + showInfo(config->readBoolEntry("ShowInfo", false)); +} + +void PartSelection::saveVisualisationConfig(KConfigGroup* config) +{ + QString mode; + if (partAreaWidget->visualisation() == PartAreaWidget::Inclusive) + mode = "Inclusive"; + else + mode = "Partitioning"; + config->writeEntry("PartitionMode", mode); + + config->writeEntry("DiagramMode", _diagramMode); + config->writeEntry("DrawFrames", _drawFrames); + + config->writeEntry("GraphZoom", partAreaWidget->zoomFunction()); + config->writeEntry("GraphLevels", partAreaWidget->callLevels()); + config->writeEntry("GraphDrawName", partAreaWidget->fieldVisible(0)); + config->writeEntry("GraphDrawCosts", partAreaWidget->fieldVisible(1)); + config->writeEntry("GraphForceStrings", partAreaWidget->fieldForced(0)); + config->writeEntry("GraphAllowRotation", partAreaWidget->allowRotation()); + + config->writeEntry("ShowInfo", _showInfo); +} + +void PartSelection::showInfo(bool enable) +{ + if (_showInfo == enable) return; + + _showInfo = enable; + if (enable) { + rangeLabel->show(); + fillInfo(); + } + else + rangeLabel->hide(); +} + +void PartSelection::fillInfo() +{ + if (!_data) { + rangeLabel->setText(i18n("(no trace loaded)")); + return; + } + + QString info = _data->activePartRange(); + + TreeMapItem* i = partAreaWidget->current(); + while (i && i->rtti()!=2) i = i->parent(); + if (i) { + TracePart* part = ((PartItem*)i)->part(); + + //if (!part->trigger().isEmpty()) info += ", " + part->trigger(); + if (!part->timeframe().isEmpty()) + info += ", Time " + part->timeframe() + " BBs"; + } + else { + TracePart* part = _data->parts().first(); + + if (part && !part->version().isEmpty()) + info += ", Cachegrind " + part->version(); + } + + + rangeLabel->setText(info); +} + +#include "partselection.moc" diff --git a/kcachegrind/kcachegrind/partselection.h b/kcachegrind/kcachegrind/partselection.h new file mode 100644 index 00000000..168446f1 --- /dev/null +++ b/kcachegrind/kcachegrind/partselection.h @@ -0,0 +1,95 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * PartSelection for KCachegrind + * For part file selection, to be put into a QDockWindow + */ + +#ifndef PARTSELECTION_H +#define PARTSELECTION_H + +#include <qobject.h> + +#include "partselectionbase.h" +#include "partgraph.h" +#include "tracedata.h" + +class KConfigGroup; +class TraceFunction; +class TraceData; +class TreeMapItem; + +class PartSelection: public PartSelectionBase +{ + Q_OBJECT + +public: + PartSelection( QWidget* parent = 0, const char* name = 0); + ~PartSelection(); + + TraceData* data() { return _data; } + void setData(TraceData*); + + PartAreaWidget* graph() { return partAreaWidget; } + + void readVisualisationConfig(KConfigGroup*); + void saveVisualisationConfig(KConfigGroup*); + +signals: + void activePartsChanged(const TracePartList& list); + void partsHideSelected(); + void partsUnhideAll(); + void groupChanged(TraceCostItem*); + void functionChanged(TraceItem*); + void showMessage(const QString&, int); + void goBack(); + +public slots: + void selectionChanged(); + void doubleClicked(TreeMapItem*); + void contextMenuRequested(TreeMapItem*, const QPoint &); + void currentChangedSlot(TreeMapItem*, bool); + + void setPart(TracePart*); + void setCostType(TraceCostType*); + void setCostType2(TraceCostType*); + void setGroupType(TraceItem::CostType); + void setGroup(TraceCostItem*); + void setFunction(TraceFunction*); + void activePartsChangedSlot(const TracePartList& list); + void hiddenPartsChangedSlot(const TracePartList& list); + void refresh(); + void showInfo(bool); + +private: + void fillInfo(); + + TraceData* _data; + TraceCostType *_costType, *_costType2; + TraceItem::CostType _groupType; + TraceCostItem* _group; + TraceFunction* _function; + bool _showInfo; + bool _diagramMode; + bool _drawFrames; + + bool _inSelectionUpdate; +}; + +#endif diff --git a/kcachegrind/kcachegrind/partselectionbase.ui b/kcachegrind/kcachegrind/partselectionbase.ui new file mode 100644 index 00000000..3267f49a --- /dev/null +++ b/kcachegrind/kcachegrind/partselectionbase.ui @@ -0,0 +1,89 @@ +<!DOCTYPE UI><UI version="3.0" stdsetdef="1"> +<class>PartSelectionBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>PartSelectionBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>460</width> + <height>402</height> + </rect> + </property> + <property name="caption"> + <string>Parts Overview</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>6</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="PartAreaWidget"> + <property name="name"> + <cstring>partAreaWidget</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>50</height> + </size> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>rangeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>(no trace parts)</string> + </property> + </widget> + </vbox> +</widget> +<customwidgets> + <customwidget> + <class>PartAreaWidget</class> + <header location="local">partgraph.h</header> + <sizehint> + <width>-1</width> + <height>-1</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>7</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="XPM.GZ" length="5230">789c9597db4e1d4b0e86eff31428be8b46b5fb54dd551acd051020211c4320c0682eecaa5e9ccf90005bf3ee53cbbfe9d9c9c548a38ea27c2977b5cbfe6dd7fae3c3c2e1cee6c2873fde3d3cf2e3595a48a77cbff0213f5d5dbdfcf35ffff8f3ddfba65998fff161a179ffb777ef771f17d2c2d6cdf53807c705a889cdac6ee72c47736eebd637b5f2b672d70ecd30e744ca7d9b9a4ad7579587363433e527e5d072abefbb03636978ceb4a31c5bc13a91716ad41f89e0aec2fe74abcc6d6ad5f9e4945357375ef75f997357751dd61d19c71afb61bdef421b951f274ebaffb6716cb3f2b2f230f18d3183dd95b1b441d7d59f2e94757ccf1b27eceff69563618d0f5dcfd9d7beeb7a5dd7f3fac6fb4ee32b87c619f1168d976f7d6ff6df26d6ef4b301e1bac7f54f63e747a5e87f5c173276aff605c22a8fefc540e5e3af5972f956359577bf2c6d978cb78d6697edc9eb2f8b143fed7945359d778b0e6c3e7be81bf7465dce23cf4ac3c16567f19fecefaae53bd081b7baccbb131b7d05f679ca03f27c6b9d1f388c6bbaffab7f80dca4d1fed7bfafdbeedb9533d490f1e2ae8d5693ccacb83d77cd027e3d4416faa97beefa3d7f8f086f168f9d9020f43a3f5c5aa877e28eb88f74fe319becfaa873e0c15de775f95639f3dec37c143d361bf2be3cef4b76bdcb7d84ff5d2cbd0227facf1ecd360fa67d54b9f078ffcd10f636e11ff53e3847853ad3c1b668837e9fe43358c2df45a81430b3db2e6676842d368fd48abdc068b87e0fd2e788bf792b21f861e7afd0e0e5ce37dcdd7d0872218e5d789f57b3218a71ae7bb501eca3af67b510e21c05fd27a1b6218ac3ef17d0ed68fe8cb1be3fc6e5159863ca0bfed824340fe687d62c4838c23f677f0370de380fcdd2be7e03dfab1d6cf300bcd80fa80bfb358dbf9547fa18abe413d68fe421d63ade7737aded094cf63fdcc38f4d0bfee17dac2d08380a3a0dff24cb90bdc23ffaa87e0636c912fed87612e38d4df1e982bc4dbdd29872001f14aca319413eb793e83cbfbe8af27606e4c0f5f8d3bcb7763ec6b8da73b57e69083d61f9d82b9b5fada31f635fcaf8d7b7b5ffb41f16e34ff34fe214dfc0c8e1ef1a22330bfcdc345e31e4c33e3c1e6e181b1609e3ac43f8759803e8cd9f2e7e0ff18ab80ef5d8239e2fc0ef129f90ff0bf0773347fee8d13ce279bc6b34af5c61abf58157bf4b765b05435f4abfec726968c2b67e536fa80fe388279067f680d2c750dff75fe9766da07e44bc02521ba4e1d584c1fb261dcc23fd97f63f8c3af136bfc687362c4e76962ed8fac7a2ecdb7853f7c63ec6bf467bd3fc421c680780c60b6f9275fc045aea84fadaf18a2e0fc04ff0237bd9e8fefc0d236e8ef3f8d83c57fdd38daf94edeb886be74fec618b3ed7f3731e66b0fe67a403e76c0454ef8de9231dbfe9f8dc5fc47be38a6887ad2f950964d2fdc1a27d3c7b6710693f6ef9864b4fdb4ffc6cc5540fea0972c339c87afc1c9f4443a5fe2c84d40bd2f82cb3ae6c7b1716dbc37b1e68bf5be118b7e3dead78125d87d0bfa99bd7d8fb78c1bdb4fcfcfd51bcb9d716bebda3fb8966cf7cf57709103f2e327c6fe5a2fdc4ab4fb01deef5283fa65d50ffb54c13fd67ec37d61e845fb070f8571df7c341eec7e7a691c6ae453fb771937d1fa999ebfa43358bf573d3327c6fdd0a9de59b87414e59f60911ef3ff07380d16bf0b63b6fbb8ce0f4e523a80ee8ff8a5f21fe83f384f2e8cf9d419db7d4bb4bff32cb1dd871ae38c79417962dd9f8f2786bfaa3fa992ddbf68d5d8d6797d62dc27b4bf4a9bdeee5be7c619efbb169c2b9bb7aa67e972857a67d59bf8943cf4a6f5237dae3b9c3f4f8cef5713637e6a3c6548e546a7eb87e0b28e7cf7c68ddd2fb15f28acf166bd2f484c19f921f8cbc57de84ff52c397b8f7e178d7bbb7f7e9f18f743d593ccd210d12f9e8c05fd8417c1e5e704e2afefa7aa5cf850ff4fc6a1473debfc4d7561c4ebd3c48897f69fd4e4d8eb7e4efb736a0b23fe9adfd465bb3f388d6ff22933fa1df6f7597ad483c62371f6e6ff8671b6f89e198fb8cfb0ea3f49619c6f6562cc27d5474a7906e6b589d1df549f29e78479450fe0b1c2fc67d5731a0ba33ffc97a167fd7d92ebb1b1f9a87acdcdd8a23ff2e3c4a81fed0fb91bbb88f8e9f772f9b9c6b8bfac187bd41febbcc8c35831f4bb6edc227eeec8b847bc58ef0f398d91a1179d6f391746bdebfd60ac4716b0180bd8a97e473f8ea2dfdb7d9c3f8e1cff8f8760057b27e57f92cb6e743377e24efff29cb97377e12edd55b190c99eddb5bb71b7eeceddbb07f7e89edc0ff7d33dbb17f7ea16dd925b761fdd8a63d8174f52b15e756bee93fbecd6dd17b7e136dd96db763b6ed77d2dd67bee9bdb77078ed49e8b27b7c5fabb3b7447eed855ae768d6bcbd339ef7a37b8e022392a2734fbd1dd119350a24c23cde8844ee98ccee9c2f9c2977445d77443b793fd8ceee89e1ee8919ee807fdb4e7995ee8b5d82fd252b15ffe8bfd097da4155aa535fa34597fa6f5f2f717da28f69bb445dbb433d99fd22e7da53dfa3659efd3017da7c3f2af233aa68aeadfec1b6aa9236fd63d0d14ca15c0719913737b965fedcbf0c83cc29a677cc2a77cc6e77cc1977cc5d7c5fee637fbdbb2db9d5adff343b17ee427fec1737ee69762fffa9bfd222ff17259fdc82bbcca6bfc893ff33a7fe10ddee42ddee69ddfec77f92beff137dee703fece877cc4c7aee58a6b6ee8b8fc10ed7eb13f635f9a4bc5e54719477152ba671977a505c84845c57222a7bfd89fcb999ccb855cca955cbfc5546ee456ee68b14cf67b799047799aec2fdc92fc28999cef567224cff222afb2c85b32e36d599265f9282bb23ad95fba6559934f2593f3e798b74acc8fd5f6b3accb17d9904dd9926db3a7520d1fdd37d9915df95a72599e1291566df7e49beccb817c974339829eb55e56dc7e51ec0d2dcbb1545297a791563af1c5eff2d3beb4faf8562f568f0744745b9e9d5f1f7992d5e412a186ffef7afff7dfdffd077c99ae99</data> + </image> +</images> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kcachegrind/kcachegrind/partview.cpp b/kcachegrind/kcachegrind/partview.cpp new file mode 100644 index 00000000..ea1db4d7 --- /dev/null +++ b/kcachegrind/kcachegrind/partview.cpp @@ -0,0 +1,235 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Part View + */ + +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <qheader.h> +#include <klocale.h> + +#include "configuration.h" +#include "partlistitem.h" +#include "toplevel.h" +#include "partview.h" + + + +// +// PartView +// + + +PartView::PartView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + _inSelectionUpdate = false; + + addColumn( i18n( "Profile Part" ) ); + addColumn( i18n( "Incl." ) ); + addColumn( i18n( "Self" ) ); + addColumn( i18n( "Called" ) ); + //addColumn( i18n( "Fixed" ) ); + addColumn( i18n( "Comment" ) ); + + setAllColumnsShowFocus(true); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + setColumnAlignment(3, Qt::AlignRight); + setMinimumHeight(50); + setSelectionMode(Extended); + + connect( this, + SIGNAL( selectionChanged() ), + SLOT( selectionChangedSlot() ) ); + + connect( this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + QWhatsThis::add( this, whatsThis() ); +} + +QString PartView::whatsThis() const +{ + return i18n( "<b>Trace Part List</b>" + "<p>This list shows all trace parts of the loaded " + "trace. For each part, the " + "self/inclusive cost of the current selected " + "function, spent in the part, is shown; " + "percentage costs are always relative to the " + "total cost <em>of the part</em> (not to the whole " + "trace as in the Trace Part Overview). " + "Also shown are the calls happening to/from the " + "current function inside of the trace part.</p>" + "<p>By choosing one or more trace parts from the " + "list, the costs shown all over KCachegrind will " + "only be the ones spent in the selected part(s). " + "If no list selection is shown, in fact all trace " + "parts are selected implicitly.</p>" + "<p>This is a multi-selection list. You can select " + "ranges by dragging the mouse or use SHIFT/CTRL " + "modifiers. " + "Selection/Deselection of trace parts can also be " + "done by using the Trace Part Overview Dockable. " + "This one also supports multiple selection.</p>" + "<p>Note that the list is hidden if only one trace " + "part is loaded.</p>"); +} + + +void PartView::context(QListViewItem* i, const QPoint & pos, int) +{ + QPopupMenu popup; + + TracePart* p = i ? ((PartListItem*) i)->part() : 0; + + if (p) { + popup.insertItem(i18n("Select '%1'").arg(p->name()), 93); + popup.insertItem(i18n("Hide '%1'").arg(p->name()), 94); + popup.insertSeparator(); + } + + popup.insertItem(i18n("Hide Selected"), 95); + popup.insertItem(i18n("Show All"), 96); + popup.insertSeparator(); + + addGoMenu(&popup); + + int r = popup.exec(pos); + if (r == 95) { + ; + } + + // TODO: ... +} + +void PartView::selectionChangedSlot() +{ + if (_inSelectionUpdate) return; + + TracePartList l; + QListViewItem* item = firstChild(); + for(;item;item = item->nextSibling()) + if (item->isSelected()) + l.append( ((PartListItem*)item)->part() ); + + selected(l); +} + + +TraceItem* PartView::canShow(TraceItem* i) +{ + if (!data()) return 0; + if (data()->parts().count()>1) return i; + return 0; +} + +void PartView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == costType2Changed) return; + if (changeType == selectedItemChanged) return; + + if (changeType == groupTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + ((PartListItem*)item)->setGroupType(_groupType); + + return; + } + + if (changeType == costTypeChanged) { + QListViewItem *item; + for (item = firstChild();item;item = item->nextSibling()) + ((PartListItem*)item)->setCostType(_costType); + + return; + } + + if (changeType == partsChanged) { + + TracePart* part; + + QListViewItem* item; + _inSelectionUpdate = true; + item = firstChild(); + for(;item;item = item->nextSibling()) { + part = ((PartListItem*)item)->part(); + + if (_partList.containsRef(part)>0) { + setSelected(item, true); + ensureItemVisible(item); + } + else + setSelected(item, false); + } + _inSelectionUpdate = false; + + return; + } + + refresh(); +} + +void PartView::refresh() +{ + clear(); + setColumnWidth(1, 50); + setColumnWidth(2, 50); + + if (!_data || !_activeItem) return; + + TraceItem::CostType t = _activeItem->type(); + TraceFunction* f = 0; + if (t == TraceItem::Function) f = (TraceFunction*) _activeItem; + if (!f) return; + + TracePart* part; + TracePartList hidden; + if (_topLevel) + hidden = _topLevel->hiddenParts(); + + TracePartList allParts = _data->parts(); + + _inSelectionUpdate = true; + + QListViewItem* item = 0; + for (part = allParts.first(); part; part = allParts.next()) { + if (hidden.findRef(part)>=0) continue; + item = new PartListItem(this, f, _costType, _groupType, part); + + if (part->isActive()) { + setSelected(item, true); + ensureItemVisible(item); + } + } + + _inSelectionUpdate = false; + + if (item) { + int headerHeight = header()->height(); + int itemHeight = item->height(); + setMinimumHeight(headerHeight + 2*itemHeight + 2); + } +} + +#include "partview.moc" diff --git a/kcachegrind/kcachegrind/partview.h b/kcachegrind/kcachegrind/partview.h new file mode 100644 index 00000000..5c6c513b --- /dev/null +++ b/kcachegrind/kcachegrind/partview.h @@ -0,0 +1,54 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Part View + */ + +#ifndef PARTVIEW_H +#define PARTVIEW_H + +#include <qlistview.h> +#include "tracedata.h" +#include "traceitemview.h" + +class PartView: public QListView, public TraceItemView +{ + Q_OBJECT + +public: + PartView(TraceItemView* parentView, + QWidget* parent=0, const char* name=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + + void refresh(); + +private slots: + void context(QListViewItem*,const QPoint &, int); + void selectionChangedSlot(); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + + bool _inSelectionUpdate; +}; + +#endif diff --git a/kcachegrind/kcachegrind/pool.cpp b/kcachegrind/kcachegrind/pool.cpp new file mode 100644 index 00000000..ec8d4074 --- /dev/null +++ b/kcachegrind/kcachegrind/pool.cpp @@ -0,0 +1,258 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2004 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <string.h> +#include <stdlib.h> +#include <qglobal.h> +#include "pool.h" + +// FixPool + +#define CHUNK_SIZE 100000 + +struct SpaceChunk +{ + struct SpaceChunk* next; + unsigned int used; + char space[1]; +}; + +FixPool::FixPool() +{ + _first = _last = 0; + _reservation = 0; + _count = 0; + _size = 0; +} + +FixPool::~FixPool() +{ + struct SpaceChunk* chunk = _first, *next; + + while(chunk) { + next = chunk->next; + free(chunk); + chunk = next; + } + + if (0) qDebug("~FixPool: Had %d objects with total size %d\n", + _count, _size); +} + +void* FixPool::allocate(unsigned int size) +{ + if (!ensureSpace(size)) return 0; + + _reservation = 0; + void* result = _last->space + _last->used; + _last->used += size; + + _count++; + _size += size; + + return result; +} + +void* FixPool::reserve(unsigned int size) +{ + if (!ensureSpace(size)) return 0; + _reservation = size; + + return _last->space + _last->used; +} + + +bool FixPool::allocateReserved(unsigned int size) +{ + if (_reservation < size) return false; + + _reservation = 0; + _last->used += size; + + _count++; + _size += size; + + return true; +} + +bool FixPool::ensureSpace(unsigned int size) +{ + if (_last && _last->used + size <= CHUNK_SIZE) return true; + + struct SpaceChunk* newChunk; + + // we don't allow allocation sizes > CHUNK_SIZE + if (size > CHUNK_SIZE) return false; + + newChunk = (struct SpaceChunk*) malloc(sizeof(struct SpaceChunk) + + CHUNK_SIZE); + newChunk->next = 0; + newChunk->used = 0; + + if (!_last) { + _last = _first = newChunk; + } + else { + _last->next = newChunk; + _last = newChunk; + } + return true; +} + + +// DynPool + +DynPool::DynPool() +{ + _data = (char*) malloc(CHUNK_SIZE); + _used = 0; + _size = CHUNK_SIZE; + + // end marker + *(int*)_data = 0; +} + +DynPool::~DynPool() +{ + // we could check for correctness by iteration over all objects + + ::free(_data); +} + +bool DynPool::allocate(char** ptr, unsigned int size) +{ + // round up to multiple of 4 + size = (size+3) & ~3; + + /* need 12 bytes more: + * - 4 bytes for forward chain + * - 4 bytes for pointer to ptr + * - 4 bytes as end marker (not used for new object) + */ + if (!ensureSpace(size + 12)) return false; + + char** obj = (char**) (_data+_used); + obj[0] = (char*)(_data + _used + size + 8); + obj[1] = (char*)ptr; + *(int*)(_data+_used+size+8) = 0; + *ptr = _data+_used+8; + + _used += size + 8; + + return true; +} + +void DynPool::free(char** ptr) +{ + if (!ptr || + !*ptr || + (*(char**)(*ptr - 4)) != (char*)ptr ) + qFatal("Chaining error in DynPool::free"); + + (*(char**)(*ptr - 4)) = 0; + *ptr = 0; +} + +bool DynPool::ensureSpace(unsigned int size) +{ + if (_used + size <= _size) return true; + + unsigned int newsize = _size *3/2 + CHUNK_SIZE; + char* newdata = (char*) malloc(newsize); + + unsigned int freed = 0, len; + char **p, **pnext, **pnew; + + qDebug("DynPool::ensureSpace size: %d => %d, used %d. %p => %p", + _size, newsize, _used, _data, newdata); + + pnew = (char**) newdata; + p = (char**) _data; + while(*p) { + pnext = (char**) *p; + len = (char*)pnext - (char*)p; + + if (0) qDebug(" [%8p] Len %d (ptr %p), freed %d (=> %p)", + p, len, p[1], freed, pnew); + + /* skip freed space ? */ + if (p[1] == 0) { + freed += len; + p = pnext; + continue; + } + + // new and old still at same address ? + if (pnew == p) { + pnew = p = pnext; + continue; + } + + // copy object + pnew[0] = (char*)pnew + len; + pnew[1] = p[1]; + memcpy((char*)pnew + 8, (char*)p + 8, len-8); + + // update pointer to object + char** ptr = (char**) p[1]; + if (*ptr != ((char*)p)+8) + qFatal("Chaining error in DynPool::ensureSpace"); + *ptr = ((char*)pnew)+8; + + pnew = (char**) pnew[0]; + p = pnext; + } + pnew[0] = 0; + + unsigned int newused = (char*)pnew - (char*)newdata; + qDebug("DynPool::ensureSpace size: %d => %d, used %d => %d (%d freed)", + _size, newsize, _used, newused, freed); + + ::free(_data); + _data = newdata; + _size = newsize; + _used = newused; + + return true; +} + +/* Testing the DynPool +int main() +{ + char* bufs[CHUNK_SIZE]; + int i; + + DynPool p; + + for(i=0;i<CHUNK_SIZE;i++) { + p.allocate(bufs+i, 10+i%10); + if (((i%3)==0) && (i>20)) + p.free(bufs+i-20); + } + + for(i=0;i<CHUNK_SIZE;i++) { + if ((bufs[i]==0) || ((i%7)==0)) continue; + p.free(bufs+i); + } + + for(i=0;i<CHUNK_SIZE;i++) { + if (bufs[i]) continue; + p.allocate(bufs+i, 10+i%10); + } +} +*/ diff --git a/kcachegrind/kcachegrind/pool.h b/kcachegrind/kcachegrind/pool.h new file mode 100644 index 00000000..ef9a6a07 --- /dev/null +++ b/kcachegrind/kcachegrind/pool.h @@ -0,0 +1,107 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2004 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef POOL_H +#define POOL_H + +/** + * Pool objects: containers for many small objects. + */ + +struct SpaceChunk; + +/** + * FixPool + * + * For objects with fixed size and life time + * ending with that of the pool. + */ +class FixPool +{ + public: + FixPool(); + ~FixPool(); + + /** + * Take <size> bytes from the pool + */ + void* allocate(unsigned int size); + + /** + * Reserve space. If you call allocateReservedSpace(realsize) + * with realSize < reserved size directly after, you + * will get the same memory area. + */ + void* reserve(unsigned int size); + + /** + * Before calling this, you have to reserve at least <size> bytes + * with reserveSpace(). + */ + bool allocateReserved(unsigned int size); + + private: + /* Checks that there is enough space in the last chunk. + * Returns false if this is not possible. + */ + bool ensureSpace(unsigned int); + + struct SpaceChunk *_first, *_last; + unsigned int _reservation; + int _count, _size; +}; + +/** + * DynPool + * + * For objects which probably need to be resized + * in the future. Objects also can be deleted to free up space. + * As objects can also be moved in a defragmentation step, + * access has to be done via the given pointer object. + */ +class DynPool +{ + public: + DynPool(); + ~DynPool(); + + /** + * Take <size> bytes from the pool, changing <*ptr> + * to point to this allocated space. + * <*ptr> will be changed if the object is moved. + * Returns false if no space available. + */ + bool allocate(char** ptr, unsigned int size); + + /** + * To resize, first allocate new space, and free old + * afterwards. + */ + void free(char** ptr); + + private: + /* Checks that there is enough space. If not, + * it compactifies, possibly moving objects. + */ + bool ensureSpace(unsigned int); + + char* _data; + unsigned int _used, _size; +}; + +#endif // POOL_H diff --git a/kcachegrind/kcachegrind/sourceitem.cpp b/kcachegrind/kcachegrind/sourceitem.cpp new file mode 100644 index 00000000..dabad043 --- /dev/null +++ b/kcachegrind/kcachegrind/sourceitem.cpp @@ -0,0 +1,444 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of source view. + */ + +#include <qpixmap.h> +#include <qregexp.h> +#include <qpainter.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kapplication.h> + +#include "configuration.h" +#include "listutils.h" +#include "sourceview.h" +#include "sourceitem.h" + + +// SourceItem + +// for source lines +SourceItem::SourceItem(SourceView* sv, QListView* parent, + int fileno, unsigned int lineno, + bool inside, const QString& src, + TraceLine* line) + : QListViewItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = inside; + _line = line; + _lineCall = 0; + _lineJump = 0; + + if (src == "...") + setText(0, src); + else + setText(0, QString::number(lineno)); + + QString s = src; + setText(4, s.replace( QRegExp("\t"), " " )); + + updateGroup(); + updateCost(); +} + +// for call lines +SourceItem::SourceItem(SourceView* sv, QListViewItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineCall* lineCall) + : QListViewItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = true; + _line = line; + _lineCall = lineCall; + _lineJump = 0; + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().ascii()); + + SubCost cc = _lineCall->callCount(); + QString templ = " "; + if (cc==0) + templ += i18n("Active call to '%1'"); + else + templ += i18n("%n call to '%1'", "%n calls to '%1'", cc); + + QString callStr = templ.arg(_lineCall->call()->calledName()); + TraceFunction* calledF = _lineCall->call()->called(); + calledF->addPrettyLocation(callStr); + + setText(4, callStr); + + updateGroup(); + updateCost(); +} + +// for jump lines +SourceItem::SourceItem(SourceView* sv, QListViewItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineJump* lineJump) + : QListViewItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = true; + _line = line; + _lineCall = 0; + _lineJump = lineJump; + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().ascii()); + + QString to; + if (_lineJump->lineTo()->functionSource() == _line->functionSource()) + to = _lineJump->lineTo()->name(); + else + to = _lineJump->lineTo()->prettyName(); + + QString jStr; + if (_lineJump->isCondJump()) + jStr = i18n("Jump %1 of %2 times to %3") + .arg(_lineJump->followedCount().pretty()) + .arg(_lineJump->executedCount().pretty()) + .arg(to); + else + jStr = i18n("Jump %1 times to %2") + .arg(_lineJump->executedCount().pretty()) + .arg(to); + + setText(4, jStr); +} + + +void SourceItem::updateGroup() +{ + if (!_lineCall) return; + + TraceFunction* f = _lineCall->call()->called(); + QColor c = Configuration::functionColor(_view->groupType(), f); + setPixmap(4, colorPixmap(10, 10, c)); +} + +void SourceItem::updateCost() +{ + _pure = SubCost(0); + _pure2 = SubCost(0); + + if (!_line) return; + if (_lineJump) return; + + TraceCost* lineCost = _lineCall ? (TraceCost*)_lineCall : (TraceCost*)_line; + + // don't show any cost inside of cycles + if (_lineCall && + ((_lineCall->call()->inCycle()>0) || + (_lineCall->call()->isRecursion()>0))) { + QString str; + QPixmap p; + + QString icon = "undo"; + KIconLoader* loader = KApplication::kApplication()->iconLoader(); + p= loader->loadIcon(icon, KIcon::Small, 0, + KIcon::DefaultState, 0, true); + if (p.isNull()) + str = i18n("(cycle)"); + + setText(1, str); + setPixmap(1, p); + setText(2, str); + setPixmap(2, p); + return; + } + + TraceCost* totalCost; + if (Configuration::showExpanded()) + totalCost = _line->functionSource()->function()->inclusive(); + else + totalCost = _line->functionSource()->function()->data(); + + TraceCostType* ct = _view->costType(); + _pure = ct ? lineCost->subCost(ct) : SubCost(0); + if (_pure == 0) { + setText(1, QString::null); + setPixmap(1, QPixmap()); + } + else { + double total = totalCost->subCost(ct); + double pure = 100.0 * _pure / total; + + if (Configuration::showPercentage()) + setText(1, QString("%1") + .arg(pure, 0, 'f', Configuration::percentPrecision())); + else + setText(1, _pure.pretty()); + + setPixmap(1, costPixmap(ct, lineCost, total, false)); + } + + TraceCostType* ct2 = _view->costType2(); + _pure2 = ct2 ? lineCost->subCost(ct2) : SubCost(0); + if (_pure2 == 0) { + setText(2, QString::null); + setPixmap(2, QPixmap()); + } + else { + double total = totalCost->subCost(ct2); + double pure2 = 100.0 * _pure2 / total; + + if (Configuration::showPercentage()) + setText(2, QString("%1") + .arg(pure2, 0, 'f', Configuration::percentPrecision())); + else + setText(2, _pure2.pretty()); + + setPixmap(2, costPixmap(ct2, lineCost, total, false)); + } +} + + +int SourceItem::compare(QListViewItem * i, int col, bool ascending ) const +{ + const SourceItem* si1 = this; + const SourceItem* si2 = (SourceItem*) i; + + // we always want descending order + if (((col>0) && ascending) || + ((col==0) && !ascending) ) { + si1 = si2; + si2 = this; + } + + if (col==1) { + if (si1->_pure < si2->_pure) return -1; + if (si1->_pure > si2->_pure) return 1; + return 0; + } + if (col==2) { + if (si1->_pure2 < si2->_pure2) return -1; + if (si1->_pure2 > si2->_pure2) return 1; + return 0; + } + if (col==0) { + // Sort file numbers + if (si1->_fileno < si2->_fileno) return -1; + if (si1->_fileno > si2->_fileno) return 1; + + // Sort line numbers + if (si1->_lineno < si2->_lineno) return -1; + if (si1->_lineno > si2->_lineno) return 1; + + // Same line: code gets above calls/jumps + if (!si1->_lineCall && !si1->_lineJump) return -1; + if (!si2->_lineCall && !si2->_lineJump) return 1; + + // calls above jumps + if (si1->_lineCall && !si2->_lineCall) return -1; + if (si2->_lineCall && !si1->_lineCall) return 1; + + if (si1->_lineCall && si2->_lineCall) { + // Two calls: desending sort according costs + if (si1->_pure < si2->_pure) return 1; + if (si1->_pure > si2->_pure) return -1; + + // Two calls: sort according function names + TraceFunction* f1 = si1->_lineCall->call()->called(); + TraceFunction* f2 = si2->_lineCall->call()->called(); + if (f1->prettyName() > f2->prettyName()) return 1; + return -1; + } + + // Two jumps: descending sort according target line + if (si1->_lineJump->lineTo()->lineno() < + si2->_lineJump->lineTo()->lineno()) + return -1; + if (si1->_lineJump->lineTo()->lineno() > + si2->_lineJump->lineTo()->lineno()) + return 1; + return 0; + } + return QListViewItem::compare(i, col, ascending); +} + +void SourceItem::paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int alignment ) +{ + QColorGroup _cg( cg ); + + if ( !_inside || ((column==1) || (column==2))) + _cg.setColor( QColorGroup::Base, cg.button() ); + else if ((_lineCall || _lineJump) && column>2) + _cg.setColor( QColorGroup::Base, cg.midlight() ); + + if (column == 3) + paintArrows(p, _cg, width); + else + QListViewItem::paintCell( p, _cg, column, width, alignment ); +} + +void SourceItem::setJumpArray(const QMemArray<TraceLineJump*>& a) +{ + _jump.duplicate(a); +} + +void SourceItem::paintArrows(QPainter *p, const QColorGroup &cg, int width) +{ + QListView *lv = listView(); + if ( !lv ) return; + SourceView* sv = (SourceView*) lv; + + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole + = QPalette::backgroundRoleFromMode( bgmode ); + if ( cg.brush( crole ) != lv->colorGroup().brush( crole ) ) + p->fillRect( 0, 0, width, height(), cg.brush( crole ) ); + else + sv->paintEmptyArea( p, QRect( 0, 0, width, height() ) ); + + if ( isSelected() && lv->allColumnsShowFocus() ) + p->fillRect( 0, 0, width, height(), cg.brush( QColorGroup::Highlight ) ); + + int marg = lv->itemMargin(); + int yy = height()/2, y1, y2; + QColor c; + + int start = -1, end = -1; + + // draw line borders, detect start/stop of a line + for(int i=0;i< (int)_jump.size();i++) { + if (_jump[i] == 0) continue; + + y1 = 0; + y2 = height(); + if (_lineJump && + (_lineJump->lineTo() == _jump[i]->lineTo()) && + (_jump[i]->lineFrom()->lineno() == _lineno)) { + + if (start<0) start = i; + if (_lineJump == _jump[i]) { + if (_jump[i]->lineTo()->lineno() <= _lineno) + y2 = yy; + else + y1 = yy; + } + } + else if (!_lineJump && !_lineCall && + (_jump[i]->lineTo()->lineno() == _lineno)) { + if (end<0) end = i; + if (_jump[i]->lineFrom()->lineno() < _lineno) + y2 = yy; + else + y1 = yy; + } + + c = _jump[i]->isCondJump() ? red : blue; + p->fillRect( marg + 6*i, y1, 4, y2, c); + p->setPen(c.light()); + p->drawLine( marg + 6*i, y1, marg + 6*i, y2); + p->setPen(c.dark()); + p->drawLine( marg + 6*i +3, y1, marg + 6*i +3, y2); + } + + // draw start/stop horizontal line + int x, y = yy-2, w, h = 4; + if (start >= 0) { + c = _jump[start]->isCondJump() ? red : blue; + x = marg + 6*start; + w = 6*(sv->arrowLevels() - start) + 10; + p->fillRect( x, y, w, h, c); + p->setPen(c.light()); + p->drawLine(x, y, x+w-1, y); + p->drawLine(x, y, x, y+h-1); + p->setPen(c.dark()); + p->drawLine(x+w-1, y, x+w-1, y+h-1); + p->drawLine(x+1, y+h-1, x+w-1, y+h-1); + } + if (end >= 0) { + c = _jump[end]->isCondJump() ? red : blue; + x = marg + 6*end; + w = 6*(sv->arrowLevels() - end) + 10; + + QPointArray a; + a.putPoints(0, 7, x, y+h, + x,y, x+w-8, y, x+w-8, y-2, + x+w, yy, + x+w-8, y+h+2, x+w-8, y+h); + p->setBrush(c); + p->drawConvexPolygon(a); + + p->setPen(c.light()); + p->drawPolyline(a, 0, 5); + p->setPen(c.dark()); + p->drawPolyline(a, 4, 2); + p->setPen(c.light()); + p->drawPolyline(a, 5, 2); + p->setPen(c.dark()); + p->drawPolyline(a, 6, 2); + } + + // draw inner vertical line for start/stop + // this overwrites borders of horizontal line + for(int i=0;i< (int)_jump.size();i++) { + if (_jump[i] == 0) continue; + + c = _jump[i]->isCondJump() ? red : blue; + + if (_jump[i]->lineFrom()->lineno() == _lineno) { + bool drawUp = true; + if (_jump[i]->lineTo()->lineno() == _lineno) + if (start<0) drawUp = false; + if (_jump[i]->lineTo()->lineno() > _lineno) drawUp = false; + if (drawUp) + p->fillRect( marg + 6*i +1, 0, 2, yy, c); + else + p->fillRect( marg + 6*i +1, yy, 2, height()-yy, c); + } + else if (_jump[i]->lineTo()->lineno() == _lineno) { + if (end<0) end = i; + if (_jump[i]->lineFrom()->lineno() < _lineno) + p->fillRect( marg + 6*i +1, 0, 2, yy, c); + else + p->fillRect( marg + 6*i +1, yy, 2, height()-yy, c); + } + } + +} + +int SourceItem::width( const QFontMetrics& fm, + const QListView* lv, int c ) const +{ + if (c != 3) return QListViewItem::width(fm, lv, c); + + SourceView* sv = (SourceView*) lv; + int levels = sv->arrowLevels(); + + if (levels == 0) return 0; + + // 10 pixels for the arrow + return 10 + 6*levels + lv->itemMargin() * 2; +} + diff --git a/kcachegrind/kcachegrind/sourceitem.h b/kcachegrind/kcachegrind/sourceitem.h new file mode 100644 index 00000000..b1a3aec4 --- /dev/null +++ b/kcachegrind/kcachegrind/sourceitem.h @@ -0,0 +1,84 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of source view. + */ + +#ifndef SOURCEITEM_H +#define SOURCEITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class SourceView; + +class SourceItem: public QListViewItem +{ +public: + // for source lines + SourceItem(SourceView* sv, QListView* parent, + int fileno, unsigned int lineno, + bool inside, const QString& src, + TraceLine* line = 0); + + // for call lines + SourceItem(SourceView* sv, QListViewItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineCall* lineCall); + + // for jump lines + SourceItem(SourceView* sv, QListViewItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineJump* lineJump); + + uint lineno() const { return _lineno; } + int fileNumber() const { return _fileno; } + bool inside() const { return _inside; } + TraceLine* line() const { return _line; } + TraceLineCall* lineCall() const { return _lineCall; } + TraceLineJump* lineJump() const { return _lineJump; } + + int compare(QListViewItem * i, int col, bool ascending ) const; + + void paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int alignment ); + int width( const QFontMetrics& fm, + const QListView* lv, int c ) const; + void updateGroup(); + void updateCost(); + + // arrow lines + void setJumpArray(const QMemArray<TraceLineJump*>& a); + +protected: + void paintArrows(QPainter *p, const QColorGroup &cg, int width); + QMemArray<TraceLineJump*> _jump; + +private: + SourceView* _view; + SubCost _pure, _pure2; + uint _lineno; + int _fileno; // for line sorting (even with multiple files) + bool _inside; + TraceLine* _line; + TraceLineJump* _lineJump; + TraceLineCall* _lineCall; +}; + +#endif diff --git a/kcachegrind/kcachegrind/sourceview.cpp b/kcachegrind/kcachegrind/sourceview.cpp new file mode 100644 index 00000000..e5102828 --- /dev/null +++ b/kcachegrind/kcachegrind/sourceview.cpp @@ -0,0 +1,813 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Source View + */ + +#include <qdir.h> +#include <qfile.h> +#include <qwhatsthis.h> +#include <qpopupmenu.h> +#include <klocale.h> +#include <kdebug.h> + +#include "configuration.h" +#include "sourceitem.h" +#include "sourceview.h" + + +// +// SourceView +// + + +SourceView::SourceView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QListView(parent, name), TraceItemView(parentView) +{ + _inSelectionUpdate = false; + + _arrowLevels = 0; + _lowList.setSortLow(true); + _highList.setSortLow(false); + + addColumn( i18n( "#" ) ); + addColumn( i18n( "Cost" ) ); + addColumn( i18n( "Cost 2" ) ); + addColumn( "" ); + addColumn( i18n( "Source (unknown)" ) ); + + setAllColumnsShowFocus(true); + setColumnAlignment(0, Qt::AlignRight); + setColumnAlignment(1, Qt::AlignRight); + setColumnAlignment(2, Qt::AlignRight); + setResizeMode(QListView::LastColumn); + + connect(this, + SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)), + SLOT(context(QListViewItem*, const QPoint &, int))); + + connect(this, + SIGNAL(selectionChanged(QListViewItem*)), + SLOT(selectedSlot(QListViewItem*))); + + connect(this, + SIGNAL(doubleClicked(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + connect(this, + SIGNAL(returnPressed(QListViewItem*)), + SLOT(activatedSlot(QListViewItem*))); + + QWhatsThis::add( this, whatsThis()); +} + +void SourceView::paintEmptyArea( QPainter * p, const QRect & r) +{ + QListView::paintEmptyArea(p, r); +} + + +QString SourceView::whatsThis() const +{ + return i18n( "<b>Annotated Source</b>" + "<p>The annotated source list shows the " + "source lines of the current selected function " + "together with (self) cost spent while executing the " + "code of this source line. If there was a call " + "in a source line, lines with details on the " + "call happening are inserted into the source: " + "the cost spent inside of the call, the " + "number of calls happening, and the call destination.</p>" + "<p>Select a inserted call information line to " + "make the destination function current.</p>"); +} + +void SourceView::context(QListViewItem* i, const QPoint & p, int c) +{ + QPopupMenu popup; + + // Menu entry: + TraceLineCall* lc = i ? ((SourceItem*) i)->lineCall() : 0; + TraceLineJump* lj = i ? ((SourceItem*) i)->lineJump() : 0; + TraceFunction* f = lc ? lc->call()->called() : 0; + TraceLine* line = lj ? lj->lineTo() : 0; + + if (f) { + QString name = f->name(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + popup.insertItem(i18n("Go to '%1'").arg(name), 93); + popup.insertSeparator(); + } + else if (line) { + popup.insertItem(i18n("Go to Line %1").arg(line->name()), 93); + popup.insertSeparator(); + } + + if ((c == 1) || (c == 2)) { + addCostMenu(&popup); + popup.insertSeparator(); + } + addGoMenu(&popup); + + int r = popup.exec(p); + if (r == 93) { + if (f) activated(f); + if (line) activated(line); + } +} + + +void SourceView::selectedSlot(QListViewItem * i) +{ + if (!i) return; + // programatically selected items are not signalled + if (_inSelectionUpdate) return; + + TraceLineCall* lc = ((SourceItem*) i)->lineCall(); + TraceLineJump* lj = ((SourceItem*) i)->lineJump(); + + if (!lc && !lj) { + TraceLine* l = ((SourceItem*) i)->line(); + if (l) { + _selectedItem = l; + selected(l); + } + return; + } + + TraceFunction* f = lc ? lc->call()->called() : 0; + if (f) { + _selectedItem = f; + selected(f); + } + else { + TraceLine* line = lj ? lj->lineTo() : 0; + if (line) { + _selectedItem = line; + selected(line); + } + } +} + +void SourceView::activatedSlot(QListViewItem * i) +{ + if (!i) return; + TraceLineCall* lc = ((SourceItem*) i)->lineCall(); + TraceLineJump* lj = ((SourceItem*) i)->lineJump(); + + if (!lc && !lj) { + TraceLine* l = ((SourceItem*) i)->line(); + if (l) activated(l); + return; + } + + TraceFunction* f = lc ? lc->call()->called() : 0; + if (f) activated(f); + else { + TraceLine* line = lj ? lj->lineTo() : 0; + if (line) activated(line); + } +} + +TraceItem* SourceView::canShow(TraceItem* i) +{ + TraceItem::CostType t = i ? i->type() : TraceItem::NoCostType; + TraceFunction* f = 0; + + switch(t) { + case TraceItem::Function: + f = (TraceFunction*) i; + break; + + case TraceItem::Instr: + f = ((TraceInstr*)i)->function(); + select(i); + break; + + case TraceItem::Line: + f = ((TraceLine*)i)->functionSource()->function(); + select(i); + break; + + default: + break; + } + + return f; +} + +void SourceView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + TraceLine* sLine = 0; + if (_selectedItem->type() == TraceItem::Line) + sLine = (TraceLine*) _selectedItem; + if (_selectedItem->type() == TraceItem::Instr) + sLine = ((TraceInstr*)_selectedItem)->line(); + + SourceItem* si = (SourceItem*)QListView::selectedItem(); + if (si) { + if (si->line() == sLine) return; + if (si->lineCall() && + (si->lineCall()->call()->called() == _selectedItem)) return; + } + + QListViewItem *item, *item2; + for (item = firstChild();item;item = item->nextSibling()) { + si = (SourceItem*)item; + if (si->line() == sLine) { + ensureItemVisible(item); + _inSelectionUpdate = true; + setCurrentItem(item); + _inSelectionUpdate = false; + break; + } + item2 = item->firstChild(); + for (;item2;item2 = item2->nextSibling()) { + si = (SourceItem*)item2; + if (!si->lineCall()) continue; + if (si->lineCall()->call()->called() == _selectedItem) { + ensureItemVisible(item2); + _inSelectionUpdate = true; + setCurrentItem(item2); + _inSelectionUpdate = false; + break; + } + } + if (item2) break; + } + return; + } + + if (changeType == groupTypeChanged) { + QListViewItem *item, *item2; + for (item = firstChild();item;item = item->nextSibling()) + for (item2 = item->firstChild();item2;item2 = item2->nextSibling()) + ((SourceItem*)item2)->updateGroup(); + } + + refresh(); +} + +void SourceView::refresh() +{ + clear(); + setColumnWidth(0, 20); + setColumnWidth(1, 50); + setColumnWidth(2, _costType2 ? 50:0); + setColumnWidth(3, 0); // arrows, defaults to invisible + setSorting(0); // always reset to line number sort + if (_costType) + setColumnText(1, _costType->name()); + if (_costType2) + setColumnText(2, _costType2->name()); + + _arrowLevels = 0; + + if (!_data || !_activeItem) { + setColumnText(4, i18n("(No Source)")); + return; + } + + TraceItem::CostType t = _activeItem->type(); + TraceFunction* f = 0; + if (t == TraceItem::Function) f = (TraceFunction*) _activeItem; + if (t == TraceItem::Instr) { + f = ((TraceInstr*)_activeItem)->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + if (t == TraceItem::Line) { + f = ((TraceLine*)_activeItem)->functionSource()->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + + if (!f) return; + + // Allow resizing of column 2 + setColumnWidthMode(2, QListView::Maximum); + + TraceFunctionSource* mainSF = f->sourceFile(); + + // skip first source if there's no debug info and there are more sources + // (this is for a bug in GCC 2.95.x giving unknown source for prologs) + if (mainSF && + (mainSF->firstLineno() == 0) && + (mainSF->lastLineno() == 0) && + (f->sourceFiles().count()>1) ) { + // skip + } + else + fillSourceFile(mainSF, 0); + + TraceFunctionSource* sf; + int fileno = 1; + TraceFunctionSourceList l = f->sourceFiles(); + for (sf=l.first();sf;sf=l.next(), fileno++) + if (sf != mainSF) + fillSourceFile(sf, fileno); + + if (!_costType2) { + setColumnWidthMode(2, QListView::Manual); + setColumnWidth(2, 0); + } +} + + +// helper for fillSourceList: +// search recursive for a file, starting from a base dir +static bool checkFileExistance(QString& dir, const QString& name) +{ + // we leave this in... + qDebug("Checking %s/%s", dir.ascii(), name.ascii()); + + if (QFile::exists(dir + "/" + name)) return true; + + // check in subdirectories + QDir d(dir); + d.setFilter( QDir::Dirs | QDir::NoSymLinks ); + d.setSorting( QDir::Unsorted ); + QStringList subdirs = d.entryList(); + QStringList::Iterator it =subdirs.begin(); + for(; it != subdirs.end(); ++it ) { + if (*it == "." || *it == ".." || *it == "CVS") continue; + + dir = d.filePath(*it); + if (checkFileExistance(dir, name)) return true; + } + return false; +} + + +void SourceView::updateJumpArray(uint lineno, SourceItem* si, + bool ignoreFrom, bool ignoreTo) +{ + TraceLineJump* lj; + uint lowLineno, highLineno; + int iEnd = -1, iStart = -1; + + if (0) qDebug("updateJumpArray(line %d, jump to %s)", + lineno, + si->lineJump() + ? si->lineJump()->lineTo()->name().ascii() : "?" ); + + + lj=_lowList.current(); + while(lj) { + lowLineno = lj->lineFrom()->lineno(); + if (lj->lineTo()->lineno() < lowLineno) + lowLineno = lj->lineTo()->lineno(); + + if (lowLineno > lineno) break; + + if (ignoreFrom && (lowLineno < lj->lineTo()->lineno())) break; + if (ignoreTo && (lowLineno < lj->lineFrom()->lineno())) break; + + if (si->lineJump() && (lj != si->lineJump())) break; + + int asize = (int)_jump.size(); +#if 0 + for(iStart=0;iStart<asize;iStart++) + if (_jump[iStart] && + (_jump[iStart]->lineTo() == lj->lineTo())) break; +#else + iStart = asize; +#endif + + if (iStart == asize) { + for(iStart=0;iStart<asize;iStart++) + if (_jump[iStart] == 0) break; + + if (iStart== asize) { + asize++; + _jump.resize(asize); + if (asize > _arrowLevels) _arrowLevels = asize; + } + + if (0) qDebug(" start %d (%s to %s)", + iStart, + lj->lineFrom()->name().ascii(), + lj->lineTo()->name().ascii()); + + _jump[iStart] = lj; + } + lj=_lowList.next(); + } + + si->setJumpArray(_jump); + + lj=_highList.current(); + while(lj) { + highLineno = lj->lineFrom()->lineno(); + if (lj->lineTo()->lineno() > highLineno) { + highLineno = lj->lineTo()->lineno(); + if (ignoreTo) break; + } + else if (ignoreFrom) break; + + if (highLineno > lineno) break; + + for(iEnd=0;iEnd< (int)_jump.size();iEnd++) + if (_jump[iEnd] == lj) break; + if (iEnd == (int)_jump.size()) { + qDebug("LineView: no jump start for end at %x ?", highLineno); + iEnd = -1; + } + lj=_highList.next(); + + if (0 && (iEnd>=0)) + qDebug(" end %d (%s to %s)", + iEnd, + _jump[iEnd]->lineFrom()->name().ascii(), + _jump[iEnd]->lineTo()->name().ascii()); + + if (0 && lj) qDebug("next end: %s to %s", + lj->lineFrom()->name().ascii(), + lj->lineTo()->name().ascii()); + + if (highLineno > lineno) + break; + else { + if (iEnd>=0) _jump[iEnd] = 0; + iEnd = -1; + } + } + if (iEnd>=0) _jump[iEnd] = 0; +} + + +/* If sourceList is empty we set the source file name into the header, + * else this code is of a inlined function, and we add "inlined from..." + */ +void SourceView::fillSourceFile(TraceFunctionSource* sf, int fileno) +{ + if (!sf) return; + + if (0) qDebug("Selected Item %s", + _selectedItem ? _selectedItem->name().ascii() : "(none)"); + + TraceLineMap::Iterator lineIt, lineItEnd; + int nextCostLineno = 0, lastCostLineno = 0; + + bool validSourceFile = (!sf->file()->name().isEmpty()); + + TraceLine* sLine = 0; + if (_selectedItem) { + if (_selectedItem->type() == TraceItem::Line) + sLine = (TraceLine*) _selectedItem; + if (_selectedItem->type() == TraceItem::Instr) + sLine = ((TraceInstr*)_selectedItem)->line(); + } + + if (validSourceFile) { + TraceLineMap* lineMap = sf->lineMap(); + if (lineMap) { + lineIt = lineMap->begin(); + lineItEnd = lineMap->end(); + // get first line with cost of selected type + while(lineIt != lineItEnd) { + if (&(*lineIt) == sLine) break; + if ((*lineIt).hasCost(_costType)) break; + if (_costType2 && (*lineIt).hasCost(_costType2)) break; + ++lineIt; + } + + nextCostLineno = (lineIt == lineItEnd) ? 0 : (*lineIt).lineno(); + if (nextCostLineno<0) { + kdError() << "SourceView::fillSourceFile: Negative line number " + << nextCostLineno << endl + << " Function '" << sf->function()->name() << "'" << endl + << " File '" << sf->file()->name() << "'" << endl; + nextCostLineno = 0; + } + + } + + if (nextCostLineno == 0) { + new SourceItem(this, this, fileno, 0, false, + i18n("There is no cost of current selected type associated")); + new SourceItem(this, this, fileno, 1, false, + i18n("with any source line of this function in file")); + new SourceItem(this, this, fileno, 2, false, + QString(" '%1'").arg(sf->function()->prettyName())); + new SourceItem(this, this, fileno, 3, false, + i18n("Thus, no annotated source can be shown.")); + return; + } + } + + QString filename = sf->file()->shortName(); + QString dir = sf->file()->directory(); + if (!dir.isEmpty()) + filename = dir + "/" + filename; + + if (nextCostLineno>0) { + // we have debug info... search for source file + if (!QFile::exists(filename)) { + QStringList list = Configuration::sourceDirs(_data, + sf->function()->object()); + QStringList::Iterator it; + + for ( it = list.begin(); it != list.end(); ++it ) { + dir = *it; + if (checkFileExistance(dir, sf->file()->shortName())) break; + } + + if (it == list.end()) + nextCostLineno = 0; + else { + filename = dir + "/" + sf->file()->shortName(); + // no need to search again + sf->file()->setDirectory(dir); + } + } + } + + // do it here, because the source directory could have been set before + if (childCount()==0) { + setColumnText(4, validSourceFile ? + i18n("Source ('%1')").arg(filename) : + i18n("Source (unknown)")); + } + else { + new SourceItem(this, this, fileno, 0, true, + validSourceFile ? + i18n("--- Inlined from '%1' ---").arg(filename) : + i18n("--- Inlined from unknown source ---")); + } + + if (nextCostLineno == 0) { + new SourceItem(this, this, fileno, 0, false, + i18n("There is no source available for the following function:")); + new SourceItem(this, this, fileno, 1, false, + QString(" '%1'").arg(sf->function()->prettyName())); + if (sf->file()->name().isEmpty()) { + new SourceItem(this, this, fileno, 2, false, + i18n("This is because no debug information is present.")); + new SourceItem(this, this, fileno, 3, false, + i18n("Recompile source and redo the profile run.")); + if (sf->function()->object()) { + new SourceItem(this, this, fileno, 4, false, + i18n("The function is located in this ELF object:")); + new SourceItem(this, this, fileno, 5, false, + QString(" '%1'") + .arg(sf->function()->object()->prettyName())); + } + } + else { + new SourceItem(this, this, fileno, 2, false, + i18n("This is because its source file cannot be found:")); + new SourceItem(this, this, fileno, 3, false, + QString(" '%1'").arg(sf->file()->name())); + new SourceItem(this, this, fileno, 4, false, + i18n("Add the folder of this file to the source folder list.")); + new SourceItem(this, this, fileno, 5, false, + i18n("The list can be found in the configuration dialog.")); + } + return; + } + + + // initialisation for arrow drawing + // create sorted list of jumps (for jump arrows) + TraceLineMap::Iterator it = lineIt, nextIt; + _lowList.clear(); + _highList.clear(); + while(1) { + + nextIt = it; + ++nextIt; + while(nextIt != lineItEnd) { + if (&(*nextIt) == sLine) break; + if ((*nextIt).hasCost(_costType)) break; + if (_costType2 && (*nextIt).hasCost(_costType2)) break; + ++nextIt; + } + + TraceLineJumpList jlist = (*it).lineJumps(); + TraceLineJump* lj; + for (lj=jlist.first();lj;lj=jlist.next()) { + if (lj->executedCount()==0) continue; + // skip jumps to next source line with cost + //if (lj->lineTo() == &(*nextIt)) continue; + + _lowList.append(lj); + _highList.append(lj); + } + it = nextIt; + if (it == lineItEnd) break; + } + _lowList.sort(); + _highList.sort(); + _lowList.first(); // iterators to list start + _highList.first(); + _jump.resize(0); + + + char buf[256]; + bool inside = false, skipLineWritten = true; + int readBytes; + int fileLineno = 0; + SubCost most = 0; + + TraceLine* currLine; + SourceItem *si, *si2, *item = 0, *first = 0, *selected = 0; + QFile file(filename); + if (!file.open(IO_ReadOnly)) return; + while (1) { + readBytes=file.readLine(buf, sizeof( buf )); + if (readBytes<=0) { + // for nice empty 4 lines after function with EOF + buf[0] = 0; + } + + if (readBytes >= (int) sizeof( buf )) { + qDebug("%s:%d Line too long\n", + sf->file()->name().ascii(), fileLineno); + } + else if ((readBytes>0) && (buf[readBytes-1] == '\n')) + buf[readBytes-1] = 0; + + + // keep fileLineno inside [lastCostLineno;nextCostLineno] + fileLineno++; + if (fileLineno == nextCostLineno) { + currLine = &(*lineIt); + + // get next line with cost of selected type + ++lineIt; + while(lineIt != lineItEnd) { + if (&(*lineIt) == sLine) break; + if ((*lineIt).hasCost(_costType)) break; + if (_costType2 && (*lineIt).hasCost(_costType2)) break; + ++lineIt; + } + + lastCostLineno = nextCostLineno; + nextCostLineno = (lineIt == lineItEnd) ? 0 : (*lineIt).lineno(); + } + else + currLine = 0; + + // update inside + if (!inside) { + if (currLine) inside = true; + } + else { + if ( (fileLineno > lastCostLineno) && + ((nextCostLineno == 0) || + (fileLineno < nextCostLineno - Configuration::noCostInside()) )) + inside = false; + } + + int context = Configuration::context(); + + if ( ((lastCostLineno==0) || (fileLineno > lastCostLineno + context)) && + ((nextCostLineno==0) || (fileLineno < nextCostLineno - context))) { + if (lineIt == lineItEnd) break; + + if (!skipLineWritten) { + skipLineWritten = true; + // a "skipping" line: print "..." instead of a line number + strcpy(buf,"..."); + } + else + continue; + } + else + skipLineWritten = false; + + si = new SourceItem(this, this, + fileno, fileLineno, inside, QString(buf), + currLine); + + if (!currLine) continue; + + if (!selected && (currLine == sLine)) selected = si; + if (!first) first = si; + + if (currLine->subCost(_costType) > most) { + item = si; + most = currLine->subCost(_costType); + } + + si->setOpen(true); + TraceLineCallList list = currLine->lineCalls(); + TraceLineCall* lc; + for (lc=list.first();lc;lc=list.next()) { + if ((lc->subCost(_costType)==0) && + (lc->subCost(_costType2)==0)) continue; + + if (lc->subCost(_costType) > most) { + item = si; + most = lc->subCost(_costType); + } + + si2 = new SourceItem(this, si, fileno, fileLineno, currLine, lc); + + if (!selected && (lc->call()->called() == _selectedItem)) + selected = si2; + } + + TraceLineJumpList jlist = currLine->lineJumps(); + TraceLineJump* lj; + for (lj=jlist.first();lj;lj=jlist.next()) { + if (lj->executedCount()==0) continue; + + new SourceItem(this, si, fileno, fileLineno, currLine, lj); + } + } + + if (selected) item = selected; + if (item) first = item; + if (first) { + ensureItemVisible(first); + _inSelectionUpdate = true; + setCurrentItem(first); + _inSelectionUpdate = false; + } + + file.close(); + + // for arrows: go down the list according to list sorting + sort(); + QListViewItem *item1, *item2; + for (item1=firstChild();item1;item1 = item1->nextSibling()) { + si = (SourceItem*)item1; + updateJumpArray(si->lineno(), si, true, false); + + for (item2=item1->firstChild();item2;item2 = item2->nextSibling()) { + si2 = (SourceItem*)item2; + if (si2->lineJump()) + updateJumpArray(si->lineno(), si2, false, true); + else + si2->setJumpArray(_jump); + } + } + + if (arrowLevels()) + setColumnWidth(3, 10 + 6*arrowLevels() + itemMargin() * 2); + else + setColumnWidth(3, 0); +} + + +void SourceView::updateSourceItems() +{ + setColumnWidth(1, 50); + setColumnWidth(2, _costType2 ? 50:0); + // Allow resizing of column 2 + setColumnWidthMode(2, QListView::Maximum); + + if (_costType) + setColumnText(1, _costType->name()); + if (_costType2) + setColumnText(2, _costType2->name()); + + SourceItem* si; + QListViewItem* item = firstChild(); + for (;item;item = item->nextSibling()) { + si = (SourceItem*)item; + TraceLine* l = si->line(); + if (!l) continue; + + si->updateCost(); + + QListViewItem *next, *i = si->firstChild(); + for (;i;i = next) { + next = i->nextSibling(); + ((SourceItem*)i)->updateCost(); + } + } + + if (!_costType2) { + setColumnWidthMode(2, QListView::Manual); + setColumnWidth(2, 0); + } +} + +#include "sourceview.moc" diff --git a/kcachegrind/kcachegrind/sourceview.h b/kcachegrind/kcachegrind/sourceview.h new file mode 100644 index 00000000..4836032a --- /dev/null +++ b/kcachegrind/kcachegrind/sourceview.h @@ -0,0 +1,70 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Source View + */ + +#ifndef SOURCEVIEW_H +#define SOURCEVIEW_H + +#include <qlistview.h> +#include "traceitemview.h" + +class SourceItem; + +class SourceView : public QListView, public TraceItemView +{ + friend class SourceItem; + + Q_OBJECT + +public: + SourceView(TraceItemView* parentView, + QWidget* parent = 0, const char* name = 0); + + QWidget* widget() { return this; } + QString whatsThis() const; + +protected: + int arrowLevels() { return _arrowLevels; } + void paintEmptyArea( QPainter *, const QRect & ); + +private slots: + void context(QListViewItem*, const QPoint &, int); + void selectedSlot(QListViewItem *); + void activatedSlot(QListViewItem *); + +private: + TraceItem* canShow(TraceItem*); + void doUpdate(int); + void refresh(); + void updateJumpArray(uint,SourceItem*,bool,bool); + void fillSourceFile(TraceFunctionSource*, int); + void updateSourceItems(); + + bool _inSelectionUpdate; + + // arrows + int _arrowLevels; + // temporary needed on creation... + QMemArray<TraceLineJump*> _jump; + TraceLineJumpList _lowList, _highList; +}; + +#endif diff --git a/kcachegrind/kcachegrind/stackbrowser.cpp b/kcachegrind/kcachegrind/stackbrowser.cpp new file mode 100644 index 00000000..783edf99 --- /dev/null +++ b/kcachegrind/kcachegrind/stackbrowser.cpp @@ -0,0 +1,417 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <qlistview.h> + +#include "stackbrowser.h" + +// Stack + +Stack::Stack(TraceFunction* top, TraceCallList calls) +{ + _refCount = 0; + _top = top; + _calls = calls; + + extendBottom(); +} + +Stack::Stack(TraceFunction* f) +{ + _refCount = 0; + _top = f; + + extendBottom(); + extendTop(); +} + +void Stack::extendBottom() +{ + TraceCallList l; + TraceCall *c, *call; + SubCost most; + TraceFunction* f; + + if (_calls.last()) + f = _calls.last()->called(); + else + f = _top; + + if (!f) return; + // don't follow calls from cycles + if (f->cycle() == f) return; + + + int max = 30; + + // try to extend to lower stack frames + while (f && (max-- >0)) { + l = f->callings(); + call = 0; + most = 0; + for (c=l.first();c;c=l.next()) { + // no cycle calls in stack: could be deleted without notice + if (c->called()->cycle() == c->called()) continue; + // no simple recursions + if (c->called() == _top) continue; + + if (c->called()->name().isEmpty()) continue; + SubCost sc = c->subCost(0); // FIXME + if (sc == 0) continue; + + if (sc > most) { + most = sc; + call = c; + } + } + if (!call) + break; + + _calls.append(call); + f = call->called(); + } +} + + +void Stack::extendTop() +{ + TraceCallList l; + TraceCall *c, *call; + SubCost most; + + int max = 10; + + // don't follow calls from cycles + if (_top->cycle() == _top) return; + + // try to extend to upper stack frames + while (_top && (max-- >0)) { + l = _top->callers(); + call = 0; + most = 0; + for (c=l.first();c;c=l.next()) { + // no cycle calls in stack: could be deleted without notice + if (c->caller()->cycle() == c->caller()) continue; + // no simple recursions + if (c->caller() == _top) continue; + + if (c->caller()->name().isEmpty()) continue; + SubCost sc = c->subCost(0); // FIXME + if (sc == 0) continue; + + if (sc > most) { + most = sc; + call = c; + } + } + if (!call) + break; + + _calls.prepend(call); + _top = call->caller(); + } +} + +TraceFunction* Stack::caller(TraceFunction* fn, bool extend) +{ + TraceFunction* f; + TraceCall* c; + + if (extend && (_top == fn)) { + // extend at top + extendTop(); + f = _top; + } + + for (c=_calls.first();c;c=_calls.next()) { + f = c->called(); + if (f == fn) + return c->caller(); + } + return 0; +} + +TraceFunction* Stack::called(TraceFunction* fn, bool extend) +{ + TraceFunction* f; + TraceCall* c; + + for (c=_calls.first();c;c=_calls.next()) { + f = c->caller(); + if (f == fn) + return c->called(); + } + + if (extend && (c->called() == fn)) { + // extend at bottom + extendBottom(); + + // and search again + for (c=_calls.first();c;c=_calls.next()) { + f = c->caller(); + if (f == fn) + return c->called(); + } + } + + return 0; +} + +bool Stack::contains(TraceFunction* fn) +{ + // cycles are listed on there own + if (fn->cycle() == fn) return false; + if (_top->cycle() == _top) return false; + + if (fn == _top) + return true; + + TraceFunction* f = _top; + TraceCall* c; + + for (c=_calls.first();c;c=_calls.next()) { + f = c->called(); + if (f == fn) + return true; + } + + TraceCallList l; + + // try to extend at bottom (even if callCount 0) + l = f->callings(); + for (c=l.first();c;c=l.next()) { + f = c->called(); + if (f == fn) + break; + } + + if (c) { + _calls.append(c); + + // extend at bottom after found one + extendBottom(); + return true; + } + + // try to extend at top (even if callCount 0) + l = _top->callers(); + for (c=l.first();c;c=l.next()) { + f = c->caller(); + if (f == fn) + break; + } + + if (c) { + _calls.prepend(c); + + // extend at top after found one + extendTop(); + return true; + } + + return false; +} + +Stack* Stack::split(TraceFunction* f) +{ + TraceCallList calls = _calls; + TraceCall *c, *c2; + + // cycles are listed on there own + if (f->cycle() == f) return 0; + if (_top->cycle() == _top) return false; + + for (c=calls.first();c;c=calls.next()) { + TraceCallList l = c->called()->callings(); + for (c2=l.first();c2;c2=l.next()) { + if (c2 == c) continue; + if (c2->called() == f) + break; + } + if (c2) + break; + } + + if (!c) + return 0; + + // remove bottom part + calls.last(); + while (calls.current() && calls.current()!=c) + calls.removeLast(); + + calls.append(c2); + return new Stack(_top, calls ); +} + +QString Stack::toString() +{ + QString res = _top->name(); + TraceCall *c; + for (c=_calls.first();c;c=_calls.next()) + res += "\n > " + c->called()->name(); + + return res; +} + + +// HistoryItem + +HistoryItem::HistoryItem(Stack* stack, TraceFunction* function) +{ + _stack = stack; + _function = function; + if (_stack) + _stack->ref(); + + _last = 0; + _next = 0; + +/* + qDebug("New Stack History Item (sRef %d): %s\n %s", + _stack->refCount(), _function->name().ascii(), + _stack->toString().ascii()); +*/ +} + +HistoryItem::~HistoryItem() +{ + if (0) qDebug("Deleting Stack History Item (sRef %d): %s", + _stack->refCount(), + _function->name().ascii()); + + if (_last) + _last->_next = _next; + if (_stack) { + if (_stack->deref() == 0) + delete _stack; + } +} + + +// StackBrowser + +StackBrowser::StackBrowser() +{ + _current = 0; +} + +StackBrowser::~StackBrowser() +{ + delete _current; +} + +HistoryItem* StackBrowser::select(TraceFunction* f) +{ + if (!_current) { + Stack* s = new Stack(f); + _current = new HistoryItem(s, f); + } + else if (_current->function() != f) { + // make current item the last one + HistoryItem* item = _current; + if (item->next()) { + item = item->next(); + item->last()->setNext(0); + + while (item->next()) { + item = item->next(); + delete item->last(); + } + delete item; + } + + Stack* s = _current->stack(); + if (!s->contains(f)) { + s = s->split(f); + if (!s) + s = new Stack(f); + } + + item = _current; + _current = new HistoryItem(s, f); + item->setNext(_current); + _current->setLast(item); + } + + // qDebug("Selected %s in StackBrowser", f->name().ascii()); + + return _current; +} + +HistoryItem* StackBrowser::goBack() +{ + if (_current && _current->last()) + _current = _current->last(); + + return _current; +} + +HistoryItem* StackBrowser::goForward() +{ + if (_current && _current->next()) + _current = _current->next(); + + return _current; +} + +HistoryItem* StackBrowser::goUp() +{ + if (_current) { + TraceFunction* f = _current->stack()->caller(_current->function(), true); + if (f) + _current = select(f); + } + + return _current; +} + +HistoryItem* StackBrowser::goDown() +{ + if (_current) { + TraceFunction* f = _current->stack()->called(_current->function(), true); + if (f) + _current = select(f); + } + + return _current; +} + +bool StackBrowser::canGoBack() +{ + return _current && _current->last(); +} + +bool StackBrowser::canGoForward() +{ + return _current && _current->next(); +} + +bool StackBrowser::canGoUp() +{ + if (!_current) return false; + + return _current->stack()->caller(_current->function(), false); +} + +bool StackBrowser::canGoDown() + { + if (!_current) return false; + + return _current->stack()->called(_current->function(), false); +} diff --git a/kcachegrind/kcachegrind/stackbrowser.h b/kcachegrind/kcachegrind/stackbrowser.h new file mode 100644 index 00000000..a533f426 --- /dev/null +++ b/kcachegrind/kcachegrind/stackbrowser.h @@ -0,0 +1,109 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef STACKBROWSER_H +#define STACKBROWSER_H + +#include "tracedata.h" + +// A history of selected functions within stacks + +class Stack +{ +public: + Stack(TraceFunction*); + + // extend the stack at top/bottom if possible + bool contains(TraceFunction*); + + void extendBottom(); + void extendTop(); + + // search for a function on stack calling specified function. + // if found, return upper part with new function call + Stack* split(TraceFunction*); + + // increment reference count + void ref() { _refCount++; } + // decrement reference count + bool deref() { return --_refCount; } + int refCount() { return _refCount; } + + TraceFunction* top() { return _top; } + TraceCallList calls() { return _calls; } + TraceFunction* caller(TraceFunction*, bool extend); + TraceFunction* called(TraceFunction*, bool extend); + + QString toString(); + +private: + Stack(TraceFunction* top, TraceCallList list); + + // at the top of the stack we have a function... + TraceFunction* _top; + // list ordered from top to bottom + TraceCallList _calls; + int _refCount; +}; + +class HistoryItem +{ +public: + HistoryItem(Stack*, TraceFunction*); + ~HistoryItem(); + + Stack* stack() { return _stack; } + TraceFunction* function() { return _function; } + HistoryItem* last() { return _last; } + HistoryItem* next() { return _next; } + void setLast(HistoryItem* h) { _last = h; } + void setNext(HistoryItem* h) { _next = h; } + +private: + + HistoryItem *_last, *_next; + Stack* _stack; + TraceFunction* _function; +}; + + +class StackBrowser +{ +public: + StackBrowser(); + ~StackBrowser(); + + // A function was selected. This creates a new history entry + HistoryItem* select(TraceFunction*); + + HistoryItem* current() { return _current; } + bool canGoBack(); + bool canGoForward(); + bool canGoUp(); + bool canGoDown(); + HistoryItem* goBack(); + HistoryItem* goForward(); + HistoryItem* goUp(); + HistoryItem* goDown(); + +private: + HistoryItem* _current; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/stackitem.cpp b/kcachegrind/kcachegrind/stackitem.cpp new file mode 100644 index 00000000..7ffdd2d3 --- /dev/null +++ b/kcachegrind/kcachegrind/stackitem.cpp @@ -0,0 +1,116 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of stack dockable. + */ + +#include <qpixmap.h> +#include <klocale.h> + +#include "configuration.h" +#include "listutils.h" +#include "stackitem.h" +#include "stackselection.h" + +// StackItem + +StackItem::StackItem(StackSelection* ss, + QListView* parent, TraceFunction* f) + :QListViewItem(parent) +{ + _view = ss; + _function = f; + _call = 0; + + updateGroup(); + updateCost(); + + setText(2, QString("-- ")); + setText(3, f->prettyName()); +} + +StackItem::StackItem(StackSelection* ss, + QListView* parent, TraceCall* call) + :QListViewItem(parent) +{ + _view = ss; + _call = call; + _function = call->called(); + + updateGroup(); + updateCost(); + + setText(3, _function->prettyName()); +} + + +void StackItem::updateGroup() +{ + QColor c = Configuration::functionColor(_view->groupType(), + _function); + setPixmap(3, colorPixmap(10, 10, c)); +} + +void StackItem::updateCost() +{ + if (!_call) return; + + setText(2, _call->prettyCallCount()); + + TraceCostType* ct = _view->costType(); + _sum = _call->subCost(ct); + double total = _call->called()->data()->subCost(ct); + if (total == 0.0) { + setText(0, "-"); + setPixmap(0, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (Configuration::showPercentage()) + setText(0, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + else + setText(0, _call->prettySubCost(ct)); + + setPixmap(0, costPixmap(ct, _call, total, false)); + } + + // if _costType2 is 0, column1 is hidden, no change needed + TraceCostType* ct2 = _view->costType2(); + if (!ct2) return; + + _sum = _call->subCost(ct2); + total = _call->called()->data()->subCost(ct2); + if (total == 0.0) { + setText(1, "-"); + setPixmap(1, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (Configuration::showPercentage()) + setText(1, QString("%1") + .arg(sum, 0, 'f', Configuration::percentPrecision())); + else + setText(1, _call->prettySubCost(ct2)); + + setPixmap(1, costPixmap(ct2, _call, total, false)); + } +} diff --git a/kcachegrind/kcachegrind/stackitem.h b/kcachegrind/kcachegrind/stackitem.h new file mode 100644 index 00000000..a3a3a4c8 --- /dev/null +++ b/kcachegrind/kcachegrind/stackitem.h @@ -0,0 +1,56 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003, 2004 + Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Items of stack dockable. + */ + +#ifndef STACKITEM_H +#define STACKITEM_H + +#include <qlistview.h> +#include "tracedata.h" + +class StackSelection; + + +// for the stack browser + +class StackItem: public QListViewItem +{ +public: + // for top + StackItem(StackSelection* ss, QListView* parent, TraceFunction* f); + StackItem(StackSelection* ss, QListView* parent, TraceCall* c); + + TraceFunction* function() { return _function; } + TraceCall* call() { return _call; } + void updateGroup(); + void updateCost(); + +private: + StackSelection* _view; + SubCost _sum; + TraceFunction* _function; + TraceCall* _call; +}; + + + +#endif diff --git a/kcachegrind/kcachegrind/stackselection.cpp b/kcachegrind/kcachegrind/stackselection.cpp new file mode 100644 index 00000000..c01098e0 --- /dev/null +++ b/kcachegrind/kcachegrind/stackselection.cpp @@ -0,0 +1,230 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * StackSelection for KCachegrind + * For function selection of a most expected stack, + * to be put into a QDockWindow + */ + +#include <qtimer.h> +#include <qlistview.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcombobox.h> +#include <qlineedit.h> + +#include <kdebug.h> + +#include "stackbrowser.h" +#include "stackselection.h" +#include "stackitem.h" + +StackSelection::StackSelection( QWidget* parent, const char* name) + : StackSelectionBase(parent, name) +{ + _data = 0; + _browser = new StackBrowser(); + _item = 0; + _function = 0; + _costType = 0; + _costType2 = 0; + _groupType = TraceItem::Function; + + stackList->setSorting(-1); + stackList->setAllColumnsShowFocus(true); + stackList->setResizeMode(QListView::LastColumn); + stackList->setColumnAlignment(0, Qt::AlignRight); + stackList->setColumnAlignment(1, Qt::AlignRight); + stackList->setColumnAlignment(2, Qt::AlignRight); + stackList->setColumnWidth(0, 50); + // 2nd cost column hidden at first (_costType2 == 0) + stackList->setColumnWidth(1, 0); + stackList->setColumnWidth(2, 50); + + connect(stackList, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(stackSelected(QListViewItem*))); +} + +StackSelection::~StackSelection() +{ + delete _browser; +} + +void StackSelection::setData(TraceData* data) +{ + if (_data == data) return; + + _data = data; + + stackList->clear(); + delete _browser; + _browser = new StackBrowser(); + _function = 0; +} + + +void StackSelection::setFunction(TraceFunction* f) +{ + if (_function == f) return; + _function = f; + + if (!_data || !_function) return; + + //kdDebug() << "StackSelection::setFunction " << f->name() << endl; + + HistoryItem* item = _browser->current(); + if (!item || item->function() != f) { + _browser->select(f); + rebuildStackList(); + } +} + + +void StackSelection::rebuildStackList() +{ + HistoryItem* item = _browser->current(); + stackList->clear(); + stackList->setColumnWidth(0, 50); + stackList->setColumnWidth(1, _costType2 ? 50:0); + stackList->setColumnWidth(2, 50); + if (!item || !item->stack()) return; + + TraceFunction* top = item->stack()->top(); + if (!top) return; + + stackList->setColumnWidthMode(1, QListView::Maximum); + + TraceCallList l = item->stack()->calls(); + TraceCall* call; + for (call=l.last();call;call=l.prev()) + new StackItem(this, stackList, call); + + new StackItem(this, stackList, top); + + // select current function + QListViewItem* i = stackList->firstChild(); + for (;i;i=i->nextSibling()) + if (((StackItem*)i)->function() == item->function()) + break; + + if (i) { + // this calls stackFunctionSelected() + stackList->setCurrentItem(i); + stackList->ensureItemVisible(i); + } + + if (!_costType2) { + stackList->setColumnWidthMode(1, QListView::Manual); + stackList->setColumnWidth(1, 0); + } +} + +void StackSelection::stackSelected(QListViewItem* i) +{ + if (!i) return; + + TraceFunction* f = ((StackItem*)i)->function(); + emit functionSelected(f); +} + + +void StackSelection::browserBack() +{ + if (_browser && _browser->canGoBack()) { + _browser->goBack(); + rebuildStackList(); + } +} + +void StackSelection::browserForward() +{ + if (_browser && _browser->canGoForward()) { + _browser->goForward(); + rebuildStackList(); + } +} + +void StackSelection::browserUp() +{ + if (_browser) { + _browser->goUp(); + rebuildStackList(); + } +} + +void StackSelection::browserDown() +{ + if (_browser) { + _browser->goDown(); + rebuildStackList(); + } +} + +void StackSelection::refresh() +{ + QListViewItem* item = stackList->firstChild(); + for(;item;item = item->nextSibling()) + ((StackItem*)item)->updateCost(); +} + +void StackSelection::setCostType(TraceCostType* ct) +{ + if (ct == _costType) return; + _costType = ct; + + stackList->setColumnWidth(0, 50); + if (_costType) + stackList->setColumnText(0, _costType->name()); + + QListViewItem* item = stackList->firstChild(); + for(;item;item = item->nextSibling()) + ((StackItem*)item)->updateCost(); +} + +void StackSelection::setCostType2(TraceCostType* ct) +{ + if (ct == _costType2) return; + _costType2 = ct; + + stackList->setColumnWidth(1, 50); + stackList->setColumnWidthMode(1, QListView::Maximum); + if (_costType2) + stackList->setColumnText(1, _costType2->name()); + + QListViewItem* item = stackList->firstChild(); + for(;item;item = item->nextSibling()) + ((StackItem*)item)->updateCost(); + + if (!_costType2) { + stackList->setColumnWidthMode(1, QListView::Manual); + stackList->setColumnWidth(1, 0); + } +} + +void StackSelection::setGroupType(TraceItem::CostType gt) +{ + if (_groupType == gt) return; + _groupType = gt; + + QListViewItem* item = stackList->firstChild(); + for(;item;item = item->nextSibling()) + ((StackItem*)item)->updateGroup(); +} + +#include "stackselection.moc" diff --git a/kcachegrind/kcachegrind/stackselection.h b/kcachegrind/kcachegrind/stackselection.h new file mode 100644 index 00000000..80c7ed0e --- /dev/null +++ b/kcachegrind/kcachegrind/stackselection.h @@ -0,0 +1,80 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * StackSelection for KCachegrind + * For function selection of a most expected stack, + * to be put into a QDockWindow + */ + +#ifndef STACKSELECTION_H +#define STACKSELECTION_H + +#include "stackselectionbase.h" +#include "tracedata.h" + +class TraceFunction; +class TraceData; +class StackBrowser; +class NestedAreaItem; + +class StackSelection : public StackSelectionBase +{ + Q_OBJECT + +public: + StackSelection( QWidget* parent = 0, const char* name = 0); + ~StackSelection(); + + TraceData* data() const { return _data; } + void setData(TraceData*); + StackBrowser* browser() const { return _browser; } + TraceCostType* costType() { return _costType; } + TraceCostType* costType2() { return _costType2; } + TraceItem::CostType groupType() { return _groupType; } + +signals: + void functionSelected(TraceItem*); + +public slots: + void setFunction(TraceFunction*); + void setCostType(TraceCostType*); + void setCostType2(TraceCostType*); + void setGroupType(TraceItem::CostType); + + void stackSelected( QListViewItem* ); + void browserBack(); + void browserForward(); + void browserUp(); + void browserDown(); + void refresh(); + void rebuildStackList(); + +private: + void selectFunction(); + + TraceData* _data; + StackBrowser* _browser; + QListViewItem* _item; + TraceFunction* _function; + TraceCostType* _costType; + TraceCostType* _costType2; + TraceItem::CostType _groupType; +}; + +#endif diff --git a/kcachegrind/kcachegrind/stackselectionbase.ui b/kcachegrind/kcachegrind/stackselectionbase.ui new file mode 100644 index 00000000..291153bc --- /dev/null +++ b/kcachegrind/kcachegrind/stackselectionbase.ui @@ -0,0 +1,80 @@ +<!DOCTYPE UI><UI version="3.0" stdsetdef="1"> +<class>StackSelectionBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>StackSelectionBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>168</width> + <height>108</height> + </rect> + </property> + <property name="caption"> + <string>Stack Selection</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QListView"> + <column> + <property name="text"> + <string>Cost</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizeable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Cost2</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizeable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Calls</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizeable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Function</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizeable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>stackList</cstring> + </property> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kcachegrind/kcachegrind/subcost.cpp b/kcachegrind/kcachegrind/subcost.cpp new file mode 100644 index 00000000..5352fe25 --- /dev/null +++ b/kcachegrind/kcachegrind/subcost.cpp @@ -0,0 +1,62 @@ +/* This file is part of KCachegrind. + Copyright (C) 2004 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <qstring.h> + +#include "subcost.h" + +//--------------------------------------------------- +// SubCost + +bool SubCost::set(const char** ps) +{ + const char* s = *ps; + if (!s || (*s < '0') || (*s > '9')) return false; + + v = *s - '0'; + s++; + while(*s >= '0' && *s <= '9') { + v = 10* v + (*s-'0'); + s++; + } + while(*s == ' ') s++; + *ps = s; + + return true; +} + +QString SubCost::pretty() +{ + unsigned long long n = v; + + if (n==0) return QString(" 0"); + + int i = 0; + QString res = ""; + + while (n) { + if ((i>0) && !(i%3)) res = " " + res; + i++; + res = QChar('0'+int(n%10)) + res; + n /= 10; + } + res = " " + res; + return res; +} + + diff --git a/kcachegrind/kcachegrind/subcost.h b/kcachegrind/kcachegrind/subcost.h new file mode 100644 index 00000000..5f24a733 --- /dev/null +++ b/kcachegrind/kcachegrind/subcost.h @@ -0,0 +1,66 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2004 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef SUBCOST_H +#define SUBCOST_H + +#include "utils.h" + +typedef unsigned long long uint64; + +/** + * Cost event counter, simple wrapper around a 64bit entity + */ +class SubCost +{ + public: + SubCost() {} + SubCost(uint64 i) { v=i; } + SubCost(unsigned i) { v=i; } + SubCost(int i) { v=(unsigned)i; } + SubCost(double d) { v= (uint64)(d + .5); } + + SubCost& operator=(uint64 i) { v = i; return *this; } + SubCost& operator=(unsigned i) { v = i; return *this; } + SubCost& operator=(int i) { v = i; return *this; } + SubCost& operator=(double d) { v = (uint64)(d + .5); return *this; } + + bool set(const char** s); + bool set(FixString& s) { return s.stripUInt64(v); } + + operator uint64&() { return v; } + + bool operator==(unsigned i) const { return v == i; } + bool operator==(int i) const { return v == (unsigned)i; } + bool operator<(unsigned i) const { return v < i; } + bool operator<(int i) const { return v < (unsigned)i; } + bool operator<(const SubCost& s) const { return v < s.v; } + bool operator>(unsigned i) const { return v > i; } + bool operator>(int i) const { return v > (unsigned)i; } + bool operator>(const SubCost& s) const { return v > s.v; } + + /** + * Convert SubCost value into a QString, + * spaced every 3 digits. + */ + QString pretty(); + + uint64 v; +}; + +#endif diff --git a/kcachegrind/kcachegrind/tabview.cpp b/kcachegrind/kcachegrind/tabview.cpp new file mode 100644 index 00000000..3a644a15 --- /dev/null +++ b/kcachegrind/kcachegrind/tabview.cpp @@ -0,0 +1,890 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Tab View, enclosing detailed views for one trace item in + * two tab widgets, separated by a splitter + */ + +#include <qobjectlist.h> +#include <qsplitter.h> +#include <qtabwidget.h> +#include <qlayout.h> +#include <qwhatsthis.h> +#include <qpopupmenu.h> + +#include <klocale.h> +#include <kconfig.h> + +#include "tabview.h" +#include "costtypeview.h" +#include "partview.h" +#include "callview.h" +#include "coverageview.h" +#include "callmapview.h" +#include "instrview.h" +#include "sourceview.h" +#include "callgraphview.h" + +// TabBar + +TabBar::TabBar(TabView* v, QTabWidget* parent, const char *name) + : QTabBar(parent, name) +{ + _tabWidget = parent; + _tabView = v; +} + +void TabBar::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == RightButton) { + QTab *tab = selectTab( e->pos() ); + QWidget* page; + page = tab ? _tabWidget->page( indexOf( tab->identifier() ) ) :0; + + QPopupMenu popup, popup1, popup2, popup3; + if (page) { + TraceItemView::Position p = _tabView->tabPosition(page); + if (p != TraceItemView::Top) { + popup.insertItem(i18n("Move to Top"), 81); + popup2.insertItem(i18n("Top"), 91); + } + if (p != TraceItemView::Right) { + popup.insertItem(i18n("Move to Right"), 82); + popup2.insertItem(i18n("Right"), 92); + } + if (p != TraceItemView::Bottom) { + popup.insertItem(i18n("Move to Bottom"), 83); + popup2.insertItem(i18n("Bottom"), 93); + } + if (p != TraceItemView::Left) { + popup.insertItem(i18n("Move to Bottom Left"), 84); + popup2.insertItem(i18n("Bottom Left"), 94); + } + popup.insertItem(i18n("Move Area To"), &popup2, 2); + popup.insertSeparator(); + popup.insertItem(i18n("Hide This Tab"), 80); + popup.insertItem(i18n("Hide Area"), 90); + + if (_tabView->visibleTabs() <2) { + popup.setItemEnabled(80, false); + popup.setItemEnabled(90, false); + } + else if (_tabView->visibleAreas() <2) + popup.setItemEnabled(90, false); + } + popup3.insertItem(i18n("Top"), 101); + popup3.insertItem(i18n("Right"), 102); + popup3.insertItem(i18n("Bottom"), 103); + popup3.insertItem(i18n("Bottom Left"), 104); + popup.insertItem(i18n("Show Hidden On"), &popup3, 3); + + int r = popup.exec( mapToGlobal( e->pos() ) ); + + TraceItemView::Position p = TraceItemView::Hidden; + if ((r % 10) == 1) p = TraceItemView::Top; + if ((r % 10) == 2) p = TraceItemView::Right; + if ((r % 10) == 3) p = TraceItemView::Bottom; + if ((r % 10) == 4) p = TraceItemView::Left; + + if (r>=80 && r<100) _tabView->moveTab(page, p, r>=90); + if (r>=100 && r<110) _tabView->moveTab(0, p, true); + } + + QTabBar::mousePressEvent( e ); +} + + +// +// Splitter +// + +Splitter::Splitter(Orientation o, QWidget* parent, const char* name) + : QSplitter(o, parent, name) +{} + +void Splitter::moveEvent(QMoveEvent* e) +{ + QSplitter::moveEvent(e); + + if (0) qDebug("Splitter %s: Move", name()); + checkVisiblity(); +} + +void Splitter::checkVisiblity() +{ + const QObjectList *l = children(); + QObjectListIt it( *l ); + QObject *obj; + while ( (obj = it.current()) != 0 ) { + ++it; + if (obj->isA("Splitter")) ((Splitter*)obj)->checkVisiblity(); + else if (obj->isA("TabWidget")) ((TabWidget*)obj)->checkVisibility(); + } +} + + + + +// +// TabWidget +// + +TabWidget::TabWidget(TabView* v, QWidget* parent, + const char* name, WFlags f) + : QTabWidget(parent, name, f) +{ + _hasVisibleRect = false; + setTabBar(new TabBar(v, this)); +} + +void TabWidget::checkVisibility() +{ + bool hasVisibleRect = (visibleRect().width()>1) && + (visibleRect().height()>1); + + if (0) qDebug("TabWidget %s: VR (%dx%d) HasVisibleRect: %s => %s", + name(), + visibleRect().width(), visibleRect().height(), + _hasVisibleRect ? "Yes":"No", + hasVisibleRect ? "Yes":"No"); + + if (hasVisibleRect != _hasVisibleRect) { + _hasVisibleRect = hasVisibleRect; + emit visibleRectChanged(this); + } +} + +void TabWidget::resizeEvent(QResizeEvent *e) +{ + QTabWidget::resizeEvent(e); + if (0) qDebug("TabWidget %s:\n Resize from (%d/%d) to (%d/%d)", + name(), + e->oldSize().width(), e->oldSize().height(), + e->size().width(), e->size().height()); + checkVisibility(); +} + +void TabWidget::showEvent(QShowEvent* e) +{ + QTabWidget::showEvent(e); + + if (0) qDebug("TabWidget %s: Show", name()); + checkVisibility(); +} + +void TabWidget::hideEvent(QHideEvent* e) +{ + QTabWidget::hideEvent(e); + + if (0) qDebug("TabWidget %s: Hide", name()); + checkVisibility(); +} + +void TabWidget::moveEvent(QMoveEvent* e) +{ + QTabWidget::moveEvent(e); + + if (0) qDebug("TabWidget %s: Move", name()); + checkVisibility(); +} + + + +// +// TabView +// + +/* + * Areas for child views + * + * leftSplitter + * | + * | ----- ----- + * | _/ \_______________/ \____ + * | | Top | TopRight | + * | | | | + * -> |---------------------| | + * | BottomLeft | Bottom | | + * | | | | + * -\_____/------\____/-------------- + * + * ^ ^ + * bottomSplitter mainSplitter + */ + +TabView::TabView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QWidget(parent, name), TraceItemView(parentView) +{ + setFocusPolicy(QWidget::StrongFocus); + + _isCollapsed = true; + + QVBoxLayout* vbox = new QVBoxLayout( this, 6, 6); + + _nameLabel = new KSqueezedTextLabel( this, "nameLabel" ); + _nameLabel->setText(i18n("(No profile data file loaded)")); + vbox->addWidget( _nameLabel ); + + _mainSplitter = new QSplitter(Qt::Horizontal, this); + _leftSplitter = new Splitter(Qt::Vertical, _mainSplitter, "Left"); + vbox->addWidget( _mainSplitter ); + + _rightTW = new TabWidget(this, _mainSplitter, "Right"); + connect(_rightTW, SIGNAL(currentChanged(QWidget*)), + this, SLOT(tabChanged(QWidget*))); + connect(_rightTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _topTW = new TabWidget(this, _leftSplitter, "Top"); + connect(_topTW, SIGNAL(currentChanged(QWidget*)), + this, SLOT(tabChanged(QWidget*))); + connect(_topTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _bottomSplitter = new Splitter(Qt::Horizontal, + _leftSplitter, "Bottom"); + + _leftTW = new TabWidget(this, _bottomSplitter, "Left"); + _leftTW->setTabPosition(QTabWidget::Bottom); + connect(_leftTW, SIGNAL(currentChanged(QWidget*)), + this, SLOT(tabChanged(QWidget*))); + connect(_leftTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _bottomTW = new TabWidget(this, _bottomSplitter, "Bottom"); + _bottomTW->setTabPosition(QTabWidget::Bottom); + connect(_bottomTW, SIGNAL(currentChanged(QWidget*)), + this, SLOT(tabChanged(QWidget*))); + connect(_bottomTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + + // default positions... + + addTop( addTab( i18n("Types"), + new CostTypeView(this, _topTW, + "CostTypeView"))); + addTop( addTab( i18n("Callers"), + new CallView(true, this, _topTW, + "CallerView"))); + addTop( addTab( i18n("All Callers"), + new CoverageView(true, this, _topTW, + "AllCallerView"))); + addTop( addTab( i18n("Caller Map"), + new CallMapView(true, this, _bottomTW, + "CallerMapView"))); + addTop( addTab( i18n("Source"), + new SourceView(this, _topTW, + "SourceView"))); + + addBottom( addTab( i18n("Parts"), + new PartView(this, _bottomTW, + "PartView"))); + addBottom( addTab( i18n("Call Graph"), + new CallGraphView(this, _bottomTW, + "CallGraphView"))); + addBottom( addTab( i18n("Callees"), + new CallView(false, this, _bottomTW, + "CalleeView"))); + addBottom( addTab( i18n("All Callees"), + new CoverageView(false, this, _bottomTW, + "AllCalleeView"))); + + addBottom( addTab( i18n("Callee Map"), + new CallMapView(false, this, _topTW, + "CalleeMapView"))); + addBottom( addTab( i18n("Assembler"), + new InstrView(this, _bottomTW, + "InstrView"))); + + // after all child widgets are created... + _lastFocus = 0; + _active = false; + installFocusFilters(); + + updateVisibility(); + + QWhatsThis::add( this, whatsThis() ); +} + +void TabView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + TraceItemView* v; + for (v=_tabs.first();v;v=_tabs.next()) + v->setData(d); +} + +TraceItemView* TabView::addTab(QString label, TraceItemView* view) +{ + view->setTitle(label); + _tabs.append(view); + return view; +} + +void TabView::addTop(TraceItemView* view) +{ + view->setPosition(TraceItemView::Top); + _topTW->insertTab(view->widget(), view->title()); +} + +void TabView::addBottom(TraceItemView* view) +{ + view->setPosition(TraceItemView::Bottom); + _bottomTW->insertTab(view->widget(), view->title()); +} + +TraceItemView::Position TabView::tabPosition(QWidget* w) +{ + TraceItemView* v; + for (v=_tabs.first();v;v=_tabs.next()) + if (v->widget() == w) return v->position(); + + return Hidden; +} + +int TabView::visibleTabs() +{ + int c = 0; + TraceItemView* v; + for (v=_tabs.first();v;v=_tabs.next()) { + if (v->position() == Hidden) continue; + c++; + } + return c; +} + + +int TabView::visibleAreas() +{ + int c = 0, t = 0, b = 0, r = 0, l = 0; + TraceItemView* v; + for (v=_tabs.first();v;v=_tabs.next()) { + switch(v->position()) { + case TraceItemView::Top: t++; break; + case TraceItemView::Bottom: b++; break; + case TraceItemView::Left: l++; break; + case TraceItemView::Right: r++; break; + default: break; + } + } + if (t>0) c++; + if (b>0) c++; + if (l>0) c++; + if (r>0) c++; + + return c; +} + + + +// This hides/shows splitters and tabwidgets according to tab childs +void TabView::updateVisibility() +{ + // calculate count of tabs in areas + int t = 0, b = 0, r = 0, l = 0; + TraceItemView* v; + for (v=_tabs.first();v;v=_tabs.next()) { + switch(v->position()) { + case TraceItemView::Top: t++; break; + case TraceItemView::Bottom: b++; break; + case TraceItemView::Left: l++; break; + case TraceItemView::Right: r++; break; + default: break; + } + } + + if (0) qDebug("TabView::updateVisiblity t %d, b %d, l %d, r %d", + t, b, l, r); + + QValueList<int> s; + s.append(100); + + + // children of mainSplitter + if (_rightTW->isHidden() != (r == 0)) { + if (r == 0) { + _rightTW->hide(); + + if (!_topTW->hasVisibleRect() && + !_bottomTW->hasVisibleRect() && + !_leftTW->hasVisibleRect()) _mainSplitter->setSizes(s); + } + else + _rightTW->show(); + } + if (_leftSplitter->isHidden() != (t+b+l == 0)) { + if (t+b+l == 0) { + _leftSplitter->hide(); + + if (!_rightTW->hasVisibleRect()) _mainSplitter->setSizes(s); + } + else + _leftSplitter->show(); + } + + // children of leftSplitter + if (_topTW->isHidden() != (t == 0)) { + if (t == 0) { + _topTW->hide(); + + if (!_bottomTW->hasVisibleRect() && + !_leftTW->hasVisibleRect()) _leftSplitter->setSizes(s); + } + else + _topTW->show(); + } + + if (_bottomSplitter->isHidden() != (b+l == 0)) { + if (b+l == 0) { + _bottomSplitter->hide(); + + if (!_topTW->hasVisibleRect()) _leftSplitter->setSizes(s); + } + else + _bottomSplitter->show(); + } + + // children of bottomSplitter + if (_bottomTW->isHidden() != (b == 0)) { + if (b == 0) { + _bottomTW->hide(); + + if (!_leftTW->hasVisibleRect()) _bottomSplitter->setSizes(s); + } + else + _bottomTW->show(); + } + if (_leftTW->isHidden() != (l == 0)) { + if (l == 0) { + _leftTW->hide(); + + if (!_bottomTW->hasVisibleRect()) _bottomSplitter->setSizes(s); + } + else + _leftTW->show(); + } +} + +TabWidget* TabView::tabWidget(Position p) +{ + switch(p) { + case TraceItemView::Top: return _topTW; + case TraceItemView::Bottom: return _bottomTW; + case TraceItemView::Left: return _leftTW; + case TraceItemView::Right: return _rightTW; + default: break; + } + return 0; +} + +void TabView::moveTab(QWidget* w, Position p, bool wholeArea) +{ + TraceItemView *v; + Position origPos = Hidden; + if (w) { + for (v=_tabs.first();v;v=_tabs.next()) + if (v->widget() == w) break; + + if (!v) return; + origPos = v->position(); + } + if (origPos == p) return; + + TabWidget *from, *to; + from = tabWidget(origPos); + to = tabWidget(p); + + QPtrList<TraceItemView> tabs; + for (v=_tabs.first();v;v=_tabs.next()) + if ((v->position() == origPos) && + (wholeArea || (v->widget() == w))) tabs.append(v); + + bool isEnabled; + for (v=tabs.first();v;v=tabs.next()) { + v->setPosition(p); + w = v->widget(); + + if (from) { + isEnabled = from->isTabEnabled(w); + from->removePage(w); + } + else isEnabled = (v->canShow(_activeItem)!=0); + + if (to) { + TraceItemView *vv; + int idx = -1, i; + for(vv = _tabs.first(); vv && (vv!=v); vv = _tabs.next()) { + i = to->indexOf(vv->widget()); + if (i>=0) idx = i; + } + to->insertTab(w, v->title(), idx+1); + to->setTabEnabled(w, isEnabled); + if (isEnabled) { + to->showPage(w); + v->updateView(); + } + } + } + updateVisibility(); +} + + +QString TabView::whatsThis() const +{ + return i18n( "<b>Information Tabs</b>" + "<p>This widget shows information for the " + "current selected function in different tabs: " + "<ul>" + "<li>The Costs tab shows a list of available event types " + "and the inclusive and self costs regarding to these types.</li>" + "<li>The Parts tab shows a list of trace parts " + "if the trace consists of more than one part (otherwise, " + "this tab is hided). " + "The cost of the selected function spent in the different " + "parts together with the calls happening is shown.</li>" + "<li>The Call Lists tab shows direct callers and " + "callees of the function in more detail.</li>" + "<li>The Coverage tab shows the same is the Call " + "Lists tab, but not only direct callers and callees " + "but also indirect ones.</li>" + "<li>The Call Graph tab shows a graphical " + "visualization of the calls done by this function.</li>" + "<li>The Source tab presents annotated source code " + "if debugging information and the source file " + "is available.</li>" + "<li>The Assembler tab presents annotated assembler code " + "if trace information on instruction level " + "is available.</li></ul>" + "For more information, see the <em>What's This?</em> " + "help of the corresponding tab widget</p>"); +} + +void TabView::installFocusFilters() +{ + QObjectList *l = queryList("QWidget"); + QObjectListIt it( *l ); + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + ++it; + if ( ((QWidget*)obj)->isFocusEnabled() ) + obj->installEventFilter(this); + } + delete l; +} + + +bool TabView::eventFilter(QObject* o, QEvent* e) +{ + if (e->type() == QEvent::FocusIn) { + _lastFocus = o->isWidgetType() ? (QWidget*) o : 0; + setActive(_lastFocus != 0); + } + return QWidget::eventFilter(o,e); +} + +void TabView::mousePressEvent(QMouseEvent*) +{ + if (_lastFocus) + _lastFocus->setFocus(); + setActive(true); +} + +void TabView::setActive(bool a) +{ + if (a == _active) return; + _active = a; + + QFont nameLabel_font( _nameLabel->font() ); + nameLabel_font.setBold(a); + _nameLabel->setFont( nameLabel_font ); + + if (0) qDebug("%s::setActive(%s)", name(), a ? "true":"false"); + + if (a) emit activated(this); +} + +void TabView::doUpdate(int changeType) +{ + if (changeType & (activeItemChanged | configChanged | dataChanged)) + + _nameLabel->setText( !_data ? i18n("(No Data loaded)") : + !_activeItem ? i18n("(No function selected)") : + _activeItem->prettyName()); + + + // we use our own list iterators because setTabEnabled can + // invoke tabChanged, which mangles with the lists, too + bool canShow; + TraceItemView *v; + QPtrListIterator<TraceItemView> it( _tabs ); + while ( (v=it.current()) != 0) { + ++it; + + TabWidget *tw = 0; + switch(v->position()) { + case TraceItemView::Top: tw = _topTW; break; + case TraceItemView::Bottom: tw = _bottomTW; break; + case TraceItemView::Left: tw = _leftTW; break; + case TraceItemView::Right: tw = _rightTW; break; + default: break; + } + + // update even if hidden + if (tw) { + if (!tw->hasVisibleRect()) continue; + } + canShow = v->set(changeType, _data, _costType, _costType2, + _groupType, _partList, + _activeItem, _selectedItem); + v->notifyChange(changeType); + + if (!tw) continue; + if (tw->isTabEnabled(v->widget()) != canShow) + tw->setTabEnabled(v->widget(), canShow); + + if (v->widget() == tw->currentPage()) + v->updateView(); + } +} + + +void TabView::tabChanged(QWidget* w) +{ + TraceItemView *v; + for (v=_tabs.first();v;v=_tabs.next()) + if (v->widget() == w) v->updateView(); +} + +void TabView::visibleRectChangedSlot(TabWidget* tw) +{ + if (0) qDebug("%s: %svisible !", + tw->name(), tw->hasVisibleRect() ? "":"un"); + + if (tw->hasVisibleRect()) doUpdate(0); +} + +void TabView::resizeEvent(QResizeEvent* e) +{ + QWidget::resizeEvent(e); + + bool collapsed = (e->size().width()<=1) || (e->size().height()<=1); + if (_isCollapsed != collapsed) { + _isCollapsed = collapsed; + updateView(); + } + + if (0) qDebug("TabView::Resize from (%d/%d) to (%d/%d)", + e->oldSize().width(), e->oldSize().height(), + e->size().width(), e->size().height()); +} + +void TabView::selected(TraceItemView*, TraceItem* s) +{ + // we set selected item for our own children + select(s); + updateView(); + + // still forward to parent + if (_parentView) _parentView->selected(this, s); +} + + +void TabView::readViewConfig(KConfig* c, + QString prefix, QString postfix, + bool withOptions) +{ + if (0) qDebug("%s::readConfig(%s%s)", name(), + prefix.ascii(), postfix.ascii()); + + KConfigGroup* g = configGroup(c, prefix, postfix); + + _mainSplitter->setSizes(g->readIntListEntry("MainSizes")); + _leftSplitter->setSizes(g->readIntListEntry("LeftSizes")); + _bottomSplitter->setSizes(g->readIntListEntry("BottomSizes")); + + QString activeT = g->readEntry("ActiveTop", "CallerView"); + QString activeB = g->readEntry("ActiveBottom", "CalleeView"); + QString activeL = g->readEntry("ActiveLeft", ""); + QString activeR = g->readEntry("ActiveRight", ""); + + QStringList topTabs = g->readListEntry("TopTabs"); + QStringList bottomTabs = g->readListEntry("BottomTabs"); + QStringList leftTabs = g->readListEntry("LeftTabs"); + QStringList rightTabs = g->readListEntry("RightTabs"); + + if (topTabs.isEmpty() && bottomTabs.isEmpty() && + rightTabs.isEmpty() && leftTabs.isEmpty()) { + // no tabs visible ?! Reset to default + topTabs << "CostTypeView" << "CallerView" << "AllCallerView" + << "CalleeMapView" << "SourceView"; + bottomTabs << "PartView" << "CalleeView" << "CallGraphView" + << "AllCalleeView" << "CallerMapView" << "InstrView"; + } + + TraceItemView *activeTop = 0, *activeBottom = 0; + TraceItemView *activeLeft = 0, *activeRight = 0; + + moveTab(0, TraceItemView::Top, true); + TraceItemView *v; + QPtrListIterator<TraceItemView> it( _tabs ); + while ( (v=it.current()) != 0) { + ++it; + + QString n = QString(v->widget()->name()); + if (topTabs.contains(n)) { + moveTab(v->widget(), TraceItemView::Top); + if (n == activeT) activeTop = v; + } + else if (bottomTabs.contains(n)) { + moveTab(v->widget(), TraceItemView::Bottom); + if (n == activeB) activeBottom = v; + } + else if (leftTabs.contains(n)) { + moveTab(v->widget(), TraceItemView::Left); + if (n == activeL) activeLeft = v; + } + else if (rightTabs.contains(n)) { + moveTab(v->widget(), TraceItemView::Right); + if (n == activeR) activeRight = v; + } + else moveTab(v->widget(), Hidden); + + if (withOptions) + v->readViewConfig(c, QString("%1-%2") + .arg(prefix).arg(v->widget()->name()), + postfix, true); + } + if (activeTop) _topTW->showPage(activeTop->widget()); + if (activeBottom)_bottomTW->showPage(activeBottom->widget()); + if (activeLeft) _leftTW->showPage(activeLeft->widget()); + if (activeRight) _rightTW->showPage(activeRight->widget()); + + QString activeType = g->readEntry("ActiveItemType", ""); + QString activeName = g->readEntry("ActiveItemName", ""); + QString selectedType = g->readEntry("SelectedItemType", ""); + QString selectedName = g->readEntry("SelectedItemName", ""); + delete g; + + if (!_data) return; + + if (withOptions) { + // restore active item + TraceItem::CostType t = TraceItem::costType(activeType); + if (t==TraceItem::NoCostType) t = TraceItem::Function; + TraceCost* activeItem = _data->search(t, activeName, _costType); + if (!activeItem) return; + activate(activeItem); + + // restore selected item + t = TraceItem::costType(selectedType); + if (t==TraceItem::NoCostType) t = TraceItem::Function; + TraceCost* selectedItem = _data->search(t, selectedName, + _costType, activeItem); + if (selectedItem) select(selectedItem); + } + + updateView(); +} + +void TabView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, + bool withOptions) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + g.writeEntry("MainSizes", _mainSplitter->sizes()); + g.writeEntry("LeftSizes", _leftSplitter->sizes()); + g.writeEntry("BottomSizes", _bottomSplitter->sizes()); + + QString a; + if ((_topTW->count()>0) && + (_topTW->isTabEnabled(_topTW->currentPage()))) + a = QString(_topTW->currentPage()->name()); + g.writeEntry("ActiveTop", a); + + a.setLength(0); + if ((_bottomTW->count()>0) && + (_bottomTW->isTabEnabled(_bottomTW->currentPage()))) + a = QString(_bottomTW->currentPage()->name()); + g.writeEntry("ActiveBottom", a); + + a.setLength(0); + if ((_leftTW->count()>0) && + (_leftTW->isTabEnabled(_leftTW->currentPage()))) + a = QString(_leftTW->currentPage()->name()); + g.writeEntry("ActiveLeft", a); + + a.setLength(0); + if ((_rightTW->count()>0) && + (_rightTW->isTabEnabled(_rightTW->currentPage()))) + a = QString(_rightTW->currentPage()->name()); + g.writeEntry("ActiveRight", a); + + if (withOptions) + if (_activeItem) { + g.writeEntry("ActiveItemType", + TraceItem::typeName(_activeItem->type())); + g.writeEntry("ActiveItemName", _activeItem->name()); + if (_selectedItem) { + g.writeEntry("SelectedItemType", + TraceItem::typeName(_selectedItem->type())); + g.writeEntry("SelectedItemName", _selectedItem->name()); + } + } + + QStringList topList, bottomList, leftList, rightList; + TraceItemView *v; + for (v=_tabs.first();v;v=_tabs.next()) { + switch(v->position()) { + case TraceItemView::Top: + topList << QString(v->widget()->name()); + break; + + case TraceItemView::Bottom: + bottomList << QString(v->widget()->name()); + break; + + case TraceItemView::Left: + leftList << QString(v->widget()->name()); + break; + + case TraceItemView::Right: + rightList << QString(v->widget()->name()); + break; + + default: break; + } + } + + g.writeEntry("TopTabs", topList); + g.writeEntry("BottomTabs", bottomList); + g.writeEntry("LeftTabs", leftList); + g.writeEntry("RightTabs", rightList); + + if (withOptions) + for (v=_tabs.first();v;v=_tabs.next()) + v->saveViewConfig(c, QString("%1-%2").arg(prefix) + .arg(v->widget()->name()), postfix, true); +} + +#include "tabview.moc" diff --git a/kcachegrind/kcachegrind/tabview.h b/kcachegrind/kcachegrind/tabview.h new file mode 100644 index 00000000..946f9225 --- /dev/null +++ b/kcachegrind/kcachegrind/tabview.h @@ -0,0 +1,170 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Tab View, enclosing detailed views for one trace item in + * two tab widgets, separated by a splitter + */ + +#ifndef TABVIEW_H +#define TABVIEW_H + +#include <qptrlist.h> +#include <qwidget.h> +#include <qtabwidget.h> +#include <qtabbar.h> +#include <ksqueezedtextlabel.h> +#include "traceitemview.h" + +class QSplitter; +class TabView; + +/** + * Subclass of QTabBar to enable context menu on tabs + */ +class TabBar : public QTabBar +{ + Q_OBJECT + + public: + TabBar(TabView*, QTabWidget* parent, const char *name = 0); + protected: + void mousePressEvent(QMouseEvent *e); + + private: + QTabWidget* _tabWidget; + TabView* _tabView; +}; + + +/** + * Own Splitter: + * Call checkVisiblity for all TabWidget children of the splitter + * on a MoveEvent. This typically is produced when collapsing the widget + * inside of another splitter. + */ +class Splitter: public QSplitter +{ + Q_OBJECT + +public: + Splitter(Orientation o, QWidget* parent = 0, const char* name = 0); + void checkVisiblity(); + +protected: + void moveEvent(QMoveEvent *); +}; + + +/** + * Own TabView: + * - A QTabWidget able to track its visible rect via resizeEvents. + * This is needed to track if this widget is collapsed in a QSplitter. + * - Use own TabBar for context menu + */ +class TabWidget: public QTabWidget +{ + Q_OBJECT + +public: + + TabWidget(TabView*, QWidget* parent = 0, + const char* name = 0, WFlags f = 0); + + bool hasVisibleRect() { return _hasVisibleRect; } + void checkVisibility(); + +signals: + void visibleRectChanged(TabWidget*); + +protected: + void resizeEvent(QResizeEvent *); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + void moveEvent(QMoveEvent *); + +private: + bool _hasVisibleRect; +}; + + + +class TabView : public QWidget, public TraceItemView +{ + Q_OBJECT + +public: + + TabView(TraceItemView* parentView, + QWidget* parent = 0, const char* name = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const ; + void setData(TraceData*); + bool isViewVisible() const { return !_isCollapsed; } + void selected(TraceItemView*, TraceItem*); + bool active() const { return _active; } + void setActive(bool); + + /** + * Rearrange tabs + * if <w> == 0, move hidden tabs + */ + void moveTab(QWidget* w, Position, bool wholeArea = false); + + Position tabPosition(QWidget*); + int visibleTabs(); + int visibleAreas(); + + void readViewConfig(KConfig*, QString prefix, QString postfix, bool); + void saveViewConfig(KConfig*, QString prefix, QString postfix, bool); + +public slots: + void tabChanged(QWidget*); + void visibleRectChangedSlot(TabWidget*); + +signals: + void activated(TabView*); + +protected: + void resizeEvent(QResizeEvent *); + bool eventFilter(QObject*, QEvent*); + void mousePressEvent(QMouseEvent*); + +private: + TraceItemView* addTab(QString, TraceItemView*); + void addTop(TraceItemView*); + void addBottom(TraceItemView*); + TabWidget* tabWidget(Position); + void updateVisibility(); + void doUpdate(int); + void installFocusFilters(); + + // this is true if width or height <= 1, and no child updates are done + bool _isCollapsed; + + KSqueezedTextLabel* _nameLabel; + QSplitter *_mainSplitter, *_leftSplitter, *_bottomSplitter; + TabWidget *_topTW, *_leftTW, *_bottomTW, *_rightTW; + QPtrList<TraceItemView> _tabs; + + QWidget* _lastFocus; + bool _active; +}; + +#endif diff --git a/kcachegrind/kcachegrind/tips b/kcachegrind/kcachegrind/tips new file mode 100644 index 00000000..1f555c0e --- /dev/null +++ b/kcachegrind/kcachegrind/tips @@ -0,0 +1,141 @@ +<tip category="KCachegrind|Help"> +<html> +<p>...that the <em>What's This?</em> help for every GUI widget +in KCachegrind contains detailed usage information for this widget? +It is highly recommended to read at least these help texts on first +use. Request <em>What's This?</em> help by pressing +Shift+F1 and clicking on the widget.</p> +</html> +</tip> + +<tip category="KCachegrind|Explanation"> +<html> +<p>...that you can get profile information at instruction level +with Calltree when you provide the option <em>--dump-instr=yes</em>? +Use the Assembler View for the instruction annotations. +</p> +</html> +</tip> + +<tip category="KCachegrind|Keyboard"> +<html> +<p>...that you can use Alt-Left/Right keys of your keyboard to go +back/forward in the active object history ?</p> +</html> +</tip> + +<tip category="KCachegrind|Keyboard"> +<html> +<p>...that you can navigate in the Callee/Caller Map View using +arrow keys? Use Left/Right to change to siblings of the current +item; use Up/Down to go one nesting level up/down. To select +the current item, press Space, and to activate it, press Return. +</p> +</html> +</tip> + +<tip category="KCachegrind|Keyboard"> +<html> +<p>...that you can navigate in the Call Graph View using +arrow keys? Use Up/Down to go one calling level up/down, alternating +between calls and functions. Use Left/Right to change to siblings of a current +selected call. To activate the current item, press Return. +</p> +</html> +</tip> + +<tip category="KCachegrind|Filters"> +<html> +<p>...that you can rapidly locate a function by entering part of its +name (case-insensitive) into the edit line of the toolbar +and hit return?</p> +</html> +</tip> + +<tip category="KCachegrind|Appearance"> +<html> +<p>...that you can assign custom colors to +ELF objects/C++ Classes/Source Files for graph coloring +in <em>Settings->Configure KCachegrind...</em>?</p> +</html> +</tip> + +<tip category="KCachegrind|Configuration"> +<html> +<p>...that you can see if debug info is available for a selected +function by looking at the location label in the Info tab or +the source listing header in the source tab?</p> +<p>There must be the name of the source file (with extension). +If KCachegrind still doesn't show the source, make sure that you +have added the directory of the source file to the +<em>Source Directories</em> list in the configuration. +</html> +</tip> + +<tip category="KCachegrind|Appearance"> +<html> +<p>...that you can configure whether KCachgrind should +show absolute event counts or relative ones (percentage display)?</p> +</html> +</tip> + +<tip category="KCachegrind|Appearance"> +<html> +<p>...that you can configure the maximum number of items +for all function lists in KCachegrind? Limiting the number +of items is done to get a fast reacting GUI. The last item in +the list will show you the number of skipped functions, together +with a cost condition for these skipped functions.</p> +<p>To activate a function with small costs, search for it and select +it in the flat profile. Selecting functions with small cost will +temporarily add them to the flat profile list.</p> +</html> +</tip> + +<tip category="KCachegrind|Explanation"> +<html> +<p>...that the Coverage tab - in contrast to the Call Lists tab - +shows <em>all</em> functions that are calling the selected function +(upper part) / are called by the selected function (bottom part), +no matter how many function are between them on the stack?</p> +<p>Examples:</p> +<p>An entry in the upper list for function foo1() with a value of 50% +with function bar() selected means that 50% of all the cost of function +bar() happened while called from function foo1().</p> +<p>An entry in the bottom list for function foo2() with a value of 50% +with function bar() selected means that 50% of all the cost of function +bar() happened while calling foo2() from bar().</p> +</html> +</tip> + +<tip category="KCachegrind|Explanation"> +<html> +<p>...that waiting for the tool tip inside of a tree map +shows the list of names of the nested rectangles the mouse +pointer is over?</p> +<p>Items from this list can be selected by pressing the right +mouse button.</p> +</html> +</tip> + +<tip category="KCachegrind|Explanation"> +<html> +<p>...that you can constrain the cost counts shown to only a +few parts of the whole trace by selecting these parts in the +"Trace Selection" Dockable?</p> +<p>To generate multiple parts in a profiling run with +cachegrind, use e.g. option --cachedumps=xxx for parts +of a length of xxx basic blocks (A basic block is a run +of not-branching assembler statements inside of your program +code).</p> +</html> +</tip> + +<tip category="KCachegrind|Explanation"> +<p>...that by splitting the view to show information of +two functions simultaniously, selecting a function in +one panel shows the information for that function +in the other panel?</p> +</html> +</tip> + diff --git a/kcachegrind/kcachegrind/toplevel.cpp b/kcachegrind/kcachegrind/toplevel.cpp new file mode 100644 index 00000000..328a6bdb --- /dev/null +++ b/kcachegrind/kcachegrind/toplevel.cpp @@ -0,0 +1,2399 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * KCachegrind top level window + */ + +#define TRACE_UPDATES 0 +#define ENABLE_DUMPDOCK 0 + +#include <stdlib.h> // for system() + +#include <qvbox.h> +#include <qtimer.h> +#include <qwhatsthis.h> +#include <qlineedit.h> +#include <qtextstream.h> +#include <qsizepolicy.h> +#include <qprogressbar.h> +#include <qfile.h> + +// With Qt 3.1, we can disallow user interaction with long tasks. +// This needs QEventLoop. Otherwise, QApplication::processEvents is used. +#if (QT_VERSION-0 >= 0x030100) +#include <qeventloop.h> +#endif + +#include <kapplication.h> +#include <klocale.h> +#include <kstatusbar.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <kaction.h> +#include <kurl.h> +#include <kfiledialog.h> +#include <kio/netaccess.h> +#include <kedittoolbar.h> +#include <kkeydialog.h> +#include <ktip.h> +#include <kpopupmenu.h> +#include <kdebug.h> + +#if ENABLE_DUMPDOCK +#include "dumpselection.h" +#endif + +#include "toplevel.h" +#include "partselection.h" +#include "functionselection.h" +#include "stackselection.h" +#include "stackbrowser.h" +#include "tracedata.h" +#include "configuration.h" +#include "configdlg.h" +#include "multiview.h" +#include "callgraphview.h" + + +TopLevel::TopLevel(const char *name) + : KMainWindow(0, name), DCOPObject("KCachegrindIface") +{ + init(); + + createDocks(); + + _multiView = new MultiView(this, this, "MultiView"); + setCentralWidget(_multiView); + + createActions(); + + _partDockShown->setChecked(!_partDock->isHidden()); + _stackDockShown->setChecked(!_stackDock->isHidden()); + _functionDockShown->setChecked(!_functionDock->isHidden()); + + connect(_partDock, SIGNAL(visibilityChanged(bool)), + this, SLOT(partVisibilityChanged(bool))); + connect(_stackDock, SIGNAL(visibilityChanged(bool)), + this, SLOT(stackVisibilityChanged(bool))); + connect(_functionDock, SIGNAL(visibilityChanged(bool)), + this, SLOT(functionVisibilityChanged(bool))); + +#if ENABLE_DUMPDOCK + _dumpDockShown->setChecked(!_dumpDock->isHidden()); + connect(_dumpDock, SIGNAL(visibilityChanged(bool)), + this, SLOT(dumpVisibilityChanged(bool))); +#endif + + _statusbar = statusBar(); + _statusLabel = new QLabel(_statusbar); +#if 0 + // how to do avoid main window resizing on large statusbar label? + QSizePolicy p(QSizePolicy::Fixed, QSizePolicy::Expanding); + _statusLabel->setSizePolicy(p); + _statusbar->setSizePolicy(p); +#endif + _statusbar->addWidget(_statusLabel, 1); + + KConfig* kconfig = KGlobal::config(); + Configuration::readOptions( kconfig ); + _openRecent->loadEntries( kconfig ); + + // set toggle after reading configuration + _showPercentage = Configuration::showPercentage(); + _showExpanded = Configuration::showExpanded(); + _showCycles = Configuration::showCycles(); + _taPercentage->setChecked(_showPercentage); + _taExpanded->setChecked(_showExpanded); + _taCycles->setChecked(_showCycles); + + setupPartSelection(_partSelection); + + // KCachegrind for KDE 3.0.x does not allow to hide toolbars... +#if KDE_VERSION >= 308 // KDE 3.1 + setStandardToolBarMenuEnabled(true); +#endif + + // QT dock windows are created before (using QT position restoring) + createGUI(); + + setAutoSaveSettings(); + + // restore current state settings (not configuration options) + restoreCurrentState(QString::null); + + // if this is the first toplevel, show tip of day + if (memberList->count() == 1) + QTimer::singleShot( 200, this, SLOT(slotShowTipOnStart()) ); +} + +void TopLevel::init() +{ + _activeParts.clear(); + _hiddenParts.clear(); + + _progressBar = 0; + + _data = 0; + _function = 0; + _costType = 0; + _costType2 = 0; + _groupType = TraceCost::NoCostType; + _group = 0; + + _layoutCurrent = 0; + _layoutCount = 1; + + // for delayed slots + _traceItemDelayed = 0; + _costTypeDelayed = 0; + _costType2Delayed = 0; + _groupTypeDelayed = TraceCost::NoCostType; + _groupDelayed = 0; + _directionDelayed = TraceItemView::None; + _lastSender = 0; +} + + +/** + * Setup the part selection widget. + * Statusbar has to be created before. + */ +void TopLevel::setupPartSelection(PartSelection* ps) +{ + // setup connections from the part selection widget + + connect(ps, SIGNAL(activePartsChanged(const TracePartList&)), + this, SLOT(activePartsChangedSlot(const TracePartList&))); + connect(ps, SIGNAL(groupChanged(TraceCostItem*)), + this, SLOT(setGroupDelayed(TraceCostItem*))); + connect(ps, SIGNAL(functionChanged(TraceItem*)), + this, SLOT(setTraceItemDelayed(TraceItem*))); + + connect(ps, SIGNAL(goBack()), + _stackSelection, SLOT(browserBack())); + + connect(ps, SIGNAL(partsHideSelected()), + this, SLOT(partsHideSelectedSlotDelayed())); + connect(ps, SIGNAL(partsUnhideAll()), + this, SLOT(partsUnhideAllSlotDelayed())); + + connect(ps, SIGNAL(showMessage(const QString&, int)), + _statusbar, SLOT(message(const QString&, int))); +} + +/** + * This saves the current state of the main window and + * sub widgets. + * + * No positions are saved. These is done automatically for + * KToolbar, and manually in queryExit() for QT docks. + */ +void TopLevel::saveCurrentState(QString postfix) +{ + KConfig* kconfig = KGlobal::config(); + QCString pf = postfix.ascii(); + + KConfigGroup psConfig(kconfig, QCString("PartOverview")+pf); + _partSelection->saveVisualisationConfig(&psConfig); + + KConfigGroup stateConfig(kconfig, QCString("CurrentState")+pf); + stateConfig.writeEntry("CostType", + _costType ? _costType->name() : QString("?")); + stateConfig.writeEntry("CostType2", + _costType2 ? _costType2->name() : QString("?")); + stateConfig.writeEntry("GroupType", TraceItem::typeName(_groupType)); + + _multiView->saveViewConfig(kconfig, QString("MainView"), postfix, true); +} + +/** + * This function is called when a trace is closed. + * Save browsing position for later restoring + */ +void TopLevel::saveTraceSettings() +{ + QString key = traceKey(); + + KConfigGroup pConfig(KGlobal::config(), QCString("TracePositions")); + pConfig.writeEntry(QString("CostType%1").arg(key), + _costType ? _costType->name() : QString("?")); + pConfig.writeEntry(QString("CostType2%1").arg(key), + _costType2 ? _costType2->name() : QString("?")); + pConfig.writeEntry(QString("GroupType%1").arg(key), + TraceItem::typeName(_groupType)); + + if (!_data) return; + + KConfigGroup aConfig(KGlobal::config(), QCString("Layouts")); + aConfig.writeEntry(QString("Count%1").arg(key), _layoutCount); + aConfig.writeEntry(QString("Current%1").arg(key), _layoutCurrent); + + saveCurrentState(key); + pConfig.writeEntry(QString("Group%1").arg(key), + _group ? _group->name() : QString::null); +} + +/** + * This restores the current state of the main window and + * sub widgets. + * + * This does NOT restore any positions. This is done automatically for + * KToolbar, and manually in the createDocks() for QT docks.. + */ +void TopLevel::restoreCurrentState(QString postfix) +{ + KConfig* kconfig = KGlobal::config(); + QStringList gList = kconfig->groupList(); + QCString pf = postfix.ascii(); + + // dock properties (not position, this should be have done before) + QCString group = QCString("PartOverview"); + if (gList.contains(group+pf)) group += pf; + KConfigGroup psConfig(kconfig, group); + _partSelection->readVisualisationConfig(&psConfig); + + _multiView->readViewConfig(kconfig, QString("MainView"), postfix, true); + _taSplit->setChecked(_multiView->childCount()>1); + _taSplitDir->setEnabled(_multiView->childCount()>1); + _taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal); +} + + +void TopLevel::createDocks() +{ + _partDock = new QDockWindow(QDockWindow::InDock, this); + _partDock->setCaption(i18n("Parts Overview")); + _partDock->setCloseMode( QDockWindow::Always ); + _partSelection = new PartSelection(_partDock, "partSelection"); + _partDock->setWidget(_partSelection); + _partDock->setResizeEnabled(true); + _partDock->setFixedExtentWidth(200); + QWhatsThis::add( _partSelection, i18n( + "<b>The Parts Overview</b>" + "<p>A trace consists of multiple trace parts when " + "there are several profile data files from one profile run. " + "The Trace Part Overview dockable shows these, " + "horizontally ordered in execution time; " + "the rectangle sizes are proportional to the total " + "cost spent in the parts. You can select one or several " + "parts to constrain all costs shown to these parts only." + "</p>" + "<p>The parts are further subdivided: there is a " + "partitioning and an callee split mode: " + "<ul><li>Partitioning: You see the " + "partitioning into groups for a trace part, according to " + "the group type selected. E.g. if ELF object groups are " + "selected, you see colored rectangles for each " + "used ELF object (shared library or executable), sized " + "according to the cost spent therein.</li>" + "<li>Callee: A rectangle showing the inclusive " + "cost of the current selected function in the trace part " + "is shown. " + "This is split up into smaller rectangles to show the costs of its " + "callees.</li></ul></p>")); + + _stackDock = new QDockWindow(QDockWindow::InDock, this); + _stackDock->setResizeEnabled(true); + // Why is the caption only correct with a close button? + _stackDock->setCloseMode( QDockWindow::Always ); + _stackSelection = new StackSelection(_stackDock, "stackSelection"); + _stackDock->setWidget(_stackSelection); + _stackDock->setFixedExtentWidth(200); + _stackDock->setCaption(i18n("Top Cost Call Stack")); + QWhatsThis::add( _stackSelection, i18n( + "<b>The Top Cost Call Stack</b>" + "<p>This is a purely fictional 'most probable' call stack. " + "It is built up by starting with the current selected " + "function and adds the callers/callees with highest cost " + "at the top and to bottom.</p>" + "<p>The <b>Cost</b> and <b>Calls</b> columns show the " + "cost used for all calls from the function in the line " + "above.</p>")); + + connect(_stackSelection, SIGNAL(functionSelected(TraceItem*)), + this, SLOT(setTraceItemDelayed(TraceItem*))); + + _functionDock = new QDockWindow(QDockWindow::InDock, this); + _functionDock->setCaption(i18n("Flat Profile")); + _functionDock->setCloseMode( QDockWindow::Always ); + _functionSelection = new FunctionSelection(this, _functionDock, + "functionSelection"); + _functionSelection->setTopLevel(this); + + _functionDock->setWidget(_functionSelection); + _functionDock->setResizeEnabled(true); + _functionDock->setFixedExtentWidth(200); + QWhatsThis::add( _functionSelection, i18n( + "<b>The Flat Profile</b>" + "<p>The flat profile contains a group and a function " + "selection list. The group list contains all groups " + "where costs " + "are spent in, depending on the chosen group type. " + "The group list is hidden when group type 'Function' " + "is selected.<p>" + "<p>The function list contains the functions of the " + "selected group (or all for 'Function' group type), " + "ordered by the costs spent therein. Functions with " + "costs less than 1% are hidden on default.</p>")); + +#if ENABLE_DUMPDOCK + _dumpDock = new QDockWindow(QDockWindow::InDock, this); + _dumpDock->setCaption(i18n("Profile Dumps")); + _dumpDock->setCloseMode( QDockWindow::Always ); + _dumpSelection = new DumpSelection(this, _dumpDock, + "dumpSelection"); + _dumpSelection->setTopLevel(this); + + _dumpDock->setWidget(_dumpSelection); + _dumpDock->setResizeEnabled(true); + _dumpDock->setFixedExtentWidth(200); + QWhatsThis::add( _dumpSelection, i18n( + "<b>Profile Dumps</b>" + "<p>This dockable shows in the top part the list of " + "loadable profile dumps in all subdirectories of: " + "<ul><li>current working directory of KCachegrind, " + "i.e. where it was started from, and " + "<li>the default profile dump directory given in the " + "configuration.</ul> " + "The list is sorted according the the target command " + "profiled in the corresponding dump.</p>" + "<p>On selecting a profile dump, information for it " + "is shown in the bottom area of the dockable: " + "<ul><li><b>Options</b> allows you to view the profiled " + "command and profile options of this dump. By changing " + "any item, a new (yet unexisting) profile template " + "is created. Press <b>Run Profile</b> to start a" + "profile run with these options in the background. " + "<li><b>Info</b> gives detailed info on the selected " + "dump like event cost summary and properties of the " + "simulated cache. " + "<li><b>State</b> is only available for current happening " + "profiles runs. Press <b>Update</b> to see different " + "counters of the run, and a stack trace of the current " + "position in the program profiled. Check the <b>Every</b> " + "option to let KCachegrind regularly poll these data. " + "Check the <b>Sync</b> option to let the dockable activate " + "the top function in the current loaded dump.</ul></p>")); +#endif + + // Restore QT Dock positions... + KConfigGroup dockConfig(KGlobal::config(), QCString("Docks")); + QString str = dockConfig.readEntry("Position", QString::null); + if (0) qDebug("Docks/Position: '%s'", str.ascii()); + if (str.isEmpty()) { + // default positions + addDockWindow(_partDock, DockLeft); + addDockWindow(_stackDock, DockLeft); + addDockWindow(_functionDock, DockLeft); + _stackDock->hide(); +#if ENABLE_DUMPDOCK + addDockWindow(_dumpDock, DockLeft); + _dumpDock->hide(); +#endif + } + else { + QTextStream ts( &str, IO_ReadOnly ); + ts >> *this; + } + _forcePartDock = dockConfig.readBoolEntry("ForcePartDockVisible", false); + +#if 0 + // dock context menu + setAppropriate(_partDock, true); + setAppropriate(_stackDock, true); + setAppropriate(_dumpDock, true); + setAppropriate(_functionDock, true); + + connect( _partDock, SIGNAL(contextMenuRequested(const QPoint &)), + this, SLOT(showDockMenu(const QPoint &))); +#endif +} + + +TopLevel::~TopLevel() +{ + delete _data; +} + + +void TopLevel::saveProperties(KConfig* c) +{ + c->writeEntry("TraceName", _data->traceName()); +} + +void TopLevel::readProperties(KConfig* c) +{ + QString traceName = c->readEntry("TraceName"); + if (!traceName.isEmpty()) { + TraceData* d = new TraceData(this); + d->load(traceName); + setData(d); + } +} + +void TopLevel::createLayoutActions() +{ + QString hint; + KAction* action; + + action = new KAction( i18n( "&Duplicate" ), + KShortcut(KKey("Ctrl+Plus")), + this, SLOT(layoutDuplicate()), + actionCollection(), "layout_duplicate" ); + hint = i18n("<b>Duplicate Current Layout</b>" + "<p>Make a copy of the current layout.</p>"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Remove" ), KShortcut(), + this, SLOT(layoutRemove()), + actionCollection(), "layout_remove" ); + hint = i18n("<b>Remove Current Layout</b>" + "<p>Delete current layout and make the previous active.</p>"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Go to Next" ), + KShortcut(KKey("Ctrl+Right")), + this, SLOT(layoutNext()), + actionCollection(), "layout_next" ); + hint = i18n("Go to Next Layout"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Go to Previous" ), + KShortcut(KKey("Ctrl+Left")), + this, SLOT(layoutPrevious()), + actionCollection(), "layout_previous" ); + hint = i18n("Go to Previous Layout"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Restore to Default" ), KShortcut(), + this, SLOT(layoutRestore()), + actionCollection(), "layout_restore" ); + hint = i18n("Restore Layouts to Default"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Save as Default" ), KShortcut(), + this, SLOT(layoutSave()), + actionCollection(), "layout_save" ); + hint = i18n("Save Layouts as Default"); + action->setWhatsThis( hint ); +} + +// TODO: split this up... +void TopLevel::createMiscActions() +{ + QString hint; + KAction* action; + + action = KStdAction::openNew(this, SLOT(newWindow()), actionCollection()); + hint = i18n("<b>New</b><p>Open new empty KCachegrind window.</p>"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Add..." ), KShortcut(), + this, SLOT(addTrace()), + actionCollection(), "file_add" ); + hint = i18n("<b>Add Profile Data</b>" + "<p>This opens an additional profile data file in the current window.</p>"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Reload" ), "reload", +#if KDE_VERSION > 0x030190 + // for KDE 3.2: KStdAccel::key is deprecated + KStdAccel::shortcut(KStdAccel::Reload), +#else + KStdAccel::key(KStdAccel::Reload), +#endif + this, SLOT( reload() ), actionCollection(), "reload" ); + hint = i18n("<b>Reload Profile Data</b>" + "<p>This loads any new created parts, too.</p>"); + action->setWhatsThis( hint ); + + action = new KAction( i18n( "&Export Graph" ), KShortcut(), + this, SLOT(exportGraph()), + actionCollection(), "export" ); + + hint = i18n("<b>Export Call Graph</b>" + "<p>Generates a file with extension .dot for the tools " + "of the GraphViz package.</p>"); + action->setWhatsThis( hint ); + + + _taDump = new KToggleAction( i18n( "&Force Dump" ), "redo", +#if KDE_VERSION > 0x030190 + // for KDE 3.2: KStdAccel::key is deprecated + KStdAccel::shortcut(KStdAccel::Redo), +#else + KStdAccel::key(KStdAccel::Redo), +#endif + this, SLOT( forceTrace() ), + actionCollection(), "dump" ); + hint = i18n("<b>Force Dump</b>" + "<p>This forces a dump for a Callgrind profile run " + "in the current directory. This action is checked while " + "KCachegrind looks for the dump. If the dump is " + "finished, it automatically reloads the current trace. " + "If this is the one from the running Callgrind, the new " + "created trace part will be loaded, too.</p>" + "<p>Force dump creates a file 'callgrind.cmd', and " + "checks every second for its existence. A running " + "Callgrind will detect this file, dump a trace part, " + "and delete 'callgrind.cmd'. " + "The deletion is detected by KCachegrind, " + "and it does a Reload. If there's <em>no</em> Callgrind " + "running, press 'Force Dump' again to cancel the dump " + "request. This deletes 'callgrind.cmd' itself and " + "stops polling for a new dump.</p>" + "<p>Note: A Callgrind run <em>only</em> detects " + "existence of 'callgrind.cmd' when actively running " + "a few milliseconds, i.e. " + "<em>not</em> sleeping. Tip: For a profiled GUI program, " + "you can awake Callgrind e.g. by resizing a window " + "of the program.</p>"); + _taDump->setWhatsThis( hint ); + + action = KStdAction::open(this, SLOT(loadTrace()), actionCollection()); + hint = i18n("<b>Open Profile Data</b>" + "<p>This opens a profile data file, with possible multiple parts</p>"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + _openRecent = KStdAction::openRecent(this, SLOT(loadTrace(const KURL&)), + actionCollection()); + + KStdAction::showStatusbar(this, + SLOT(toggleStatusBar()), actionCollection()); + + _partDockShown = new KToggleAction(i18n("Parts Overview"), KShortcut(), + this, SLOT(togglePartDock()), + actionCollection(), + "settings_show_partdock"); + + hint = i18n("Show/Hide the Parts Overview Dockable"); + _partDockShown->setToolTip( hint ); + _partDockShown->setWhatsThis( hint ); + + _stackDockShown = new KToggleAction(i18n("Call Stack"), KShortcut(), + this, SLOT(toggleStackDock()), + actionCollection(), + "settings_show_stackdock"); + + hint = i18n("Show/Hide the Call Stack Dockable"); + _stackDockShown->setToolTip( hint ); + _stackDockShown->setWhatsThis( hint ); + + _functionDockShown = new KToggleAction(i18n("Function Profile"), KShortcut(), + this, SLOT(toggleFunctionDock()), + actionCollection(), + "settings_show_profiledock"); + + hint = i18n("Show/Hide the Function Profile Dockable"); + _functionDockShown->setToolTip( hint ); + _functionDockShown->setWhatsThis( hint ); + +#if ENABLE_DUMPDOCK + _dumpDockShown = new KToggleAction(i18n("Profile Dumps"), KShortcut(), + this, SLOT(toggleDumpDock()), + actionCollection(), + "settings_show_dumpdock"); + + hint = i18n("Show/Hide the Profile Dumps Dockable"); + _dumpDockShown->setToolTip( hint ); + _dumpDockShown->setWhatsThis( hint ); +#endif + + _taPercentage = new KToggleAction(i18n("Show Relative Costs"), "percent", + KShortcut(), + this, SLOT(togglePercentage()), + actionCollection(), + "view_percentage"); +#if KDE_VERSION >= 0x030290 + // for KDE 3.3: show another text instead of a checkmark + _taPercentage->setCheckedState(i18n("Show Absolute Costs")); +#endif + + hint = i18n("Show relative instead of absolute costs"); + _taPercentage->setToolTip( hint ); + _taPercentage->setWhatsThis( hint ); + + _taExpanded = new KToggleAction(i18n("Percentage Relative to Parent"), "move", + KShortcut(), + this, SLOT(toggleExpanded()), + actionCollection(), + "view_expanded"); + + hint = i18n("Show percentage costs relative to parent"); + _taExpanded->setToolTip( hint ); + _taExpanded->setWhatsThis( hint ); + + hint = i18n("<b>Show percentage costs relative to parent</b>" + "<p>If this is switched off, percentage costs are always shown " + "relative to the total cost of the profile part(s) that are " + "currently browsed. By turning on this option, percentage cost " + "of shown cost items will be relative to the parent cost item." + "<ul><table>" + "<tr><td><b>Cost Type</td><td><b>Parent Cost</td></tr>" + "<tr><td>Function Cumulative</td><td>Total</td></tr>" + "<tr><td>Function Self</td><td>Function Group (*) / Total</td></tr>" + "<tr><td>Call</td><td>Function Cumulative</td></tr>" + "<tr><td>Source Line</td><td>Function Cumulative</td></tr>" + "</table>" + "<p>(*) Only if function grouping is switched on (e.g. ELF object grouping)."); + _taExpanded->setWhatsThis( hint ); + + _taCycles = new KToggleAction( i18n( "Do Cycle Detection" ), "undo", + KShortcut(), + this, SLOT( toggleCycles() ), actionCollection(), + "view_cycles" ); +#if KDE_VERSION >= 0x030290 + // for KDE 3.3: show another text instead of a checkmark + _taCycles->setCheckedState(i18n("Skip Cycle Detection")); +#endif + + hint = i18n("<b>Detect recursive cycles</b>" + "<p>If this is switched off, the treemap drawing will show " + "black areas when a recursive call is made instead of drawing the " + "recursion ad infinitum. Note that " + "the size of black areas often will be wrong, as inside recursive " + "cycles the cost of calls cannot be determined; the error is small, " + "however, for false cycles (see documentation)." + "<p>The correct handling for cycles is to detect them and collapse all " + "functions of a cycle into a virtual function, which is done when this " + "option is selected. Unfortunately, with GUI applications, this often will " + "lead to huge false cycles, making the analysis impossible; therefore, there " + "is the option to switch this off."); + _taCycles->setWhatsThis( hint ); + + KStdAction::quit(this, SLOT(close()), actionCollection()); + KStdAction::preferences(this, SLOT(configure()), actionCollection()); + KStdAction::keyBindings(this, SLOT(configureKeys()), actionCollection()); + KStdAction::configureToolbars(this,SLOT(configureToolbars()), + actionCollection()); +#if 0 + action = KStdAction::back(_stackSelection, SLOT(browserBack()), + actionCollection()); + hint = i18n("Go back in function selection history"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStdAction::forward(_stackSelection, SLOT(browserForward()), + actionCollection()); + hint = i18n("Go forward in function selection history"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStdAction::up(_stackSelection, SLOT(browserUp()), + actionCollection()); + hint = i18n("<b>Go Up</b>" + "<p>Go to last selected caller of current function. " + "If no caller was visited, use that with highest cost.</p>"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); +#else + _paUp = new KToolBarPopupAction( i18n( "&Up" ), "up", + ALT+Key_Up, + _stackSelection, SLOT( browserUp() ), + actionCollection(), "go_up" ); + connect( _paUp->popupMenu(), SIGNAL( aboutToShow() ), + this, SLOT( upAboutToShow() ) ); + connect( _paUp->popupMenu(), SIGNAL( activated( int ) ), + this, SLOT( upActivated( int ) ) ); + hint = i18n("<b>Go Up</b>" + "<p>Go to last selected caller of current function. " + "If no caller was visited, use that with highest cost.</p>"); + _paUp->setToolTip( hint ); + _paUp->setWhatsThis( hint ); + + QPair< KGuiItem, KGuiItem > backForward = KStdGuiItem::backAndForward(); + _paBack = new KToolBarPopupAction( backForward.first, ALT+Key_Left, + _stackSelection, SLOT(browserBack()), + actionCollection(), "go_back" ); + connect( _paBack->popupMenu(), SIGNAL( aboutToShow() ), + this, SLOT( backAboutToShow() ) ); + connect( _paBack->popupMenu(), SIGNAL( activated( int ) ), + this, SLOT( backActivated( int ) ) ); + hint = i18n("Go back in function selection history"); + _paBack->setToolTip( hint ); + _paBack->setWhatsThis( hint ); + + _paForward = new KToolBarPopupAction( backForward.second, ALT+Key_Right, + _stackSelection, + SLOT(browserForward()), + actionCollection(), "go_forward" ); + connect( _paForward->popupMenu(), SIGNAL( aboutToShow() ), + this, SLOT( forwardAboutToShow() ) ); + connect( _paForward->popupMenu(), SIGNAL( activated( int ) ), + this, SLOT( forwardActivated( int ) ) ); + hint = i18n("Go forward in function selection history"); + _paForward->setToolTip( hint ); + _paForward->setWhatsThis( hint ); +#endif + + _saCost = new KSelectAction( i18n("Primary Event Type"), KShortcut(), + actionCollection(), "view_cost_type"); + hint = i18n("Select primary event type of costs"); + _saCost->setComboWidth(300); + _saCost->setToolTip( hint ); + _saCost->setWhatsThis( hint ); + + // cost types are dependent on loaded data, thus KSelectAction + // is filled in setData() + connect( _saCost, SIGNAL(activated(const QString&)), + this, SLOT(costTypeSelected(const QString&))); + + _saCost2 = new KSelectAction( i18n("Secondary Event Type"), KShortcut(), + actionCollection(), "view_cost_type2"); + hint = i18n("Select secondary event type for cost e.g. shown in annotations"); + _saCost2->setComboWidth(300); + _saCost2->setToolTip( hint ); + _saCost2->setWhatsThis( hint ); + + connect( _saCost2, SIGNAL(activated(const QString&)), + this, SLOT(costType2Selected(const QString&))); + + saGroup = new KSelectAction( i18n("Grouping"), KShortcut(), + actionCollection(), "view_group_type"); + + hint = i18n("Select how functions are grouped into higher level cost items"); + saGroup->setToolTip( hint ); + saGroup->setWhatsThis( hint ); + + QStringList args; + + args << i18n("(No Grouping)") + << TraceCost::i18nTypeName(TraceItem::Object) + << TraceCost::i18nTypeName(TraceItem::File) + << TraceCost::i18nTypeName(TraceItem::Class) + << TraceCost::i18nTypeName(TraceItem::FunctionCycle); + + saGroup->setItems(args); + connect( saGroup, SIGNAL(activated(int)), + this, SLOT(groupTypeSelected(int))); + + _taSplit = new KToggleAction(i18n("Split"), "view_left_right", KShortcut(), + this, SLOT(splitSlot()), + actionCollection(), "view_split"); + + hint = i18n("Show two information panels"); + _taSplit->setToolTip( hint ); + _taSplit->setWhatsThis( hint ); + + _taSplitDir = new KToggleAction(i18n("Split Horizontal"), + "view_left_right", KShortcut(), + this, SLOT(splitDirSlot()), + actionCollection(), "view_split_dir"); + + hint = i18n("Change Split Orientation when main window is split."); + _taSplitDir->setToolTip( hint ); + _taSplitDir->setWhatsThis( hint ); + + // copied from KMail... +#if KDE_VERSION >= 308 // KDE 3.1 + KStdAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() ); +#else + (void) new KAction( KGuiItem( i18n("Tip of the &Day..."), "idea", + i18n("Show \"Tip of the Day\"") ), + 0, this, SLOT(slotShowTip()), + actionCollection(), "help_show_tip" ); +#endif +} + +void TopLevel::createActions() +{ + createMiscActions(); + createLayoutActions(); +} + +void TopLevel::toggleStatusBar() +{ + if (statusBar()->isVisible()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void TopLevel::togglePartDock() +{ + if (!_partDock->isVisible()) + _partDock->show(); + else + _partDock->hide(); +} + +void TopLevel::toggleStackDock() +{ + if (!_stackDock->isVisible()) + _stackDock->show(); + else + _stackDock->hide(); +} + +void TopLevel::toggleDumpDock() +{ +#if ENABLE_DUMPDOCK + if (!_dumpDock->isVisible()) + _dumpDock->show(); + else + _dumpDock->hide(); +#endif +} + +void TopLevel::toggleFunctionDock() +{ + if (!_functionDock->isVisible()) + _functionDock->show(); + else + _functionDock->hide(); +} + +void TopLevel::togglePercentage() +{ + setPercentage(_taPercentage->isChecked()); +} + +void TopLevel::setAbsoluteCost() +{ + setPercentage(false); +} + +void TopLevel::setRelativeCost() +{ + setPercentage(true); +} + +void TopLevel::setPercentage(bool show) +{ + if (_showPercentage == show) return; + _showPercentage = show; + if (_taPercentage->isChecked() != show) + _taPercentage->setChecked(show); + + // FIXME: Delete when no view gets this config from Configuration + Configuration::setShowPercentage(_showPercentage); + + _partSelection->refresh(); + _stackSelection->refresh(); + + _functionSelection->notifyChange(TraceItemView::configChanged); + _functionSelection->updateView(); + + _multiView->notifyChange(TraceItemView::configChanged); + _multiView->updateView(); +} + +void TopLevel::toggleExpanded() +{ + bool show = _taExpanded->isChecked(); + if (_showExpanded == show) return; + _showExpanded = show; + + // FIXME: Delete when no view gets this config from Configuration + Configuration::setShowExpanded(_showExpanded); + + _partSelection->refresh(); + _stackSelection->refresh(); + + _functionSelection->notifyChange(TraceItemView::configChanged); + _functionSelection->updateView(); + + _multiView->notifyChange(TraceItemView::configChanged); + _multiView->updateView(); +} + +void TopLevel::toggleCycles() +{ + bool show = _taCycles->isChecked(); + if (_showCycles == show) return; + _showCycles = show; + + // FIXME: Delete when no view gets this config from Configuration + Configuration::setShowCycles(_showCycles); + + if (!_data) return; + + _data->invalidateDynamicCost(); + _data->updateFunctionCycles(); + + _partSelection->refresh(); + _stackSelection->rebuildStackList(); + + _functionSelection->notifyChange(TraceItemView::configChanged); + _functionSelection->updateView(); + + _multiView->notifyChange(TraceItemView::configChanged); + _multiView->updateView(); +} + +void TopLevel::partVisibilityChanged(bool v) +{ + _partDockShown->setChecked(v); +} + +void TopLevel::stackVisibilityChanged(bool v) +{ + _stackDockShown->setChecked(v); +} + +#if ENABLE_DUMPDOCK +void TopLevel::dumpVisibilityChanged(bool v) +#else +void TopLevel::dumpVisibilityChanged(bool) +#endif +{ +#if ENABLE_DUMPDOCK + _dumpDockShown->setChecked(v); +#endif +} + +void TopLevel::functionVisibilityChanged(bool v) +{ + _functionDockShown->setChecked(v); + if (v) + _functionSelection->updateView(); +} + + +void TopLevel::querySlot() +{ + _functionSelection->query(queryLineEdit->text()); +} + +void TopLevel::configureKeys() +{ +#if KDE_VERSION > 0x030190 + // for KDE 3.2: KKeyDialog::configureKeys is deprecated + KKeyDialog::configure(actionCollection(), this, true); +#else + KKeyDialog::configureKeys(actionCollection(), xmlFile(), true, this); +#endif +} + + +void TopLevel::configureToolbars() +{ + KEditToolbar *dlg = new KEditToolbar(guiFactory(),this); + + if (dlg->exec()) + createGUI(); + + delete dlg; +} + + +void TopLevel::newTrace() +{ + // start cachegrind on command... +} + +void TopLevel::newWindow() +{ + TopLevel* t = new TopLevel(0); + t->show(); +} + + +void TopLevel::loadTrace() +{ + KURL url = KFileDialog::getOpenURL(":", + i18n("cachegrind.out* callgrind.out*|Callgrind Profile Data\n*|All Files"), + this, + i18n("Select Callgrind Profile Data")); + loadTrace(url); +} + +void TopLevel::loadTrace(const KURL& url) +{ + if (url.isEmpty()) return; + + // network transparancy + QString tmpFile; +#if KDE_VERSION > 0x030190 + // for KDE 3.2: KIO::NetAccess::download with 2 args is deprecated + if(KIO::NetAccess::download( url, tmpFile, this )) { +#else + if(KIO::NetAccess::download( url, tmpFile )) { +#endif + _openRecent->addURL(url); + _openRecent->saveEntries( KGlobal::config() ); + + loadTrace(tmpFile); + KIO::NetAccess::removeTempFile( tmpFile ); + } +} + +void TopLevel::loadTrace(QString file) +{ + if (file.isEmpty()) return; + + if (_data && _data->parts().count()>0) { + + // In new window + TopLevel* t = new TopLevel(); + t->show(); + t->loadDelayed(file); + return; + } + + // this constructor enables progress bar callbacks + TraceData* d = new TraceData(this); + d->load(file); + setData(d); +} + + +void TopLevel::addTrace() +{ + KURL url = KFileDialog::getOpenURL(QString::null, + i18n("cachegrind.out* callgrind.out*|Callgrind Profile Data\n*|All Files"), + this, + i18n("Add Callgrind Profile Data")); + addTrace(url); +} + +void TopLevel::addTrace(const KURL& url) +{ + if (url.isEmpty()) return; + + // network transparancy + QString tmpFile; +#if KDE_VERSION > 0x030190 + // for KDE 3.2: KIO::NetAccess::download with 2 args is deprecated + if(KIO::NetAccess::download( url, tmpFile, this )) { +#else + if(KIO::NetAccess::download( url, tmpFile )) { +#endif + _openRecent->addURL(url); + _openRecent->saveEntries( KGlobal::config() ); + + addTrace(tmpFile); + KIO::NetAccess::removeTempFile( tmpFile ); + } +} + +void TopLevel::addTrace(QString file) +{ + if (file.isEmpty()) return; + + if (_data) { + _data->load(file); + + // GUI update for added data + configChanged(); + return; + } + + // this constructor enables progress bar callbacks + TraceData* d = new TraceData(this); + d->load(file); + setData(d); +} + + + +void TopLevel::loadDelayed(QString file) +{ + _loadTraceDelayed = file; + QTimer::singleShot(0, this, SLOT(loadTraceDelayed())); +} + +void TopLevel::loadTraceDelayed() +{ + if (_loadTraceDelayed.isEmpty()) return; + + loadTrace(_loadTraceDelayed); + _loadTraceDelayed = QString::null; +} + + +void TopLevel::reload() +{ + QString trace; + if (!_data || _data->parts().count()==0) + trace = "."; // open first trace found in dir + else + trace = _data->traceName(); + + // this also keeps sure we have the same browsing position... + TraceData* d = new TraceData(this); + d->load(trace); + setData(d); +} + +void TopLevel::exportGraph() +{ + if (!_data || !_function) return; + + QString n = QString("callgraph.dot"); + GraphExporter ge(_data, _function, _costType, _groupType, n); + ge.writeDot(); + + QString cmd = QString("(dot %1 -Tps > %2.ps; kghostview %3.ps)&") + .arg(n).arg(n).arg(n); + system(QFile::encodeName( cmd )); +} + + +bool TopLevel::setCostType(QString s) +{ + TraceCostType* ct; + + ct = (_data) ? _data->mapping()->type(s) : 0; + + // if costtype with given name not found, use first available + if (!ct && _data) ct = _data->mapping()->type(0); + + return setCostType(ct); +} + +bool TopLevel::setCostType2(QString s) +{ + TraceCostType* ct; + + // Special type i18n("(Hidden)") gives 0 + ct = (_data) ? _data->mapping()->type(s) : 0; + + return setCostType2(ct); +} + +void TopLevel::costTypeSelected(const QString& s) +{ + TraceCostType* ct; + + ct = (_data) ? _data->mapping()->typeForLong(s) : 0; + setCostType(ct); +} + +void TopLevel::costType2Selected(const QString& s) +{ + TraceCostType* ct; + + ct = (_data) ? _data->mapping()->typeForLong(s) : 0; + setCostType2(ct); +} + +bool TopLevel::setCostType(TraceCostType* ct) +{ + if (_costType == ct) return false; + _costType = ct; + + if (ct) { + int idx=0; + QStringList l = _saCost->items(); + for (QStringList::Iterator it = l.begin(); it != l.end(); ++it, ++idx ) { + if (*it == ct->longName()) + _saCost->setCurrentItem(idx); + } + } + + _partSelection->setCostType(_costType); + _stackSelection->setCostType(_costType); + + _functionSelection->setCostType(_costType); + _functionSelection->updateView(); + + _multiView->setCostType(_costType); + _multiView->updateView(); + + updateStatusBar(); + + return true; +} + +bool TopLevel::setCostType2(TraceCostType* ct) +{ + if (_costType2 == ct) return false; + _costType2 = ct; + + QString longName = ct ? ct->longName() : i18n("(Hidden)"); + + int idx=0; + QStringList l = _saCost2->items(); + for (QStringList::Iterator it = l.begin(); it != l.end(); ++it, ++idx ) { + if (*it == longName) + _saCost2->setCurrentItem(idx); + } + + _partSelection->setCostType2(_costType2); + _stackSelection->setCostType2(_costType2); + + _functionSelection->setCostType2(_costType2); + _functionSelection->updateView(); + + _multiView->setCostType2(_costType2); + _multiView->updateView(); + + updateStatusBar(); + + return true; +} + + +void TopLevel::groupTypeSelected(int cg) +{ + switch(cg) { + case 0: setGroupType( TraceItem::Function ); break; + case 1: setGroupType( TraceItem::Object ); break; + case 2: setGroupType( TraceItem::File ); break; + case 3: setGroupType( TraceItem::Class ); break; + case 4: setGroupType( TraceItem::FunctionCycle ); break; + default: break; + } +} + +bool TopLevel::setGroupType(QString s) +{ + TraceItem::CostType gt; + + gt = (_data) ? _data->costType(s) : TraceData::costType(s); + // only allow Function/Object/File/Class as grouptype + switch(gt) { + case TraceItem::Object: + case TraceItem::File: + case TraceItem::Class: + case TraceItem::FunctionCycle: + break; + default: + gt = TraceItem::Function; + } + + return setGroupType(gt); +} + +bool TopLevel::setGroupType(TraceItem::CostType gt) +{ + if (_groupType == gt) return false; + _groupType = gt; + + int idx = -1; + switch(gt) { + case TraceItem::Function: idx = 0; break; + case TraceItem::Object: idx = 1; break; + case TraceItem::File: idx = 2; break; + case TraceItem::Class: idx = 3; break; + case TraceItem::FunctionCycle: idx = 4; break; + default: + break; + } + + if (idx==-1) return false; + + if (saGroup->currentItem() != idx) + saGroup->setCurrentItem(idx); + + _stackSelection->setGroupType(_groupType); + _partSelection->setGroupType(_groupType); + + _functionSelection->set(_groupType); + _functionSelection->updateView(); + + _multiView->set(_groupType); + _multiView->updateView(); + + updateStatusBar(); + + return true; +} + +bool TopLevel::setGroup(QString s) +{ + return true; + TraceCostItem* ci = _functionSelection->group(s); + if (!ci) + return false; + + return setGroup(ci); +} + + +bool TopLevel::setGroup(TraceCostItem* g) +{ + _multiView->activate(g); + _multiView->updateView(); + _functionSelection->activate(g); + _functionSelection->updateView(); + + if (_group == g) return false; + _group = g; + + + updateStatusBar(); + + return true; +} + +bool TopLevel::setFunction(QString s) +{ + if (!_data) return false; + + TraceCost* f = _data->search(TraceItem::Function, s, _costType); + if (!f) return false; + + return setFunction((TraceFunction*)f); +} + +bool TopLevel::setFunction(TraceFunction* f) +{ + _multiView->activate(f); + _multiView->updateView(); + + _functionSelection->activate(f); + _functionSelection->updateView(); + + if (_function == f) return false; + _function = f; + + _partSelection->setFunction(_function); + _stackSelection->setFunction(_function); + + StackBrowser* b = _stackSelection->browser(); + if (b) { + // don't disable up: a press forces stack-up extending... + _paForward->setEnabled(b->canGoForward()); + _paBack->setEnabled(b->canGoBack()); + } + +#if TRACE_UPDATES + qDebug("TopLevel::setFunction(%s), lastSender %s", + f ? f->prettyName().ascii() : "0", + _lastSender ? _lastSender->name() :"0" ); +#endif + + return true; +} + + +/** + * Delayed versions. + * We always have a pair of slots: One receiver to start the + * delay with a singleShot Timer. It stores the parameter into a + * temporary variable. And one parameterless slot for + * forwarding, using this temporary. + */ +void TopLevel::setCostTypeDelayed(TraceCostType* ct) +{ + _costTypeDelayed = ct; + QTimer::singleShot (0, this, SLOT(setCostTypeDelayed())); +} + +void TopLevel::setCostType2Delayed(TraceCostType* ct) +{ + _costType2Delayed = ct; + QTimer::singleShot (0, this, SLOT(setCostType2Delayed())); +} + +void TopLevel::setCostTypeDelayed() +{ + setCostType(_costTypeDelayed); +} + +void TopLevel::setCostType2Delayed() +{ + setCostType2(_costType2Delayed); +} + +void TopLevel::setGroupTypeDelayed(TraceItem::CostType gt) +{ + _groupTypeDelayed = gt; + QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed())); +} + +void TopLevel::setGroupTypeDelayed() +{ + setGroupType(_groupTypeDelayed); +} + +void TopLevel::setGroupDelayed(TraceCostItem* g) +{ +#if TRACE_UPDATES + qDebug("TopLevel::setGroupDelayed(%s), sender %s", + g ? g->prettyName().ascii() : "0", + _lastSender ? _lastSender->name() :"0" ); +#endif + + _groupDelayed = g; + QTimer::singleShot (0, this, SLOT(setGroupDelayed())); +} + +void TopLevel::setGroupDelayed() +{ + setGroup(_groupDelayed); +} + +void TopLevel::setDirectionDelayed(TraceItemView::Direction d) +{ + _directionDelayed = d; + QTimer::singleShot (0, this, SLOT(setDirectionDelayed())); +} + +void TopLevel::setDirectionDelayed() +{ + switch(_directionDelayed) { + case TraceItemView::Back: + _stackSelection->browserBack(); + break; + + case TraceItemView::Forward: + _stackSelection->browserForward(); + break; + + case TraceItemView::Up: + { + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f = hi ? hi->function() : 0; + + if (!f) break; + f = hi->stack()->caller(f, false); + if (f) setFunction(f); + } + break; + + default: break; + } + + _directionDelayed = TraceItemView::None; +} + + +void TopLevel::setTraceItemDelayed(TraceItem* i) +{ + // no need to select same item a 2nd time... + if (_traceItemDelayed == i) return; + _traceItemDelayed = i; + _lastSender = sender(); + + kdDebug() << "Selected " << (i ? i->prettyName() : "(none)") << endl; + +#if TRACE_UPDATES + qDebug("TopLevel::setTraceItemDelayed(%s), sender %s", + i ? i->prettyName().ascii() : "0", + _lastSender ? _lastSender->name() :"0" ); +#endif + + QTimer::singleShot (0, this, SLOT(setTraceItemDelayed())); +} + +void TopLevel::setTraceItemDelayed() +{ + if (!_traceItemDelayed) return; + + switch(_traceItemDelayed->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + setFunction((TraceFunction*)_traceItemDelayed); + break; + + case TraceItem::Object: + case TraceItem::File: + case TraceItem::Class: + setGroup((TraceCostItem*)_traceItemDelayed); + break; + +#if 0 + // this conflicts with the selection policy of InstrView ?!? + case TraceItem::Instr: + case TraceItem::Line: + // only for multiview + _multiView->activate(_traceItemDelayed); + _multiView->updateView(); + break; +#endif + + default: break; + } + + _traceItemDelayed = 0; + _lastSender = 0; +} + +/** + * A TraceData object cannot be viewed many times in different + * toplevel windows. Thus, this toplevel window takes ownership + * of the TraceData object: on closing the window or opening + * another trace, the object is destroyed. + */ +void TopLevel::setData(TraceData* data) +{ + if (data == _data) return; + + _lastSender = 0; + + saveTraceSettings(); + + if (_data) { + _partSelection->setData(0); + _stackSelection->setData(0); + + _functionSelection->setData(0); + _functionSelection->updateView(); + _multiView->setData(0); + _multiView->updateView(); + + // we are the owner... + delete _data; + } + + // reset members + init(); + + _data = data; + + // fill cost type list + QStringList types; + + if (_data) { + /* add all supported virtual types */ + TraceCostMapping* m = _data->mapping(); + m->addKnownVirtualTypes(); + + /* first, fill selection list with available cost types */ + for (int i=0;i<m->realCount();i++) + types << m->realType(i)->longName(); + for (int i=0;i<m->virtualCount();i++) + types << m->virtualType(i)->longName(); + } + _saCost->setItems(types); + _saCost->setComboWidth(300); + + if (types.count()>0) { + // second type list gets an additional "(Hidden)" + types.prepend(i18n("(Hidden)")); + } + _saCost2->setItems(types); + _saCost2->setComboWidth(300); + // default is hidden + if (types.count()>0) + _saCost2->setCurrentItem(0); + + _partSelection->setData(_data); + _stackSelection->setData(_data); + _functionSelection->setData(_data); + _functionSelection->updateView(); + _multiView->setData(_data); + _multiView->updateView(); + + /* this is needed to let the other widgets know the types */ + restoreTraceTypes(); + + restoreTraceSettings(); + + QString caption; + if (_data) { + caption = _data->traceName(); + if (!_data->command().isEmpty()) + caption += " [" + _data->command() + "]"; + } + setCaption(caption); + + if (!_data || (!_forcePartDock && _data->parts().count()<2)) { + _partDock->hide(); + _partDockShown->setChecked(false); + } + else { + _partDock->show(); + _partDockShown->setChecked(true); + } + + updateStatusBar(); +} + +void TopLevel::addCostMenu(QPopupMenu* popup, bool withCost2) +{ + if (_data) { + QPopupMenu *popup1 = new QPopupMenu(popup); + QPopupMenu *popup2 = 0; + popup1->setCheckable(true); + + if (withCost2) { + popup2 = new QPopupMenu(popup); + popup2->setCheckable(true); + + if (_costType2) { + popup2->insertItem(i18n("Hide"),199); + popup2->insertSeparator(); + } + } + + TraceCostMapping* m = _data->mapping(); + TraceCostType* ct; + for (int i=0;i<m->realCount();i++) { + ct = m->realType(i); + popup1->insertItem(ct->longName(), 100+i); + if (_costType == ct) popup1->setItemChecked(100+i,true); + if (popup2) { + popup2->insertItem(ct->longName(), 100+i); + if (_costType2 == ct) popup2->setItemChecked(100+i,true); + } + } + for (int i=0;i<m->virtualCount();i++) { + ct = m->virtualType(i); + popup1->insertItem(ct->longName(), 200+i); + if (_costType == ct) popup1->setItemChecked(200+i,true); + if (popup2) { + popup2->insertItem(ct->longName(), 200+i); + if (_costType2 == ct) popup2->setItemChecked(200+i,true); + } + } + popup->insertItem(i18n("Primary Event Type"), popup1); + connect(popup1,SIGNAL(activated(int)),this,SLOT(setCostType(int))); + if (popup2) { + popup->insertItem(i18n("Secondary Event Type"), popup2); + connect(popup2,SIGNAL(activated(int)),this,SLOT(setCostType2(int))); + } + } + if (_showPercentage) + popup->insertItem(i18n("Show Absolute Cost"), + this, SLOT(setAbsoluteCost())); + else + popup->insertItem(i18n("Show Relative Cost"), + this, SLOT(setRelativeCost())); +} + +bool TopLevel::setCostType(int id) +{ + if (!_data) return false; + + TraceCostMapping* m = _data->mapping(); + TraceCostType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->virtualType(id-200); + + return ct ? setCostType(ct) : false; +} + +bool TopLevel::setCostType2(int id) +{ + if (!_data) return false; + + TraceCostMapping* m = _data->mapping(); + TraceCostType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->virtualType(id-200); + + return setCostType2(ct); +} + +void TopLevel::addGoMenu(QPopupMenu* popup) +{ + popup->insertItem(i18n("Go Back"), this, SLOT(goBack())); + popup->insertItem(i18n("Go Forward"), this, SLOT(goForward())); + popup->insertItem(i18n("Go Up"), this, SLOT(goUp())); +} + +void TopLevel::goBack() +{ + setDirectionDelayed(TraceItemView::Back); +} + +void TopLevel::goForward() +{ + setDirectionDelayed(TraceItemView::Forward); +} + +void TopLevel::goUp() +{ + setDirectionDelayed(TraceItemView::Up); +} + +QString TopLevel::traceKey() +{ + if (!_data || _data->command().isEmpty()) return QString::null; + + QString name = _data->command(); + QString key; + for (unsigned int l=0;l<name.length();l++) + if (name[l].isLetterOrNumber()) key += name[l]; + + return QString("-") + key; +} + + +void TopLevel::restoreTraceTypes() +{ + QString key = traceKey(); + + KConfigGroup cConfig(KGlobal::config(), QCString("CurrentState")); + KConfigGroup pConfig(KGlobal::config(), QCString("TracePositions")); + + QString groupType, costType, costType2; + groupType = pConfig.readEntry(QString("GroupType%1").arg(key)); + costType = pConfig.readEntry(QString("CostType%1").arg(key)); + costType2 = pConfig.readEntry(QString("CostType2%1").arg(key)); + + if (groupType.isEmpty()) groupType = cConfig.readEntry("GroupType"); + if (costType.isEmpty()) costType = cConfig.readEntry("CostType"); + if (costType2.isEmpty()) costType2 = cConfig.readEntry("CostType2"); + + setGroupType(groupType); + setCostType(costType); + setCostType2(costType2); + + // if still no cost type set, use first available + if (!_costType && !_saCost->items().isEmpty()) + costTypeSelected(_saCost->items().first()); + + KConfigGroup aConfig(KGlobal::config(), QCString("Layouts")); + _layoutCount = aConfig.readNumEntry(QString("Count%1").arg(key), 0); + _layoutCurrent = aConfig.readNumEntry(QString("Current%1").arg(key), 0); + if (_layoutCount == 0) layoutRestore(); + updateLayoutActions(); +} + + +/** + * This must be called after setting group/cost types in the function + * selection widget, because the group/function choosing depends on + * filled lists in the function selection widget + */ +void TopLevel::restoreTraceSettings() +{ + if (!_data) return; + + QString key = traceKey(); + + KConfigGroup pConfig(KGlobal::config(), QCString("TracePositions")); + QString group = pConfig.readEntry(QString("Group%1").arg(key)); + if (!group.isEmpty()) setGroup(group); + + restoreCurrentState(key); + + // restoreCurrentState() usually leads to a call to setTraceItemDelayed() + // to restore last active item... + if (!_traceItemDelayed) { + // function not available any more.. try with "main" + if (!setFunction("main")) + _functionSelection->setTopFunction(); + } +} + + +/* Layout */ + +void TopLevel::layoutDuplicate() +{ + // save current and allocate a new slot + _multiView->saveViewConfig(KGlobal::config(), + QString("Layout%1-MainView").arg(_layoutCurrent), + traceKey(), false); + _layoutCurrent = _layoutCount; + _layoutCount++; + + updateLayoutActions(); + + kdDebug() << "TopLevel::layoutDuplicate: count " << _layoutCount << endl; +} + +void TopLevel::layoutRemove() +{ + if (_layoutCount <2) return; + + int from = _layoutCount-1; + if (_layoutCurrent == from) { _layoutCurrent--; from--; } + // restore from last and decrement count + _multiView->readViewConfig(KGlobal::config(), + QString("Layout%1-MainView").arg(from), + traceKey(), false); + _layoutCount--; + + updateLayoutActions(); +} + +void TopLevel::layoutNext() +{ + if (_layoutCount <2) return; + + KConfig* config = KGlobal::config(); + QString key = traceKey(); + + _multiView->saveViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + _layoutCurrent++; + if (_layoutCurrent == _layoutCount) _layoutCurrent = 0; + + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + + if (0) kdDebug() << "TopLevel::layoutNext: current " + << _layoutCurrent << endl; +} + +void TopLevel::layoutPrevious() +{ + if (_layoutCount <2) return; + + KConfig* config = KGlobal::config(); + QString key = traceKey(); + + _multiView->saveViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + _layoutCurrent--; + if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1; + + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + + if (0) kdDebug() << "TopLevel::layoutPrevious: current " + << _layoutCurrent << endl; +} + +void TopLevel::layoutSave() +{ + KConfig* config = KGlobal::config(); + QString key = traceKey(); + + _multiView->saveViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + + for(int i=0;i<_layoutCount;i++) { + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(i), + key, false); + _multiView->saveViewConfig(config, + QString("Layout%1-MainView").arg(i), + QString(), false); + } + + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + + KConfigGroup aConfig(config, QCString("Layouts")); + aConfig.writeEntry("DefaultCount", _layoutCount); + aConfig.writeEntry("DefaultCurrent", _layoutCurrent); +} + +void TopLevel::layoutRestore() +{ + KConfig* config = KGlobal::config(); + KConfigGroup aConfig(config, QCString("Layouts")); + _layoutCount = aConfig.readNumEntry("DefaultCount", 0); + _layoutCurrent = aConfig.readNumEntry("DefaultCurrent", 0); + if (_layoutCount == 0) { + _layoutCount++; + return; + } + + QString key = traceKey(); + for(int i=0;i<_layoutCount;i++) { + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(i), + QString(), false); + _multiView->saveViewConfig(config, + QString("Layout%1-MainView").arg(i), + key, false); + } + + _multiView->readViewConfig(config, + QString("Layout%1-MainView").arg(_layoutCurrent), + key, false); + + updateLayoutActions(); +} + + +void TopLevel::updateLayoutActions() +{ + KAction* ka; + + ka = actionCollection()->action("layout_next"); + if (ka) ka->setEnabled(_layoutCount>1); + + ka = actionCollection()->action("layout_previous"); + if (ka) ka->setEnabled(_layoutCount>1); + + ka = actionCollection()->action("layout_remove"); + if (ka) ka->setEnabled(_layoutCount>1); + + _statusbar->message(i18n("Layout Count: %1").arg(_layoutCount), 1000); +} + + +void TopLevel::updateStatusBar() +{ + if (!_data || _data->parts().count()==0) { + _statusLabel->setText(i18n("No profile data file loaded.")); + return; + } + + QString status = QString("%1 [%2] - ") + .arg(_data->shortTraceName()) + .arg(_data->activePartRange()); + + if (_costType) { + status += i18n("Total %1 Cost: %2") + .arg(_costType->longName()) + .arg(_data->prettySubCost(_costType)); + + /* this gets too long... + if (_costType2 && (_costType2 != _costType)) + status += i18n(", %1 Cost: %2") + .arg(_costType2->longName()) + .arg(_data->prettySubCost(_costType2)); + */ + } + else + status += i18n("No event type selected"); + + /* Not working... should give group of selected function + + if (_groupType != TraceItem::Function) { + status += QString(" - %1 '%2'") + .arg(TraceItem::i18nTypeName(_groupType)) + .arg(_group ? _group->prettyName() : i18n("(None)")); + } + */ + + _statusLabel->setText(status); +} + +void TopLevel::configure() +{ + if (ConfigDlg::configure(Configuration::config(), _data, this)) { + Configuration::saveOptions(KGlobal::config()); + + configChanged(); + } + else + Configuration::readOptions(KGlobal::config()); +} + +bool TopLevel::queryClose() +{ + saveTraceSettings(); + + return true; +} + +bool TopLevel::queryExit() +{ + // save current toplevel options as defaults... + Configuration::setShowPercentage(_showPercentage); + Configuration::setShowExpanded(_showExpanded); + Configuration::setShowCycles(_showCycles); + Configuration::saveOptions(KGlobal::config()); + + saveCurrentState(QString::null); + + // save QT dock positions... + + // We don't want to save the KToolbar position here. + // Its already stored. + delete toolBar(); + + KConfigGroup dockConfig(KGlobal::config(), QCString("Docks")); + QString str; + QTextStream ts( &str, IO_WriteOnly ); + ts << *this; +#if 1 + dockConfig.writeEntry("Position", str); +#else + /* We store this with a localized key because for dock positions, + * QT uses the localized captions of docks. + * This way, when changing languages, you don't loose dock position + * settings. + * For the retrieval to work, we need to store a non-localized. + */ + dockConfig.writeEntry("Position", str, true, false, true); +#endif + + // if part dock was chosen visible even for only 1 part loaded, + // keep this choice... + _forcePartDock = false; + if (_data && (_data->parts().count()<2) && _partDock->isVisible()) + _forcePartDock=true; + dockConfig.writeEntry("ForcePartDockVisible", _forcePartDock); + + return true; +} + + +void TopLevel::splitSlot() +{ + int count = _multiView->childCount(); + if (count<1) count = 1; + if (count>2) count = 2; + count = 3-count; + _multiView->setChildCount(count); + + _taSplit->setChecked(count>1); + _taSplitDir->setEnabled(count>1); + _taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal); +} + +void TopLevel::splitDirSlot() +{ + _multiView->setOrientation( _taSplitDir->isChecked() ? + Qt::Horizontal : Qt::Vertical ); +} + + + +// this is called after a config change in the dialog +void TopLevel::configChanged() +{ + //qDebug("TopLevel::configChanged"); + //_showPercentage->setChecked(Configuration::showPercentage()); + + // invalidate found/cached dirs of source files + _data->resetSourceDirs(); + + _partSelection->refresh(); + _stackSelection->refresh(); + + _functionSelection->notifyChange(TraceItemView::configChanged); + _functionSelection->updateView(); + + _multiView->notifyChange(TraceItemView::configChanged); + _multiView->updateView(); +} + +void TopLevel::slotShowTipOnStart() { + KTipDialog::showTip(this); +} + +void TopLevel::slotShowTip() { + KTipDialog::showTip( this, QString::null, true ); +} + +void TopLevel::dummySlot() +{ +} + +void TopLevel::activePartsChangedSlot(const TracePartList& list) +{ + if (!_data) return; + + if (!_data->activateParts(list)) { +// qDebug("TopLevel::activePartsChangedSlot: No Change!"); + return; + } + _activeParts = list; + + _partSelection->activePartsChangedSlot(list); + + _multiView->set(list); + _multiView->updateView(); + + _functionSelection->set(list); + _functionSelection->updateView(); + + _stackSelection->refresh(); + + updateStatusBar(); +} + +void TopLevel::partsHideSelectedSlotDelayed() +{ + QTimer::singleShot( 0, this, SLOT(partsHideSelectedSlot()) ); +} + +// this puts selected parts into hidden list, +// deselects them and makes the remaining parts selected +void TopLevel::partsHideSelectedSlot() +{ + if (!_data) return; + + TracePart* part; + TracePartList newHidden, newActive; + TracePartList l = _data->parts(); + for (part=l.first();part;part=l.next()) { + if ((_activeParts.findRef(part)>=0) || + (_hiddenParts.findRef(part)>=0)) + newHidden.append(part); + else + newActive.append(part); + } + + _hiddenParts = newHidden; + _partSelection->hiddenPartsChangedSlot(_hiddenParts); + +#if 0 + _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); + _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); +#endif + + activePartsChangedSlot(newActive); +} + +void TopLevel::partsUnhideAllSlotDelayed() +{ + QTimer::singleShot( 0, this, SLOT(partsUnhideAllSlot()) ); +} + +// this unhides all hidden parts. Does NOT change selection +void TopLevel::partsUnhideAllSlot() +{ + if (!_data) return; + + _hiddenParts.clear(); + _partSelection->hiddenPartsChangedSlot(_hiddenParts); +#if 0 + _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); + _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); +#endif +} + +void TopLevel::forceTrace() +{ +// qDebug("forceTrace"); + + // Needs Callgrind now... + QFile cmd("callgrind.cmd"); + if (!cmd.exists()) { + cmd.open(IO_WriteOnly); + cmd.writeBlock("DUMP\n", 5); + cmd.close(); + } + if (_taDump->isChecked()) + QTimer::singleShot( 1000, this, SLOT(forceTraceReload()) ); + else { + // cancel request + cmd.remove(); + } + +} + +void TopLevel::forceTraceReload() +{ +// qDebug("forceTraceReload"); + + QFile cmd("callgrind.cmd"); + if (cmd.exists()) { + if (_taDump->isChecked()) + QTimer::singleShot( 1000, this, SLOT(forceTraceReload()) ); + return; + } + _taDump->setChecked(false); + reload(); +} + +void TopLevel::forwardAboutToShow() +{ + QPopupMenu *popup = _paForward->popupMenu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + + if (!hi) { + popup->insertItem(i18n("(No Stack)")); + return; + } + + hi = hi->next(); + if (!hi) { + popup->insertItem(i18n("(No next function)")); + return; + } + + int count = 1; + while (count<Configuration::maxSymbolCount() && hi) { + f = hi->function(); + if (!f) break; + + QString name = f->prettyName(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + + //qDebug("forward: Adding %s", name.ascii()); + popup->insertItem(name, count); + hi = hi->next(); + count++; + } +} + +void TopLevel::backAboutToShow() +{ + QPopupMenu *popup = _paBack->popupMenu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + + if (!hi) { + popup->insertItem(i18n("(No Stack)")); + return; + } + + hi = hi->last(); + if (!hi) { + popup->insertItem(i18n("(No previous function)")); + return; + } + + int count = 1; + while (count<Configuration::maxSymbolCount() && hi) { + f = hi->function(); + if (!f) break; + + QString name = f->prettyName(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + + //qDebug("back: Adding %s", name.ascii()); + popup->insertItem(name, count); + hi = hi->last(); + count++; + } +} + +void TopLevel::upAboutToShow() +{ + QPopupMenu *popup = _paUp->popupMenu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f = hi ? hi->function() : 0; + + if (!f) { + popup->insertItem(i18n("(No Stack)")); + return; + } + f = hi->stack()->caller(f, false); + if (!f) { + popup->insertItem(i18n("(No Function Up)")); + return; + } + + int count = 1; + while (count<Configuration::maxSymbolCount() && f) { + QString name = f->prettyName(); + if ((int)name.length()>Configuration::maxSymbolLength()) + name = name.left(Configuration::maxSymbolLength()) + "..."; + + popup->insertItem(name, count); + f = hi->stack()->caller(f, false); + count++; + } + +} + +void TopLevel::forwardActivated(int id) +{ + //qDebug("forwardActivated: %d", id); + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (id>1) { + b->goForward(); + id--; + } + _stackSelection->browserForward(); +} + +void TopLevel::backActivated(int id) +{ + //qDebug("backActivated: %d", id); + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (id>1) { + b->goBack(); + id--; + } + _stackSelection->browserBack(); +} + +void TopLevel::upActivated(int id) +{ + //qDebug("upActivated: %d", id); + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + if (!hi) return; + + TraceFunction* f = hi->function(); + + while (id>0 && f) { + f = hi->stack()->caller(f, false); + id--; + } + + //qDebug("upActivated: %s", f ? f->prettyName().ascii() : "??" ); + if (f) + setFunction(f); + +} + +void TopLevel::showMessage(const QString& msg, int ms) +{ + if (_statusbar) + _statusbar->message(msg, ms); +} + +void TopLevel::showStatus(QString msg, int progress) +{ + static bool msgUpdateNeeded = true; + + if (msg.isEmpty()) { + if (_progressBar) { + _statusbar->removeWidget(_progressBar); + delete _progressBar; + _progressBar = 0; + } + _statusbar->clear(); + _progressMsg = msg; + return; + } + + if (_progressMsg.isEmpty()) _progressStart.start(); + + if (msg != _progressMsg) { + _progressMsg = msg; + msgUpdateNeeded = true; + } + + // do nothing if last change was less than 0.5 seconds ago + if (_progressStart.elapsed() < 500) return; + + if (!_progressBar) { + _progressBar = new QProgressBar(_statusbar); + _progressBar->setMaximumSize(200, _statusbar->height()-4); + _statusbar->addWidget(_progressBar, 1, true); + _progressBar->show(); + msgUpdateNeeded = true; + } + + _progressStart.restart(); + + if (msgUpdateNeeded) { + _statusbar->message(msg); + msgUpdateNeeded = false; + } + _progressBar->setProgress(progress); + + // let the progress bar update itself +#if (QT_VERSION-0 >= 0x030100) + QEventLoop* l = qApp->eventLoop(); + if (l) l->processEvents(QEventLoop::ExcludeUserInput); +#else + // for Qt 3.0.x. This allows user input and thus potentially races + qApp->processEvents(); +#endif +} + +#include "toplevel.moc" diff --git a/kcachegrind/kcachegrind/toplevel.h b/kcachegrind/kcachegrind/toplevel.h new file mode 100644 index 00000000..8662768e --- /dev/null +++ b/kcachegrind/kcachegrind/toplevel.h @@ -0,0 +1,274 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * KCachegrind top level window + */ + +#ifndef TOPLEVEL_H +#define TOPLEVEL_H + +#include <qdatetime.h> + +#include <dcopobject.h> +#include <kmainwindow.h> + +#include "traceitemview.h" +#include "tracedata.h" + +class MultiView; +class QLineEdit; +class QDockWidget; +class QLabel; +class QProgressBar; +class QPopupMenu; + +class KURL; +class KSelectAction; +class KToggleAction; +class KToolBarPopupAction; + +class TraceData; +class KRecentFilesAction; +class MainWidget; +class PartSelection; +class FunctionSelection; +class DumpSelection; +class StackSelection; +class TraceFunction; + +class TopLevel : public KMainWindow, public DCOPObject +{ + Q_OBJECT + +public: + TopLevel(const char *name = 0); + ~TopLevel(); + + TraceData* data() { return _data; } + void setData(TraceData*); + + virtual void saveProperties(KConfig*); + virtual void readProperties(KConfig*); + + void createActions(); + void createDocks(); + + TraceItem::CostType groupType() { return _groupType; } + TraceCostType* costType() { return _costType; } + TraceCostType* costType2() { return _costType2; } + TracePartList activeParts() { return _activeParts; } + TracePartList hiddenParts() { return _hiddenParts; } + + // current config + bool showPercentage() const { return _showPercentage; } + bool showExpanded() const { return _showExpanded; } + bool showCycles() const { return _showCycles; } + + /* convenience functions for often used context menu items */ + void addCostMenu(QPopupMenu*,bool); + void addGoMenu(QPopupMenu*); + +public slots: + void newTrace(); + void loadTrace(); + void loadTrace(const KURL&); + void loadTrace(QString); + void addTrace(); + void addTrace(const KURL&); + void addTrace(QString); + + // for quick showing the main window... + void loadDelayed(QString); + + void reload(); + void exportGraph(); + void newWindow(); + void configure(); + void querySlot(); + void dummySlot(); + + // layouts + void layoutDuplicate(); + void layoutRemove(); + void layoutNext(); + void layoutPrevious(); + void layoutSave(); + void layoutRestore(); + void updateLayoutActions(); + + void updateStatusBar(); + void costTypeSelected(const QString&); + void costType2Selected(const QString&); + void groupTypeSelected(int); + void splitSlot(); + void splitDirSlot(); + void configureToolbars(); + void configureKeys(); + bool queryExit(); + bool queryClose(); + void togglePartDock(); + void toggleStackDock(); + void toggleFunctionDock(); + void toggleDumpDock(); + void toggleStatusBar(); + void partVisibilityChanged(bool); + void dumpVisibilityChanged(bool); + void stackVisibilityChanged(bool); + void functionVisibilityChanged(bool); + void togglePercentage(); + void setPercentage(bool); + void setAbsoluteCost(); + void setRelativeCost(); + void toggleExpanded(); + void toggleCycles(); + void forceTrace(); + void forceTraceReload(); + void forwardAboutToShow(); + void backAboutToShow(); + void upAboutToShow(); + void forwardActivated(int); + void backActivated(int); + void upActivated(int); + + bool setCostType(TraceCostType*); + bool setCostType2(TraceCostType*); + bool setCostType(QString); + bool setCostType2(QString); + bool setCostType(int); + bool setCostType2(int); + bool setGroupType(TraceItem::CostType); + bool setGroupType(QString); + bool setGroup(TraceCostItem*); + bool setGroup(QString); + bool setFunction(TraceFunction*); + bool setFunction(QString); + void activePartsChangedSlot(const TracePartList& list); + void partsHideSelectedSlot(); + void partsUnhideAllSlot(); + + /* These go back to mainloop first by using a timer. + * So they can be called from event handlers that + * aren't allowed to delete list entries. + */ + void setCostTypeDelayed(TraceCostType*); + void setCostType2Delayed(TraceCostType*); + void setGroupTypeDelayed(TraceItem::CostType); + void setGroupDelayed(TraceCostItem*); + void setTraceItemDelayed(TraceItem*); + void partsHideSelectedSlotDelayed(); + void partsUnhideAllSlotDelayed(); + void goBack(); + void goForward(); + void goUp(); + void setDirectionDelayed(TraceItemView::Direction); + + /* SingleShot Slots (without parameters) for the delayed versions */ + void setCostTypeDelayed(); + void setCostType2Delayed(); + void setGroupTypeDelayed(); + void setGroupDelayed(); + void setTraceItemDelayed(); + void loadTraceDelayed(); + void setDirectionDelayed(); + + // configuration has changed + void configChanged(); + + //void refresh(); + void slotShowTipOnStart(); + void slotShowTip(); + + // progress in status bar, empty message disables progress display + void showStatus(QString msg, int progress); + void showMessage(const QString&, int msec); + +private: + void init(); + void createLayoutActions(); + void createMiscActions(); + void setupMainWidget(MainWidget*); + void setupPartSelection(PartSelection*); + void restoreCurrentState(QString postfix); + void saveCurrentState(QString postfix); + void saveTraceSettings(); + QString traceKey(); + void restoreTraceTypes(); + void restoreTraceSettings(); + + KStatusBar* _statusbar; + QLabel* _statusLabel; + KRecentFilesAction* _openRecent; + bool _twoMainWidgets; + Orientation _spOrientation; + + MultiView* _multiView; + FunctionSelection* _functionSelection; + DumpSelection* _dumpSelection; + PartSelection* _partSelection; + StackSelection* _stackSelection; + QLineEdit* queryLineEdit; + + QDockWindow *_partDock, *_stackDock, *_functionDock, *_dumpDock; + bool _forcePartDock; + + KSelectAction *_saCost, *_saCost2, *saGroup; + KToggleAction *_partDockShown, *_stackDockShown; + KToggleAction *_functionDockShown, *_dumpDockShown; + KToggleAction *_taPercentage, *_taExpanded, *_taCycles; + KToggleAction *_taDump, *_taSplit, *_taSplitDir; + KToolBarPopupAction *_paForward, *_paBack, *_paUp; + + TraceFunction* _function; + const QObject* _lastSender; + + // trace data shown in this window + TraceData* _data; + // subcost types used for visualisation + TraceCostType* _costType; + TraceCostType* _costType2; + // grouping of function list + TraceItem::CostType _groupType; + // selected group + TraceCostItem* _group; + // selected parts + TracePartList _activeParts; + // hidden parts + TracePartList _hiddenParts; + // layouts + int _layoutCurrent, _layoutCount; + + // for delayed slots + TraceCostType* _costTypeDelayed; + TraceCostType* _costType2Delayed; + TraceItem::CostType _groupTypeDelayed; + TraceCostItem* _groupDelayed; + TraceItem* _traceItemDelayed; + QString _loadTraceDelayed; + TraceItemView::Direction _directionDelayed; + + // for status progress display + QString _progressMsg; + QTime _progressStart; + QProgressBar* _progressBar; + + // toplevel configuration options + bool _showPercentage, _showExpanded, _showCycles; +}; + +#endif diff --git a/kcachegrind/kcachegrind/tracedata.cpp b/kcachegrind/kcachegrind/tracedata.cpp new file mode 100644 index 00000000..85f4f2e1 --- /dev/null +++ b/kcachegrind/kcachegrind/tracedata.cpp @@ -0,0 +1,5072 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + + +#include <stdlib.h> + +#include <qfile.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kdebug.h> + +#include "tracedata.h" +#include "toplevel.h" +#include "loader.h" +#include "configuration.h" +#include "utils.h" +#include "fixcost.h" + + +#define TRACE_DEBUG 0 +#define TRACE_ASSERTIONS 0 + +const int TraceCost::MaxRealIndex = MaxRealIndexValue; +const int TraceCost::InvalidIndex = -1; + +//--------------------------------------------------- +// Addr + +bool Addr::set(FixString& s) +{ + return s.stripUInt64(_v); +} + +int Addr::set(const char *s) +{ + int n = 0; + _v = 0; + + while((n<16) && *s) { + if ((*s>='0') && (*s<='9')) + _v = 16*_v + (*s-'0'); + else if ((*s>='a') && (*s<='f')) + _v = 16*_v + 10 + (*s-'a'); + else if ((*s>='A') && (*s<='F')) + _v = 16*_v + 10 + (*s-'A'); + else break; + s++; + n++; + } + + return n; +} + + +QString Addr::toString() const +{ + if (_v == 0) return QString("0"); + + uint64 n = _v; + QString hex; +#if (QT_VERSION-0 >= 0x030200) + hex.reserve(16); +#endif + + while(n>0) { + int d = (n & 15); + hex = QChar((d<10) ? ('0'+d) : ('A'-10+d)) + hex; + n /= 16; + } + + return hex; +} + +QString Addr::pretty() const +{ + if (_v == 0) return QString("0"); + + uint64 n = _v; + int p = 0; + QString hex; +#if (QT_VERSION-0 >= 0x030200) + hex.reserve(20); +#endif + + while(n>0) { + int d = (n & 15); + if ((p>0) && ((p%4)==0)) hex = " " + hex; + hex = QChar((d<10) ? ('0'+d) : ('A'-10+d)) + hex; + n /= 16; + p++; + } + + return hex; +} + +bool Addr::isInRange(Addr a, int distance) +{ + uint64 diff = (a._v > _v) ? (a._v - _v) : (_v - a._v); + uint64 dist = (distance<0) ? distance : -distance; + return (diff < dist); +} + +//--------------------------------------------------- +// TraceItem + +QString* TraceItem::_typeName = 0; +QString* TraceItem::_i18nTypeName = 0; + +TraceItem::TraceItem() +{ + _position = 0; + _dep = 0; + _dirty = true; +} + +TraceItem::~TraceItem() +{} + +void TraceItem::cleanup() +{ + if (_typeName) { + delete [] _typeName; + _typeName = 0; + } + if (_i18nTypeName) { + delete [] _i18nTypeName; + _i18nTypeName = 0; + } +} + +QString TraceItem::typeName(CostType t) +{ + if (!_typeName) { + _typeName = new QString [MaxCostType+1]; + QString* strs = _typeName; + for(int i=0;i<=MaxCostType;i++) + strs[i] = QString("?"); + + strs[Item] = I18N_NOOP("Abstract Item"); + strs[Cost] = I18N_NOOP("Cost Item"); + strs[PartLine] = I18N_NOOP("Part Source Line"); + strs[Line] = I18N_NOOP("Source Line"); + strs[PartLineCall] = I18N_NOOP("Part Line Call"); + strs[LineCall] = I18N_NOOP("Line Call"); + strs[PartLineJump] = I18N_NOOP("Part Jump"); + strs[LineJump] = I18N_NOOP("Jump"); + strs[PartInstr] = I18N_NOOP("Part Instruction"); + strs[Instr] = I18N_NOOP("Instruction"); + strs[PartInstrJump] = I18N_NOOP("Part Instruction Jump"); + strs[InstrJump] = I18N_NOOP("Instruction Jump"); + strs[PartInstrCall] = I18N_NOOP("Part Instruction Call"); + strs[InstrCall] = I18N_NOOP("Instruction Call"); + strs[PartCall] = I18N_NOOP("Part Call"); + strs[Call] = I18N_NOOP("Call"); + strs[PartFunction] = I18N_NOOP("Part Function"); + strs[FunctionSource] = I18N_NOOP("Function Source File"); + strs[Function] = I18N_NOOP("Function"); + strs[FunctionCycle] = I18N_NOOP("Function Cycle"); + strs[PartClass] = I18N_NOOP("Part Class"); + strs[Class] = I18N_NOOP("Class"); + strs[PartFile] = I18N_NOOP("Part Source File"); + strs[File] = I18N_NOOP("Source File"); + strs[PartObject] = I18N_NOOP("Part ELF Object"); + strs[Object] = I18N_NOOP("ELF Object"); + strs[Part] = I18N_NOOP("Profile Part"); + strs[Data] = I18N_NOOP("Program Trace"); + } + if (t<0 || t> MaxCostType) t = MaxCostType; + return _typeName[t]; +} + +TraceItem::CostType TraceItem::costType(QString s) +{ + // This is the default cost Type + if (s.isEmpty()) return Function; + + CostType type; + for (int i=0; i<MaxCostType;i++) { + type = (CostType) i; + if (typeName(type) == s) + return type; + } + return NoCostType; +} + +// all strings of typeName() are translatable because of I18N_NOOP there +QString TraceItem::i18nTypeName(CostType t) +{ + if (!_i18nTypeName) { + _i18nTypeName = new QString [MaxCostType+1]; + for(int i=0;i<=MaxCostType;i++) + _i18nTypeName[i] = i18n(typeName((CostType)i).utf8().data()); + } + if (t<0 || t> MaxCostType) t = MaxCostType; + return _i18nTypeName[t]; +} + +TraceItem::CostType TraceItem::i18nCostType(QString s) +{ + // This is the default cost Type + if (s.isEmpty()) return Function; + + CostType type; + for (int i=0; i<MaxCostType;i++) { + type = (CostType) i; + if (i18nTypeName(type) == s) + return type; + } + return NoCostType; +} + + +void TraceItem::clear() +{ + invalidate(); +} + + +QString TraceItem::costString(TraceCostMapping*) +{ + return QString("(no cost)"); +} + +QString TraceItem::name() const +{ + if (part()) { + return i18n("%1 from %2") + .arg(_dep->name()) + .arg(part()->name()); + } + + if (_dep) + return _dep->name(); + + return i18n("(unknown)"); +} + +QString TraceItem::prettyName() const +{ + if (name().isEmpty()) return i18n("(unknown)"); + return name(); +} + + +QString TraceItem::fullName() const +{ + return QString("%1 %2") + .arg(typeName(type())).arg(prettyName()); +} + +QString TraceItem::toString() +{ + return QString("%1\n [%3]").arg(fullName()).arg(costString(0)); +} + +void TraceItem::invalidate() +{ + if (_dirty) return; + _dirty = true; + + if (_dep) + _dep->invalidate(); +} + +void TraceItem::update() +{ + _dirty = false; +} + +TracePart* TraceItem::part() +{ + return _position ? _position->part() : 0; +} + +const TracePart* TraceItem::part() const +{ + return _position ? _position->part() : 0; +} + +TraceData* TraceItem::data() +{ + return _position ? _position->data() : 0; +} + +const TraceData* TraceItem::data() const +{ + return _position ? _position->data() : 0; +} + + +//--------------------------------------------------- +// TraceCost + +TraceCost::TraceCost() + : TraceItem() +{ + _cachedType = 0; // no virtual value cached + + TraceCost::clear(); +} + +TraceCost::~TraceCost() +{} + + +void TraceCost::clear() +{ + // simple set usage count to 0 + _count = 0; + + TraceItem::clear(); +} + + + +void TraceCost::set(TraceSubMapping* sm, const char* s) +{ + if (!sm) return; + if (!s) { + if (_count>0) clear(); + return; + } + + while(*s == ' ') s++; + + if (sm->isIdentity()) { + int i = 0; + while(i<sm->count()) { + if (!_cost[i].set(&s)) break; + i++; + } + _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + index = sm->realIndex(i); + if (maxIndex<index) maxIndex=index; + if (index == TraceCost::InvalidIndex) break; + if (!_cost[index].set(&s)) break; + i++; + } + // we have to set all costs of unused indexes till maxIndex to zero + for(i=sm->firstUnused(); i<=maxIndex; i=sm->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex; + } + // a cost change has to be propagated (esp. in subclasses) + invalidate(); +} + +void TraceCost::set(TraceSubMapping* sm, FixString & s) +{ + if (!sm) return; + + s.stripSpaces(); + + if (sm->isIdentity()) { + int i = 0; + while(i<sm->count()) { + if (!s.stripUInt64(_cost[i])) break; + i++; + } + _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + index = sm->realIndex(i); + if (maxIndex<index) maxIndex=index; + if (index == TraceCost::InvalidIndex) break; + if (!s.stripUInt64(_cost[index])) break; + i++; + } + // we have to set all costs of unused indexes till maxIndex to zero + for(i=sm->firstUnused(); i<=maxIndex; i=sm->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + invalidate(); +} + + +void TraceCost::addCost(TraceSubMapping* sm, const char* s) +{ + if (!sm || !s) return; + + SubCost v; + + if (sm->isIdentity()) { + int i = 0; + while(i<sm->count()) { + if (!v.set(&s)) break; + if (i<_count) + _cost[i] += v; + else + _cost[i] = v; + i++; + } + if (i > _count) _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + if (!v.set(&s)) break; + index = sm->realIndex(i); + if (maxIndex<index) maxIndex=index; + if (index == TraceCost::InvalidIndex) break; + if (index<_count) + _cost[index] += v; + else + _cost[index] = v; + i++; + } + if (maxIndex >= _count) { + /* we have to set all costs of unused indexes in the interval + * [_count;maxIndex] to zero */ + for(i=sm->nextUnused(_count-1); i<=maxIndex; i=sm->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + // a cost change has to be propagated (esp. in subclasses) + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // don't recurse ! + qDebug("%s\n now %s", fullName().ascii(), + TraceCost::costString(0).ascii()); + _dirty = true; // because of invalidate() +#endif +} + +void TraceCost::addCost(TraceSubMapping* sm, FixString & s) +{ + if (!sm) return; + + s.stripSpaces(); + + SubCost v; + + if (sm->isIdentity()) { + int i = 0; + while(i<sm->count()) { + if (!s.stripUInt64(v)) break; + if (i<_count) + _cost[i] += v; + else + _cost[i] = v; + i++; + } + if (i > _count) _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + if (!s.stripUInt64(v)) break; + index = sm->realIndex(i); + if (maxIndex<index) maxIndex=index; + if (index == TraceCost::InvalidIndex) break; + if (index<_count) + _cost[index] += v; + else + _cost[index] = v; + i++; + } + if (maxIndex >= _count) { + /* we have to set all costs of unused indexes in the interval + * [_count;maxIndex] to zero */ + for(i=sm->nextUnused(_count-1); i<=maxIndex; i=sm->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // don't recurse ! + qDebug("%s\n now %s", fullName().ascii(), + TraceCost::costString(0).ascii()); + _dirty = true; // because of invalidate() +#endif +} + + +// update each subcost to be maximum of old and given costs +void TraceCost::maxCost(TraceSubMapping* sm, FixString & s) +{ + if (!sm) return; + + s.stripSpaces(); + + SubCost v; + + if (sm->isIdentity()) { + int i = 0; + while(i<sm->count()) { + if (!s.stripUInt64(v)) break; + if (i<_count) { + if (v>_cost[i]) _cost[i] = v; + } + else + _cost[i] = v; + i++; + } + if (i > _count) _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + if (!s.stripUInt64(v)) break; + index = sm->realIndex(i); + if (maxIndex<index) maxIndex=index; + if (index == TraceCost::InvalidIndex) break; + if (index<_count) { + if (v>_cost[index]) _cost[index] = v; + } + else + _cost[index] = v; + i++; + } + if (maxIndex >= _count) { + /* we have to set all costs of unused indexes in the interval + * [_count;maxIndex] to zero */ + for(i=sm->nextUnused(_count-1); i<=maxIndex; i=sm->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // don't recurse ! + qDebug("%s\n now %s", fullName().ascii(), + TraceCost::costString(0).ascii()); + _dirty = true; // because of invalidate() +#endif +} + + +void TraceCost::addCost(TraceCost* item) +{ + int i; + + if (!item) return; + + // we have to update the other item if needed + // because we access the item costs directly + if (item->_dirty) item->update(); + + if (item->_count < _count) { + for (i = 0; i<item->_count; i++) + _cost[i] += item->_cost[i]; + } + else { + for (i = 0; i<_count; i++) + _cost[i] += item->_cost[i]; + for (; i<item->_count; i++) + _cost[i] = item->_cost[i]; + _count = item->_count; + } + + // a cost change has to be propagated (esp. in subclasses) + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // don't recurse ! + qDebug("%s added cost item\n %s\n now %s", + fullName().ascii(), item->fullName().ascii(), + TraceCost::costString(0).ascii()); + _dirty = true; // because of invalidate() +#endif +} + +void TraceCost::maxCost(TraceCost* item) +{ + int i; + + if (!item) return; + + // we have to update the other item if needed + // because we access the item costs directly + if (item->_dirty) item->update(); + + if (item->_count < _count) { + for (i = 0; i<item->_count; i++) + if (_cost[i] < item->_cost[i]) _cost[i] = item->_cost[i]; + } + else { + for (i = 0; i<_count; i++) + if (_cost[i] < item->_cost[i]) _cost[i] = item->_cost[i]; + for (; i<item->_count; i++) + _cost[i] = item->_cost[i]; + _count = item->_count; + } + + // a cost change has to be propagated (esp. in subclasses) + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // don't recurse ! + qDebug("%s added cost item\n %s\n now %s", + fullName().ascii(), item->fullName().ascii(), + TraceCost::costString(0).ascii()); + _dirty = true; // because of invalidate() +#endif +} + +void TraceCost::addCost(int type, SubCost value) +{ + if (type<0 || type>=MaxRealIndex) return; + if (type<_count) + _cost[type] += value; + else { + for(int i=_count;i<type;i++) + _cost[i] = 0; + _cost[type] = value; + _count = type+1; + } + + // a cost change has to be propagated (esp. in subclasses) + invalidate(); +} + +void TraceCost::maxCost(int type, SubCost value) +{ + if (type<0 || type>=MaxRealIndex) return; + if (type<_count) { + if (value>_cost[type]) _cost[type] = value; + } + else { + for(int i=_count;i<type;i++) + _cost[i] = 0; + _cost[type] = value; + _count = type+1; + } + + // a cost change has to be propagated (esp. in subclasses) + invalidate(); +} + + +TraceCost TraceCost::diff(TraceCost* item) +{ + TraceCost res; + + // we have to update the other item if needed + // because we access the item costs directly + if (item->_dirty) item->update(); + + int maxCount = (item->_count > _count) ? item->_count : _count; + + res._count = maxCount; + for (int i=0; i<maxCount;i++) + res._cost[i] = item->subCost(i) - subCost(i); + + return res; +} + +QString TraceCost::costString(TraceCostMapping* m) +{ + QString res; + + if (_dirty) update(); + + int maxIndex = m ? m->realCount() : TraceCost::MaxRealIndex; + for (int i = 0; i<maxIndex; i++) { + if (!res.isEmpty()) res += ", "; + if (m) res += m->type(i)->name() + " "; + + res += subCost(i).pretty(); + } + return res; +} + + +void TraceCost::invalidate() +{ + if (_dirty) return; + _dirty = true; + _cachedType = 0; // cached value is invalid, too + + if (_dep) + _dep->invalidate(); +} + +void TraceCost::update() +{ + _dirty = false; +} + +// this is only for real types +SubCost TraceCost::subCost(int idx) +{ + if (idx<0) return 0; + + /* update if needed as cost could be calculated dynamically in subclasses + * this can change _count !! */ + if (_dirty) update(); + if (idx>=_count) return 0; + + return _cost[idx]; +} + +SubCost TraceCost::subCost(TraceCostType* t) +{ + if (!t) return 0; + if (_cachedType != t) { + _cachedType = t; + _cachedCost = t->subCost(this); + } + return _cachedCost; +} + +QString TraceCost::prettySubCost(TraceCostType* t) +{ + return subCost(t).pretty(); +} + + + +//--------------------------------------------------- +// TraceJumpCost + +TraceJumpCost::TraceJumpCost() + :TraceItem() +{ + TraceJumpCost::clear(); +} + +TraceJumpCost::~TraceJumpCost() +{} + +SubCost TraceJumpCost::executedCount() +{ + if (_dirty) update(); + + return _executedCount; +} + +SubCost TraceJumpCost::followedCount() +{ + if (_dirty) update(); + + return _followedCount; +} + +QString TraceJumpCost::costString(TraceCostMapping*) +{ + if (_dirty) update(); + + return QString("%1/%2") + .arg(_followedCount.pretty()) + .arg(_executedCount.pretty()); +} + +void TraceJumpCost::clear() +{ + _followedCount = 0; + _executedCount = 0; +} + +void TraceJumpCost::addCost(TraceJumpCost* item) +{ + if (item->_dirty) item->update(); + + _followedCount += item->followedCount(); + _executedCount += item->executedCount(); +} + + +//--------------------------------------------------- +// TraceCostType + +QPtrList<TraceCostType>* TraceCostType::_knownTypes = 0; + +TraceCostType::TraceCostType(QString name, QString longName, QString formula) +{ + _name = name; + _longName = longName; + _formula = formula; + _mapping = 0; + _realIndex = TraceCost::InvalidIndex; + _parsed = false; + _inParsing = false; + + for (int i=0; i<TraceCost::MaxRealIndex;i++) + _coefficient[i] = 0; +} + +void TraceCostType::setFormula(QString formula) +{ + _formula = formula; + _realIndex = TraceCost::InvalidIndex; + _parsed = false; +} + +void TraceCostType::setMapping(TraceCostMapping* m) +{ + _parsed = false; + _mapping = m; +} + +// setting the index to TraceCost::MaxRealIndex makes it a +// real type with unspecified index +void TraceCostType::setRealIndex(int i) +{ + if (i<0 || i>TraceCost::MaxRealIndex) + i=TraceCost::InvalidIndex; + + _realIndex = i; + _formula = QString::null; +} + +// checks for existing types and sets coefficients +bool TraceCostType::parseFormula() +{ + if (_parsed) return true; + if (_inParsing) { + qDebug("TraceCostType::parseFormula: Recursion detected."); + return false; + } + + if (!_mapping) { + qDebug("TraceCostType::parseFormula: No mapping set!"); + return false; + } + + _inParsing = true; + + for (int i=0; i<TraceCost::MaxRealIndex;i++) + _coefficient[i] = 0; + + QRegExp rx( "((?:\\+|\\-)?)\\s*(\\d*)\\s*\\*?\\s*(\\w+)" ); + + int factor, pos; + QString costName; + TraceCostType* costType; + + pos = 0; + while (1) { + pos = rx.search(_formula, pos); + if (pos<0) break; + pos += rx.matchedLength(); + if (rx.cap(0).isEmpty()) break; + + //qDebug("parseFormula: matched '%s','%s','%s'", + // rx.cap(1).ascii(), rx.cap(2).ascii(), rx.cap(3).ascii()); + + costName = rx.cap(3); + costType = _mapping->type(costName); + if (!costType) { + // qDebug("Cost type '%s': In formula cost '%s' unknown.", + // _name.ascii(), costName.ascii()); + + _inParsing = false; + return false; + } + + factor = (rx.cap(2).isEmpty()) ? 1 : rx.cap(2).toInt(); + if (rx.cap(1) == "-") factor = -factor; + + if (costType->isReal()) + _coefficient[costType->realIndex()] += factor; + else { + costType->parseFormula(); + for (int i=0; i<TraceCost::MaxRealIndex;i++) + _coefficient[i] += factor * costType->_coefficient[i]; + } + } + + _inParsing = false; + _parsed = true; + + return true; +} + +QString TraceCostType::parsedFormula() +{ + QString res; + + if (!parseFormula()) return res; + + for (int i=0; i<TraceCost::MaxRealIndex;i++) { + int c = _coefficient[i]; + if (c == 0) continue; + + if (!res.isEmpty()) { + res += " "; + if (c>0) res += "+ "; + } + if (c<0) { res += "- "; c = -c; } + res += QString::number(c); + + TraceCostType* t = _mapping->type(i); + if (!t) continue; + + if (!t->name().isEmpty()) + res += QString(" * %1").arg(t->name()); + } + + return res; +} + +SubCost TraceCostType::subCost(TraceCost* c) +{ + if (_realIndex != TraceCost::InvalidIndex) + return c->subCost(_realIndex); + + if (!_parsed) { + if (!parseFormula()) return 0; + } + SubCost res = 0; + + int rc = _mapping->realCount(); + for (int i = 0;i<rc;i++) + if (_coefficient[i] != 0) + res += _coefficient[i] * c->subCost(i); + + return res; +} + +int TraceCostType::histCost(TraceCost* c, double total, double* hist) +{ + if (total == 0.0) return 0; + + if (!_parsed) { + if (!parseFormula()) return 0; + } + + int rc = _mapping->realCount(); + for (int i = 0;i<rc;i++) { + if (_coefficient[i] != 0) + hist[i] = _coefficient[i] * c->subCost(i) / total; + else + hist[i] = 0.0; + } + + return rc; +} + + + + +TraceCostType* TraceCostType::knownRealType(QString n) +{ + if (!_knownTypes) return 0; + + TraceCostType* t; + for (t=_knownTypes->first();t;t=_knownTypes->next()) + if (t->isReal() && (t->name() == n)) { + TraceCostType* type = new TraceCostType(*t); + return type; + } + + return 0; +} + +TraceCostType* TraceCostType::knownVirtualType(QString n) +{ + if (!_knownTypes) return 0; + + TraceCostType* t; + for (t=_knownTypes->first();t;t=_knownTypes->next()) + if (!t->isReal() && (t->name() == n)) { + TraceCostType* type = new TraceCostType(*t); + return type; + } + + return 0; +} + +// we take ownership +void TraceCostType::add(TraceCostType* t) +{ + if (!t) return; + + t->setMapping(0); + + if (!_knownTypes) + _knownTypes = new QPtrList<TraceCostType>; + + /* Already known? */ + TraceCostType* kt; + for (kt=_knownTypes->first();kt;kt=_knownTypes->next()) + if (kt->name() == t->name()) break; + + if (kt) { + // Overwrite old type + if (!t->longName().isEmpty() && + (t->longName() != t->name())) kt->setLongName(t->longName()); + if (!t->formula().isEmpty()) kt->setFormula(t->formula()); + + delete t; + } + else { + if (t->longName().isEmpty()) t->setLongName(t->name()); + _knownTypes->append(t); + } +} + + +int TraceCostType::knownTypeCount() +{ + if (!_knownTypes) return 0; + + return _knownTypes->count(); +} + +bool TraceCostType::remove(QString n) +{ + if (!_knownTypes) return false; + + TraceCostType* t; + for (t=_knownTypes->first();t;t=_knownTypes->next()) + if (!t->isReal() && (t->name() == n)) { + _knownTypes->removeRef(t); + delete t; + return true; + } + + return false; +} + +TraceCostType* TraceCostType::knownType(int i) +{ + if (!_knownTypes) return 0; + if (i<0 || i>=(int)_knownTypes->count()) return 0; + + return _knownTypes->at(i); +} + +QColor TraceCostType::color() +{ + if (!_mapping) return QColor(); + return _mapping->realColors()[_realIndex]; +} + + +//--------------------------------------------------- +// TraceCostMapping + +TraceCostMapping::TraceCostMapping() +{ + _realCount = 0; + _virtualCount = 0; + for (int i=0;i<TraceCost::MaxRealIndex;i++) _real[i] = 0; + for (int i=0;i<TraceCost::MaxRealIndex;i++) _virtual[i] = 0; +} + +TraceCostMapping::~TraceCostMapping() +{ + for (int i=0;i<TraceCost::MaxRealIndex;i++) + if (_real[i]) delete _real[i]; + + for (int i=0;i<TraceCost::MaxRealIndex;i++) + if (_virtual[i]) delete _virtual[i]; +} + +TraceSubMapping* TraceCostMapping::subMapping(QString types, bool create) +{ + // first check if there's enough space in the mapping + int newCount = 0; + int pos = 0, pos2, len = types.length(); + + while (1) { + // skip space + while((pos<len) && types[pos].isSpace()) pos++; + + pos2 = pos; + while((pos2<len) && !types[pos2].isSpace()) pos2++; + if (pos2 == pos) break; + + if (realIndex(types.mid(pos,pos2-pos)) == TraceCost::InvalidIndex) + newCount++; + + pos = pos2; + } + + if (!create && (newCount>0)) return 0; + + if (newCount+_realCount > TraceCost::MaxRealIndex) { + kdDebug() << "TraceCostMapping::subMapping: No space for " + << newCount << " sub costs." << endl; + return 0; + } + + TraceSubMapping* sm = new TraceSubMapping(this); + + pos = 0; + while (1) { + // skip space + while((pos<len) && types[pos].isSpace()) pos++; + + pos2 = pos; + while((pos2<len) && !types[pos2].isSpace()) pos2++; + if (pos2 == pos) break; + + sm->append(addReal(types.mid(pos,pos2-pos))); + + pos = pos2; + } + + return sm; +} + + +int TraceCostMapping::addReal(QString t) +{ + int index = realIndex(t); + if (index>=0) return index; + + TraceCostType* ct = TraceCostType::knownRealType(t); + if (!ct) ct = new TraceCostType(t, t); + + // make it real + ct->setRealIndex(); + + return add(ct); +} + +// add a cost type to a mapping +// this transfers ownership of the type! +int TraceCostMapping::add(TraceCostType* ct) +{ + if (!ct) return TraceCost::InvalidIndex; + + ct->setMapping(this); + + if (ct->isReal()) { + if (_realCount >= TraceCost::MaxRealIndex) { + qDebug("WARNING: Maximum for real cost types reached (on adding '%s')", + ct->name().ascii()); + return TraceCost::InvalidIndex; + } + _real[_realCount] = ct; + ct->setRealIndex(_realCount); + _realColor[_realCount] = Configuration::costTypeColor(ct); + + _realCount++; + return _realCount-1; + } + + if (_virtualCount >= TraceCost::MaxRealIndex) { + qDebug("WARNING: Maximum for virtual cost types reached (on adding '%s')", + ct->name().ascii()); + return TraceCost::InvalidIndex; + } + _virtual[_virtualCount] = ct; + _virtualCount++; + return _virtualCount-1; +} + +// we delete the type: t is invalid when returning true! +bool TraceCostMapping::remove(TraceCostType* t) +{ + if (!t) return false; + if (t->mapping() != this) return false; + + // don't delete real types + if (t->isReal()) return false; + + int i; + for(i=0;i<_virtualCount;i++) + if (_virtual[i] == t) break; + + // not found? + if (i == _virtualCount) return false; + + // delete known type with same name + TraceCostType::remove(t->name()); + + // delete this type + _virtual[i] = 0; + delete t; + if (i+1 == _virtualCount) { + // we can reuse the last index + _virtualCount--; + } + return true; +} + + +TraceCostType* TraceCostMapping::realType(int t) +{ + if (t<0 || t>=_realCount) return 0; + return _real[t]; +} + +TraceCostType* TraceCostMapping::virtualType(int t) +{ + if (t<0 || t>=_virtualCount) return 0; + return _virtual[t]; +} + + +TraceCostType* TraceCostMapping::type(int t) +{ + if (t<0) return 0; + if (t<_realCount) return _real[t]; + + t -= TraceCost::MaxRealIndex; + if (t<0) return 0; + if (t<_virtualCount) return _virtual[t]; + + return 0; +} + +TraceCostType* TraceCostMapping::type(QString name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return _real[i]; + + for (int i=0;i<_virtualCount;i++) + if (_virtual[i] && (_virtual[i]->name() == name)) + return _virtual[i]; + + return 0; +} + +TraceCostType* TraceCostMapping::typeForLong(QString name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->longName() == name)) + return _real[i]; + + for (int i=0;i<_virtualCount;i++) + if (_virtual[i] && (_virtual[i]->longName() == name)) + return _virtual[i]; + + return 0; +} + + +int TraceCostMapping::realIndex(QString name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return i; + + return TraceCost::InvalidIndex; +} + +int TraceCostMapping::index(QString name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return i; + + for (int i=0;i<_virtualCount;i++) + if (_virtual[i] && (_virtual[i]->name() == name)) + return TraceCost::MaxRealIndex + 1 + i; + + return TraceCost::InvalidIndex; +} + +int TraceCostMapping::addKnownVirtualTypes() +{ + int addCount = 0; + int addDiff, i; + int knownCount = TraceCostType::knownTypeCount(); + + while (1) { + addDiff = 0; + for (i=0; i<knownCount; i++) { + TraceCostType* t = TraceCostType::knownType(i); + if (t->isReal()) continue; + if (index(t->name()) != TraceCost::InvalidIndex) continue; + t->setMapping(this); + if (t->parseFormula()) { + addDiff++; + add(new TraceCostType(t->name(), t->longName(), t->formula())); + } + t->setMapping(0); + } + if (addDiff == 0) break; + addCount += addDiff; + } + return addCount; +} + + +//--------------------------------------------------- +// TraceSubMapping + +TraceSubMapping::TraceSubMapping(TraceCostMapping* mapping) +{ + _mapping = mapping; + clear(); +} + +void TraceSubMapping::clear() +{ + _count = 0; + _isIdentity = true; + _firstUnused = 0; + for(int i=0;i<TraceCost::MaxRealIndex;i++) { + _realIndex[i] = TraceCost::InvalidIndex; + _nextUnused[i] = i+1; + } +} + +bool TraceSubMapping::append(QString type, bool create) +{ + if (!_mapping) return false; + int index = create ? _mapping->addReal(type) : _mapping->realIndex(type); + + return append(index); +} + +bool TraceSubMapping::append(int type) +{ + if (!_mapping) return false; + if ((type<0) || (type >= _mapping->realCount())) return false; + + if ( _count >= TraceCost::MaxRealIndex) return false; + + _realIndex[_count] = type; + + if (_isIdentity && (_count != type)) _isIdentity = false; + if (type == _firstUnused) + _firstUnused = _nextUnused[type]; + for(int i=0;i<type;i++) + if (_nextUnused[i] == type) + _nextUnused[i]=_nextUnused[type]; + + _count++; + return true; +} + + +//--------------------------------------------------- +// TraceCallCost + +TraceCallCost::TraceCallCost() +{ + _callCount = 0; +} + +TraceCallCost::~TraceCallCost() +{} + + +QString TraceCallCost::costString(TraceCostMapping* m) +{ + return QString("%1, Calls %2") + .arg(TraceCost::costString(m)) + .arg(_callCount.pretty()); +} + +QString TraceCallCost::prettyCallCount() +{ + return _callCount.pretty(); +} + +void TraceCallCost::clear() +{ + _callCount = 0; + TraceCost::clear(); +} + +SubCost TraceCallCost::callCount() +{ + if (_dirty) update(); + + return _callCount; +} + +void TraceCallCost::addCallCount(SubCost c) +{ + _callCount += c; + + invalidate(); +} + + +//--------------------------------------------------- +// TraceInclusiveCost + +TraceInclusiveCost::TraceInclusiveCost() +{} + +TraceInclusiveCost::~TraceInclusiveCost() +{} + +QString TraceInclusiveCost::costString(TraceCostMapping* m) +{ + return QString("%1, Inclusive %2") + .arg(TraceCost::costString(m)) + .arg(_inclusive.costString(m)); +} + +void TraceInclusiveCost::clear() +{ + _inclusive.clear(); + TraceCost::clear(); +} + +TraceCost* TraceInclusiveCost::inclusive() +{ + if (_dirty) update(); + + return &_inclusive; +} + +void TraceInclusiveCost::addInclusive(TraceCost* c) +{ + _inclusive.addCost(c); + + invalidate(); +} + + +//--------------------------------------------------- +// TraceListCost + +TraceListCost::TraceListCost() +{ + _lastDep = 0; +} + +TraceListCost::~TraceListCost() +{} + +void TraceListCost::addDep(TraceCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.findRef(dep)>=0) { + qDebug("addDep: %s already in list!", + dep->fullName().ascii()); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), dep->fullName().ascii(), + _deps.count()); +#endif +} + +TraceCost* TraceListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + TraceCost* dep; + for (dep = _deps.first(); dep; dep = _deps.next()) + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + return 0; +} + + +void TraceListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + fullName().ascii(), _deps.count()); +#endif + + clear(); + TraceCost* item; + for (item = _deps.first(); item; item = _deps.next()) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + + + +//--------------------------------------------------- +// TraceJumpListCost + +TraceJumpListCost::TraceJumpListCost() +{ + _lastDep = 0; +} + +TraceJumpListCost::~TraceJumpListCost() +{} + +void TraceJumpListCost::addDep(TraceJumpCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.findRef(dep)>=0) { + qDebug("addDep: %s already in list!", + dep->fullName().ascii()); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), dep->fullName().ascii(), + _deps.count()); +#endif +} + +TraceJumpCost* TraceJumpListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + TraceJumpCost* dep; + for (dep = _deps.first(); dep; dep = _deps.next()) + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + return 0; +} + + +void TraceJumpListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + fullName().ascii(), _deps.count()); +#endif + + clear(); + TraceJumpCost* item; + for (item = _deps.first(); item; item = _deps.next()) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + + + +//--------------------------------------------------- +// TraceCallListCost + +TraceCallListCost::TraceCallListCost() +{ + _lastDep = 0; +} + +TraceCallListCost::~TraceCallListCost() +{} + +void TraceCallListCost::addDep(TraceCallCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.findRef(dep)>=0) { + qDebug("addDep: %s already in list!", + dep->fullName().ascii()); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), dep->fullName().ascii(), + _deps.count()); +#endif +} + +TraceCallCost* TraceCallListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + TraceCallCost* dep; + for (dep = _deps.first(); dep; dep = _deps.next()) + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + return 0; +} + + +void TraceCallListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + fullName().ascii(), _deps.count()); +#endif + + /* Without dependent cost items, assume fixed costs, + * i.e. don't change cost */ + if (_deps.count()>0) { + clear(); + TraceCallCost* item; + for (item = _deps.first(); item; item = _deps.next()) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addCallCount(item->callCount()); + } + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + + +//--------------------------------------------------- +// TraceInclusiveListCost + +TraceInclusiveListCost::TraceInclusiveListCost() +{ + _lastDep = 0; +} + +TraceInclusiveListCost::~TraceInclusiveListCost() +{} + + +void TraceInclusiveListCost::addDep(TraceInclusiveCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.findRef(dep)>=0) { + qDebug("addDep: %s already in list!", + dep->fullName().ascii()); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), dep->fullName().ascii(), + _deps.count()); +#endif +} + +TraceInclusiveCost* TraceInclusiveListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + TraceInclusiveCost* dep; + for (dep = _deps.first(); dep; dep = _deps.next()) + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + return 0; +} + +void TraceInclusiveListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + fullName().ascii(), _deps.count()); +#endif + + clear(); + TraceInclusiveCost* item; + for (item = _deps.first(); item; item = _deps.next()) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addInclusive(item->inclusive()); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + + + +//--------------------------------------------------- +// TracePartInstrJump + +TracePartInstrJump::TracePartInstrJump(TraceInstrJump* instrJump, + TracePartInstrJump* next) +{ + _dep = instrJump; + _next = next; +} + +TracePartInstrJump::~TracePartInstrJump() +{} + + +//--------------------------------------------------- +// TracePartInstrCall + +TracePartInstrCall::TracePartInstrCall(TraceInstrCall* instrCall) +{ + _dep = instrCall; +} + +TracePartInstrCall::~TracePartInstrCall() +{} + + + +//--------------------------------------------------- +// TracePartInstr + +TracePartInstr::TracePartInstr(TraceInstr* instr) +{ + _dep = instr; +} + +TracePartInstr::~TracePartInstr() +{} + + + +//--------------------------------------------------- +// TracePartLineJump + +TracePartLineJump::TracePartLineJump(TraceLineJump* lineJump) +{ + _dep = lineJump; +} + +TracePartLineJump::~TracePartLineJump() +{} + + +//--------------------------------------------------- +// TracePartLineCall + +TracePartLineCall::TracePartLineCall(TraceLineCall* lineCall) +{ + _dep = lineCall; +} + +TracePartLineCall::~TracePartLineCall() +{} + + +//--------------------------------------------------- +// TracePartLine + +TracePartLine::TracePartLine(TraceLine* line) +{ + _dep = line; +} + +TracePartLine::~TracePartLine() +{} + + + + +//--------------------------------------------------- +// TracePartCall + +TracePartCall::TracePartCall(TraceCall* call) +{ + _dep = call; + + _firstFixCallCost = 0; +} + +TracePartCall::~TracePartCall() +{} + +bool TracePartCall::isRecursion() +{ + return call()->isRecursion(); +} + +void TracePartCall::update() +{ +#if !USE_FIXCOST + TraceCallListCost::update(); +#else + + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s", fullName().ascii()); +#endif + + /* Without dependent cost items, assume fixed costs, + * i.e. don't change cost */ + if (_firstFixCallCost) { + clear(); + FixCallCost* item; + for (item = _firstFixCallCost; item; item = item->nextCostOfPartCall()) + item->addTo(this); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif + +#endif // USE_FIXCOST +} + + +//--------------------------------------------------- +// TracePartFunction + +TracePartFunction::TracePartFunction(TraceFunction* function, + TracePartObject* partObject, + TracePartFile *partFile) +{ + _dep = function; + _partObject = partObject; + _partFile = partFile; + _partClass = 0; + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + + _firstFixCost = 0; + _firstFixJump = 0; +} + +TracePartFunction::~TracePartFunction() +{} + +QString TracePartFunction::prettyCalledCount() +{ + return _calledCount.pretty(); +} + +QString TracePartFunction::prettyCallingCount() +{ + return _callingCount.pretty(); +} + +QString TracePartFunction::costString(TraceCostMapping* m) +{ + update(); + + QString res = TraceInclusiveCost::costString(m); + res += QString(", called from %1: %2") + .arg(_calledContexts).arg(prettyCalledCount()); + res += QString(", calling from %1: %2") + .arg(_callingContexts).arg(prettyCallingCount()); + + return res; +} + + +void TracePartFunction::addPartInstr(TracePartInstr* ref) +{ +#if TRACE_ASSERTIONS + if (_partInstr.findRef(ref)>=0) { + qDebug("TracePartFunction::addPartInstr: %s already in list!", + ref->name().ascii()); + return; + } +#endif + + _partInstr.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), ref->fullName().ascii(), + _partInstr.count()); +#endif +} + + +void TracePartFunction::addPartLine(TracePartLine* ref) +{ +#if TRACE_ASSERTIONS + if (_partLines.findRef(ref)>=0) { + qDebug("TracePartFunction::addPartLine: %s already in list!", + ref->name().ascii()); + return; + } +#endif + + _partLines.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), ref->fullName().ascii(), + _partLines.count()); +#endif +} + + +void TracePartFunction::addPartCaller(TracePartCall* ref) +{ +#if TRACE_ASSERTIONS + if (_partCallers.findRef(ref)>=0) { + qDebug("TracePartFunction::addPartCaller: %s already in list!", + ref->name().ascii()); + return; + } +#endif + + _partCallers.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Caller\n %s (now %d)", + fullName().ascii(), ref->fullName().ascii(), + _partCallers.count()); +#endif +} + + +void TracePartFunction::addPartCalling(TracePartCall* ref) +{ +#if TRACE_ASSERTIONS + if (_partCallings.findRef(ref)>=0) { + qDebug("TracePartFunction::addPartCalling: %s already in list!", + ref->name().ascii()); + return; + } +#endif + + _partCallings.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Calling\n %s (now %d)", + fullName().ascii(), ref->fullName().ascii(), + _partCallings.count()); +#endif +} + +SubCost TracePartFunction::calledCount() +{ + if (_dirty) update(); + + return _calledCount; +} + +int TracePartFunction::calledContexts() +{ + if (_dirty) update(); + + return _calledContexts; +} + +SubCost TracePartFunction::callingCount() +{ + if (_dirty) update(); + + return _callingCount; +} + + +int TracePartFunction::callingContexts() +{ + if (_dirty) update(); + + return _callingContexts; +} + + +void TracePartFunction::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("TracePartFunction::update %s (Callers %d, Callings %d, lines %d)", + name().ascii(), _partCallers.count(), _partCallings.count(), + _partLines.count()); +#endif + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + + // calculate additional cost metrics + TracePartCall *caller, *calling; + for (caller=_partCallers.first();caller;caller=_partCallers.next()) { + + // FIXME + if (caller->subCost(0)>0) + _calledContexts++; + + SubCost c = caller->callCount(); + if (c>0) { + _calledCount += c; + } + } + for (calling=_partCallings.first();calling;calling=_partCallings.next()) { + // FIXME + if (calling->subCost(0)>0) + _callingContexts++; + + SubCost c = calling->callCount(); + if (c>0) { + _callingCount += c; + } + } + + // self cost +#if !USE_FIXCOST + if (_partLines.count()>0) { + TraceCost::clear(); + + TracePartLine* line; + for (line = _partLines.first(); line; line = _partLines.next()) + addCost(line); + } +#else + if (_firstFixCost) { + TraceCost::clear(); + + FixCost* item; + for (item = _firstFixCost; item; item = item->nextCostOfPartFunction()) + item->addTo(this); + } +#endif + + + /* There are two possibilities to calculate inclusive cost: + * 1) sum of call costs to this function + * 2) sum of call costs from this function + self cost + * + * 1) is wrong if a function was called spontaneous, but also by a call. + * This eventually can happen with thread/process startup functions, + * and signal handlers. + * + * 2) is wrong with "skipped PLT" and the calltree skin, because + * cost of PLT is attributed to called function (?) + * + * For now, do 1) if there are callers, otherwise 2). + * Should this be fixed to take the maximum of 1) and 2) ? + */ + _inclusive.clear(); + if (_calledCount>0) { + // inclusive cost: if possible, use caller sums + for (caller=_partCallers.first();caller;caller=_partCallers.next()) { + // detect simple recursion (no cycle) + if (caller->isRecursion()) continue; + + addInclusive(caller); + } + } + else { + // without caller info, use calling sum + line costs + for (calling=_partCallings.first();calling;calling=_partCallings.next()) { + // detect simple recursion (no cycle) + if (calling->isRecursion()) continue; + + addInclusive(calling); + } + _dirty = false; // don't recurse! + addInclusive(this); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + + + +//--------------------------------------------------- +// TracePartClass + +TracePartClass::TracePartClass(TraceClass* cls) +{ + _dep = cls; +} + +TracePartClass::~TracePartClass() +{} + +QString TracePartClass::prettyName() const +{ + return QString("%1 from %2") + .arg( _dep->name().isEmpty() ? QString("(global)") : _dep->name()) + .arg(part()->name()); +} + +//--------------------------------------------------- +// TracePartFile + +TracePartFile::TracePartFile(TraceFile* file) +{ + _dep = file; +} + +TracePartFile::~TracePartFile() +{} + + +//--------------------------------------------------- +// TracePartObject + +TracePartObject::TracePartObject(TraceObject* object) +{ + _dep = object; +} + +TracePartObject::~TracePartObject() +{} + + + + +//--------------------------------------------------- +// TraceInstrJump + +TraceInstrJump::TraceInstrJump(TraceInstr* instrFrom, TraceInstr* instrTo, + bool isCondJump) +{ + _first = 0; + + _instrFrom = instrFrom; + _instrTo = instrTo; + _isCondJump = isCondJump; +} + +TraceInstrJump::~TraceInstrJump() +{ + // we are the owner of the TracePartInstrJump's generated in our factory + TracePartInstrJump* item = _first, *next; + while(item) { + next = item->next(); + delete item; + item = next; + } +} + +TracePartInstrJump* TraceInstrJump::partInstrJump(TracePart* part) +{ + static TracePartInstrJump* item = 0; + + // shortcut + if (item && (item->instrJump()==this) && (item->part() == part)) return item; + + for(item = _first; item; item = item->next()) + if (item->part() == part) break; + + if (!item) { + item = new TracePartInstrJump(this, _first); + item->setPosition(part); + _first = item; + } + return item; +} + +void TraceInstrJump::update() +{ + if (!_dirty) return; + + clear(); + TracePartInstrJump* item; + for (item = _first; item; item = item->next()) { + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + _dirty = false; + +#if TRACE_DEBUG + qDebug("updated %s", fullName().ascii()); +#endif + +#if TRACE_DEBUG + qDebug(" > %s", costString(0).ascii()); +#endif +} + +QString TraceInstrJump::name() const +{ + return QString("jump at 0x%1 to 0x%2") + .arg(_instrFrom->addr().toString()) + .arg(_instrTo->addr().toString()); +} + + +//--------------------------------------------------- +// TraceInstrJumpList + + +int TraceInstrJumpList::compareItems ( Item item1, Item item2 ) +{ + TraceInstrJump* ij1 = (TraceInstrJump*) item1; + TraceInstrJump* ij2 = (TraceInstrJump*) item2; + + Addr addr1Low = ij1->instrFrom()->addr(); + Addr addr2Low = ij2->instrFrom()->addr(); + Addr addr1High = ij1->instrTo()->addr(); + Addr addr2High = ij2->instrTo()->addr(); + Addr t; + + if (addr1Low > addr1High) { + t = addr1Low; + addr1Low = addr1High; + addr1High = t; + } + + if (addr2Low > addr2High) { + t = addr2Low; + addr2Low = addr2High; + addr2High = t; + } + + if (_sortLow) { + // we sort according to smallest instruction address + if (addr1Low != addr2Low) return (addr1Low > addr2Low) ? 1:-1; + // jump ends come before jump starts + if (addr1Low == ij1->instrTo()->addr()) return -1; + if (addr2Low == ij2->instrTo()->addr()) return 1; + return (addr1High > addr2High) ? 1:-1; + } + + // we sort according to highest instruction address + if (addr1High != addr2High) return (addr1High > addr2High) ? 1:-1; + // jump ends come before jump starts + if (addr1High == ij1->instrTo()->addr()) return -1; + if (addr2High == ij2->instrTo()->addr()) return 1; + return (addr1Low > addr2Low) ? 1:-1; +} + + +//--------------------------------------------------- +// TraceLineJump + +TraceLineJump::TraceLineJump(TraceLine* lineFrom, TraceLine* lineTo, + bool isCondJump) +{ + // we are the owner of TracePartLineJump's generated in our factory + _deps.setAutoDelete(true); + + _lineFrom = lineFrom; + _lineTo = lineTo; + _isCondJump = isCondJump; +} + +TraceLineJump::~TraceLineJump() +{} + + +TracePartLineJump* TraceLineJump::partLineJump(TracePart* part) +{ + TracePartLineJump* item = (TracePartLineJump*) findDepFromPart(part); + if (!item) { + item = new TracePartLineJump(this); + item->setPosition(part); + addDep(item); + } + return item; +} + + +QString TraceLineJump::name() const +{ + return QString("jump at %1 to %2") + .arg(_lineFrom->prettyName()) + .arg(_lineTo->prettyName()); +} + + +//--------------------------------------------------- +// TraceLineJumpList + + +int TraceLineJumpList::compareItems ( Item item1, Item item2 ) +{ + TraceLineJump* lj1 = (TraceLineJump*) item1; + TraceLineJump* lj2 = (TraceLineJump*) item2; + + uint line1Low = lj1->lineFrom()->lineno(); + uint line2Low = lj2->lineFrom()->lineno(); + uint line1High = lj1->lineTo()->lineno(); + uint line2High = lj2->lineTo()->lineno(); + uint t; + + if (line1Low > line1High) { + t = line1Low; line1Low = line1High; line1High = t; + } + if (line2Low > line2High) { + t = line2Low; line2Low = line2High; line2High = t; + } + + if (_sortLow) { + // we sort according to smallest line number + if (line1Low != line2Low) return line1Low - line2Low; + // jump ends come before jump starts + if (line1Low == lj1->lineTo()->lineno()) return -1; + if (line2Low == lj2->lineTo()->lineno()) return 1; + return line1High - line2High; + } + + // we sort according to highest line number + if (line1High != line2High) return line1High - line2High; + // jump ends come before jump starts + if (line1High == lj1->lineTo()->lineno()) return -1; + if (line2High == lj2->lineTo()->lineno()) return 1; + return line1Low - line2Low; +} + + +//--------------------------------------------------- +// TraceInstrCall + +TraceInstrCall::TraceInstrCall(TraceCall* call, TraceInstr* instr) +{ + // we are the owner of TracePartInstrCall's generated in our factory + _deps.setAutoDelete(true); + + _call = call; + _instr = instr; +} + +TraceInstrCall::~TraceInstrCall() +{} + + +TracePartInstrCall* TraceInstrCall::partInstrCall(TracePart* part, + TracePartCall*) +{ + TracePartInstrCall* item = (TracePartInstrCall*) findDepFromPart(part); + if (!item) { + item = new TracePartInstrCall(this); + item->setPosition(part); + addDep(item); + // instruction calls are not registered in function calls + // as together with line calls calls are duplicated + //partCall->addDep(item); + } + return item; +} + + +QString TraceInstrCall::name() const +{ + return QString("%1 at %2").arg(_call->name()).arg(_instr->name()); +} + + +//--------------------------------------------------- +// TraceLineCall + +TraceLineCall::TraceLineCall(TraceCall* call, TraceLine* line) +{ + // we are the owner of TracePartLineCall's generated in our factory + _deps.setAutoDelete(true); + + _call = call; + _line = line; +} + +TraceLineCall::~TraceLineCall() +{} + + +TracePartLineCall* TraceLineCall::partLineCall(TracePart* part, + TracePartCall* partCall) +{ + TracePartLineCall* item = (TracePartLineCall*) findDepFromPart(part); + if (!item) { + item = new TracePartLineCall(this); + item->setPosition(part); + addDep(item); + partCall->addDep(item); + } + return item; +} + + +QString TraceLineCall::name() const +{ + return QString("%1 at %2").arg(_call->name()).arg(_line->name()); +} + + +//--------------------------------------------------- +// TraceCall + +TraceCall::TraceCall(TraceFunction* caller, TraceFunction* called) +{ + // we are the owner of all items generated in our factory + _deps.setAutoDelete(true); + _lineCalls.setAutoDelete(true); + + _caller = caller; + _called = called; +} + + +TraceCall::~TraceCall() +{} + +TracePartCall* TraceCall::partCall(TracePart* part, + TracePartFunction* partCaller, + TracePartFunction* partCalling) +{ + TracePartCall* item = (TracePartCall*) findDepFromPart(part); + if (!item) { + item = new TracePartCall(this); + item->setPosition(part); + addDep(item); + partCaller->addPartCalling(item); + partCalling->addPartCaller(item); + } + return item; +} + +TraceInstrCall* TraceCall::instrCall(TraceInstr* i) +{ + TraceInstrCall* icall; + for (icall=_instrCalls.first();icall;icall=_instrCalls.next()) + if (icall->instr() == i) + break; + + if (!icall) { + icall = new TraceInstrCall(this, i); + + _instrCalls.append(icall); + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceCall::instrCall]", icall->fullName().ascii()); +#endif + i->addInstrCall(icall); + } + return icall; +} + + +TraceLineCall* TraceCall::lineCall(TraceLine* l) +{ + TraceLineCall* lcall; + for (lcall=_lineCalls.first();lcall;lcall=_lineCalls.next()) + if (lcall->line() == l) + break; + + if (!lcall) { + lcall = new TraceLineCall(this, l); + + _lineCalls.append(lcall); + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceCall::lineCall]", lcall->fullName().ascii()); +#endif + l->addLineCall(lcall); + } + return lcall; +} + + +void TraceCall::invalidateDynamicCost() +{ + TraceLineCall* lc; + for (lc=_lineCalls.first();lc;lc=_lineCalls.next()) + lc->invalidate(); + + TraceInstrCall* ic; + for (ic=_instrCalls.first();ic;ic=_instrCalls.next()) + ic->invalidate(); + + invalidate(); +} + + +QString TraceCall::name() const +{ + return QString("%1 => %2") + .arg(_caller->name()) + .arg(_called->name()); +} + +int TraceCall::inCycle() +{ + if (!_caller || !_called) return 0; + if (!_caller->cycle()) return 0; + if (_caller == _caller->cycle()) return 0; + if (_caller->cycle() != _called->cycle()) return 0; + + return _caller->cycle()->cycleNo(); +} + +void TraceCall::update() +{ + if (!_dirty) return; + + // special handling for cycles + if (_caller && _caller->cycle() && _caller==_caller->cycle()) { + + // we have no part calls: use inclusive cost of called function + clear(); + if (_called) + addCost(_called->inclusive()); + _dirty = false; + return; + } + + TraceCallListCost::update(); +} + +TraceFunction* TraceCall::caller(bool /*skipCycle*/) const +{ + return _caller; +} + +TraceFunction* TraceCall::called(bool skipCycle) const +{ + if (!skipCycle && _called) { + // if this is a call to a cycle member from outside of the cycle, + // fake it to be a call to the whole cycle + if (_called->cycle() && _caller && + (_caller->cycle() != _called->cycle())) + return _called->cycle(); + } + + return _called; +} + +QString TraceCall::callerName(bool skipCycle) const +{ + if (!_caller) return i18n("(no caller)"); + + if (!skipCycle) { + // if this call goes into a cycle, add the entry function + TraceFunctionCycle* c = _called->cycle(); + if (c && _caller && (_caller->cycle() != c)) { + QString via = _called->prettyName(); + return i18n("%1 via %2").arg(_caller->prettyName()).arg(via); + } + } + + return _caller->prettyName(); +} + +QString TraceCall::calledName(bool skipCycle) const +{ + if (!_called) return i18n("(no callee)"); + + if (!skipCycle) { + // if this call goes into a cycle, add the entry function + TraceFunctionCycle* c = _called->cycle(); + if (c && _caller && (_caller->cycle() != c)) { + // HACK to get rid of cycle postfix... + _called->setCycle(0); + QString via = _called->prettyName(); + _called->setCycle(c); + return i18n("%1 via %2").arg(c->name()).arg(via); + } + } + return _called->prettyName(); +} + + +//--------------------------------------------------- +// TraceInstr + +TraceInstr::TraceInstr() +{ + // we are the owner of TracePartInstr's generated in our factory + _deps.setAutoDelete(true); + _instrJumps.setAutoDelete(true); + + _addr = 0; + _line = 0; + _function = 0; +} + +TraceInstr::~TraceInstr() +{} + +bool TraceInstr::hasCost(TraceCostType* ct) +{ + bool res = subCost(ct) > 0; + if (!res) { + TraceInstrCall* ic; + for(ic=_instrCalls.first();ic;ic=_instrCalls.next()) + if (ic->subCost(ct) > 0) break; + res = (ic != 0); + if (!res) { + TraceInstrJump* ij; + for(ij=_instrJumps.first();ij;ij=_instrJumps.next()) + if (ij->executedCount() > 0) break; + res = (ij != 0); + } + } + + return res; +} + +TracePartInstr* TraceInstr::partInstr(TracePart* part, + TracePartFunction* partFunction) +{ + TracePartInstr* item = (TracePartInstr*) findDepFromPart(part); + if (!item) { + item = new TracePartInstr(this); + item->setPosition(part); + addDep(item); + //part->addDep(item); + partFunction->addPartInstr(item); + } + return item; +} + +TraceInstrJump* TraceInstr::instrJump(TraceInstr* to, bool isJmpCond) +{ + TraceInstrJump* jump; + for (jump=_instrJumps.first();jump;jump=_instrJumps.next()) + if (jump->instrTo() == to) + break; + + if (!jump) { + jump = new TraceInstrJump(this, to, isJmpCond); + + _instrJumps.append(jump); + } + return jump; +} + + + +void TraceInstr::addInstrCall(TraceInstrCall* instrCall) +{ +#if TRACE_ASSERTIONS + if (_instrCalls.findRef(instrCall)>=0) return; + + if (instrCall->instr() != this) { + qDebug("Can't add instruction call to another instruction!"); + return; + } +#endif + + _instrCalls.append(instrCall); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), + instrCall->fullName().ascii(), _instrCalls.count()); +#endif +} + + +QString TraceInstr::name() const +{ + return QString("0x%1").arg(_addr.toString()); +} + +QString TraceInstr::prettyName() const +{ + return QString("0x%1").arg(_addr.toString()); +} + + +//--------------------------------------------------- +// TraceLine + +TraceLine::TraceLine() +{ + // we are the owner of TracePartLine's generated in our factory + _deps.setAutoDelete(true); + _lineJumps.setAutoDelete(true); + + _lineno = 0; + _sourceFile = 0; +} + +TraceLine::~TraceLine() +{} + +bool TraceLine::hasCost(TraceCostType* ct) +{ + bool res = subCost(ct) > 0; + if (!res) { + TraceLineCall* lc; + for(lc=_lineCalls.first();lc;lc=_lineCalls.next()) + if (lc->subCost(ct) > 0) break; + res = (lc != 0); + if (!res) { + TraceLineJump* lj; + for(lj=_lineJumps.first();lj;lj=_lineJumps.next()) + if (lj->executedCount() > 0) break; + res = (lj != 0); + } + } + + return res; +} + +TracePartLine* TraceLine::partLine(TracePart* part, + TracePartFunction* partFunction) +{ + TracePartLine* item = (TracePartLine*) findDepFromPart(part); + if (!item) { + item = new TracePartLine(this); + item->setPosition(part); + addDep(item); +#if !USE_FIXCOST + part->addDep(item); +#endif + partFunction->addPartLine(item); + } + return item; +} + +TraceLineJump* TraceLine::lineJump(TraceLine* to, bool isJmpCond) +{ + TraceLineJump* jump; + for (jump=_lineJumps.first();jump;jump=_lineJumps.next()) + if (jump->lineTo() == to) + break; + + if (!jump) { + jump = new TraceLineJump(this, to, isJmpCond); + + _lineJumps.append(jump); + } + return jump; +} + + +void TraceLine::addLineCall(TraceLineCall* lineCall) +{ +#if TRACE_ASSERTIONS + if (_lineCalls.findRef(lineCall)>=0) return; + + if (lineCall->line() != this) { + qDebug("Can't add line call to another line!"); + return; + } +#endif + + TraceFunction* caller = lineCall->call()->caller(); + TraceFunction* function = _sourceFile->function(); + if (caller != function) { + // We regard 2 functions as the same if they have + // same class, name, object + if ((caller->cls() != function->cls()) || + (caller->name() != function->name()) || + (caller->object() != function->object())) { + + qDebug("ERROR: Adding line call, line %d\n of %s to\n %s ?!", + lineCall->line()->lineno(), + caller->info().ascii(), function->info().ascii()); + } + } + + _lineCalls.append(lineCall); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), + lineCall->fullName().ascii(), _lineCalls.count()); +#endif +} + + +QString TraceLine::name() const +{ + QString fileShortName = _sourceFile->file()->shortName(); + if (fileShortName.isEmpty()) + return i18n("(unknown)"); + + return QString("%1:%2") + .arg(fileShortName).arg(_lineno); +} + +QString TraceLine::prettyName() const +{ + return QString("%1 [%2]") + .arg(name()).arg(_sourceFile->function()->prettyName()); +} + +//--------------------------------------------------- +// TraceCostItem + +TraceCostItem::TraceCostItem() +{ +} + +TraceCostItem::~TraceCostItem() +{} + + +//--------------------------------------------------- +// TraceFunctionSource + +TraceFunctionSource::TraceFunctionSource(TraceFunction* function, + TraceFile* file) +{ + _file = file; + _function = function; + + // the function is dependent from our cost sum + _dep = _function; + + _lineMap = 0; + _lineMapFilled = false; + _line0 = 0; +} + +TraceFunctionSource::~TraceFunctionSource() +{ + if (_lineMap) delete _lineMap; + if (_line0) delete _line0; +} + +QString TraceFunctionSource::name() const +{ + return QString("%1 for %2").arg(_file->name()).arg(_function->name()); +} + +uint TraceFunctionSource::firstLineno() +{ + // lazy generate the map if not done up to now + TraceLineMap* map = lineMap(); + // ignore line 0 here + if (!map || map->count() == 0) return 0; + TraceLineMap::Iterator it = map->begin(); + return (*it).lineno(); +} + +uint TraceFunctionSource::lastLineno() +{ + // lazy generate the map if not done up to now + TraceLineMap* map = lineMap(); + // ignore line 0 here + if (!map || map->count() == 0) return 0; + TraceLineMap::Iterator it = map->end(); + --it; + return (*it).lineno(); +} + +/* factory */ +TraceLine* TraceFunctionSource::line(uint lineno, bool createNew) +{ + if (lineno == 0) { + if (!_line0) { + if (!createNew) return 0; + _line0 = new TraceLine; + _line0->setSourceFile(this); + _line0->setLineno(0); + } + return _line0; + } + + if (!createNew) { + if (!_lineMap) return 0; + TraceLineMap::Iterator it = _lineMap->find(lineno); + if (it == _lineMap->end()) return 0; + return &(it.data()); + } + + if (!_lineMap) _lineMap = new TraceLineMap; + + TraceLine& l = (*_lineMap)[lineno]; + if (!l.isValid()) { + l.setSourceFile(this); + l.setLineno(lineno); + +#if TRACE_DEBUG + qDebug("Created %s [TraceFunctionSource::line]", + l.fullName().ascii()); +#endif + } + return &l; +} + +void TraceFunctionSource::update() +{ + if (!_dirty) return; + + clear(); + + // no need to create lineMap if not already created + if (_lineMap) { + TraceLineMap::Iterator lit; + for ( lit = _lineMap->begin(); + lit != _lineMap->end(); ++lit ) + addCost( &(*lit) ); + } + + _dirty = false; +} + +void TraceFunctionSource::invalidateDynamicCost() +{ + // no need to create lineMap if not already created + if (_lineMap) { + TraceLineMap::Iterator lit; + for ( lit = _lineMap->begin(); + lit != _lineMap->end(); ++lit ) + (*lit).invalidate(); + } + + invalidate(); +} + +TraceLineMap* TraceFunctionSource::lineMap() +{ +#if USE_FIXCOST + + if (_lineMapFilled) return _lineMap; + _lineMapFilled = true; + if (!_lineMap) + _lineMap = new TraceLineMap; + + TraceLine* l = 0; + TracePartLine* pl = 0; + TraceLineCall* lc = 0; + TracePartLineCall* plc = 0; + + /* go over all part objects for this function, and + * - build TraceLines (the line map) using FixCost objects + * - build TraceJumpLines using FixJump objects + */ + TraceInclusiveCostList pfList = _function->deps(); + TracePartFunction* pf = (TracePartFunction*) pfList.first(); + for(; pf; pf = (TracePartFunction*) pfList.next()) { + + if (0) qDebug("PartFunction %s:%d", + pf->function()->name().ascii(), pf->part()->partNumber()); + + FixCost* fc = pf->firstFixCost(); + for(; fc; fc = fc->nextCostOfPartFunction()) { + if (fc->line() == 0) continue; + if (fc->functionSource() != this) continue; + + if (!l || l->lineno() != fc->line()) { + l = &(*_lineMap)[fc->line()]; + if (!l->isValid()) { + l->setSourceFile(this); + l->setLineno(fc->line()); + } + pl = 0; + } + if (!pl || pl->part() != fc->part()) + pl = l->partLine(fc->part(), pf); + fc->addTo(pl); + } + + TraceLine* to = 0; + TraceLineJump* lj; + TracePartLineJump* plj; + FixJump* fj = pf->firstFixJump(); + for(; fj; fj = fj->nextJumpOfPartFunction()) { + if (fj->line() == 0) continue; + if (fj->source() != this) continue; + if (!fj->targetSource()) { + // be robust against buggy loaders + continue; + } + + // don't display jumps to same or following line + if ((fj->line() == fj->targetLine()) || + (fj->line()+1 == fj->targetLine())) continue; + + if (!l || l->lineno() != fj->line()) { + l = &(*_lineMap)[fj->line()]; + if (!l->isValid()) { + l->setSourceFile(this); + l->setLineno(fj->line()); + } + } + + to = fj->targetSource()->line(fj->targetLine(), true); + + lj = l->lineJump(to, fj->isCondJump()); + plj = lj->partLineJump(fj->part()); + + fj->addTo(plj); + } + + + TracePartCallList pcList = pf->partCallings(); + TracePartCall* pc = pcList.first(); + for(; pc; pc = pcList.next()) { + + if (0) qDebug("PartCall %s:%d", + pc->call()->name().ascii(), + pf->part()->partNumber()); + + FixCallCost* fcc = pc->firstFixCallCost(); + for(; fcc; fcc = fcc->nextCostOfPartCall()) { + if (fcc->line() == 0) continue; + if (fcc->functionSource() != this) continue; + + if (!l || l->lineno() != fcc->line()) { + l = &(*_lineMap)[fcc->line()]; + if (!l->isValid()) { + l->setSourceFile(this); + l->setLineno(fcc->line()); + } + } + if (!lc || lc->call() != pc->call() || lc->line() != l) { + lc = pc->call()->lineCall(l); + plc = 0; + } + if (!plc || plc->part() != fcc->part()) + plc = lc->partLineCall(fcc->part(), pc); + + fcc->addTo(plc); + if (0) qDebug("Add FixCallCost %s:%d/0x%s, CallCount %s", + fcc->functionSource()->file()->shortName().ascii(), + fcc->line(), fcc->addr().toString().ascii(), + fcc->callCount().pretty().ascii()); + } + } + } + +#endif + + return _lineMap; +} + + + +//--------------------------------------------------- +// TraceAssoziation + +TraceAssoziation::TraceAssoziation() +{ + _function = 0; + _valid = false; +} + +TraceAssoziation::~TraceAssoziation() +{ + // don't delete from TraceFunction + if (_function) _function->removeAssoziation(this); +} + +bool TraceAssoziation::isAssoziated() +{ + if (!_function) return false; + + return _function->assoziation(rtti())==this; +} + +bool TraceAssoziation::setFunction(TraceFunction* f) +{ + if (_function == f) + return isAssoziated(); + + if (_function) { + // don't delete ourself + _function->removeAssoziation(this); + } + + _function = f; + if (f && f->assoziation(rtti()) == 0) { + f->addAssoziation(this); + return true; + } + return false; +} + +void TraceAssoziation::clear(TraceData* d, int rtti) +{ + TraceFunctionMap::Iterator it; + for ( it = d->functionMap().begin(); + it != d->functionMap().end(); ++it ) + (*it).removeAssoziation(rtti); +} + +void TraceAssoziation::invalidate(TraceData* d, int rtti) +{ + TraceFunctionMap::Iterator it; + for ( it = d->functionMap().begin(); + it != d->functionMap().end(); ++it ) + (*it).invalidateAssoziation(rtti); +} + + +//--------------------------------------------------- +// TraceFunction + +TraceFunction::TraceFunction() +{ + _object = 0; + _file = 0; + _cls = 0; + _cycle = 0; + + // we are the owner of items generated in our factory + _deps.setAutoDelete(true); + _callings.setAutoDelete(true); + _sourceFiles.setAutoDelete(true); + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + + _instrMap = 0; + _instrMapFilled = false; +} + + +TraceFunction::~TraceFunction() +{ + _assoziations.setAutoDelete(true); + _assoziations.clear(); + + if (_instrMap) delete _instrMap; +} + +// no unique check is done! +void TraceFunction::addAssoziation(TraceAssoziation* a) +{ + if (!a) return; + _assoziations.append(a); +} + +void TraceFunction::removeAssoziation(TraceAssoziation* a) +{ + _assoziations.removeRef(a); +} + +void TraceFunction::removeAssoziation(int rtti, bool reallyDelete) +{ + if (rtti==0) { + if (reallyDelete) + _assoziations.setAutoDelete(true); + _assoziations.clear(); + _assoziations.setAutoDelete(false); + return; + } + + TraceAssoziation* a; + for (a=_assoziations.first();a;a=_assoziations.next()) + if (a->rtti() == rtti) { + if (reallyDelete) delete a; + _assoziations.remove(); + return; + } +} + +void TraceFunction::invalidateAssoziation(int rtti) +{ + TraceAssoziation* a; + for (a=_assoziations.first();a;a=_assoziations.next()) + if ((rtti==0) || (a->rtti() == rtti)) + a->invalidate(); +} + +TraceAssoziation* TraceFunction::assoziation(int rtti) +{ + TraceAssoziation* a; + for (a=_assoziations.first();a;a=_assoziations.next()) + if (a->rtti() == rtti) + return a; + return 0; +} + + +// helper for prettyName +bool TraceFunction::isUniquePrefix(QString prefix) const +{ + TraceFunctionMap::ConstIterator it, it2; + it = it2 = _myMapIterator; + if (it != data()->functionBeginIterator()) { + it2--; + if ((*it2).name().startsWith(prefix)) return false; + } + if (it != data()->functionEndIterator()) { + it++; + if ((*it).name().startsWith(prefix)) return false; + } + return true; +} + + +QString TraceFunction::prettyName() const +{ + QString res = _name; + + if (_name.isEmpty()) + return i18n("(unknown)"); + + int p = _name.find('('); + if (p>0) { + // handle C++ "operator()" correct + if ((_name[p+1] == ')') && (_name[p+2] == '(')) p+=2; + + // we have a C++ symbol with argument types: + // check for unique function name (inclusive '(' !) + if (isUniquePrefix(_name.left(p+1))) + res = _name.left(p); + } + + // cycle members + if (_cycle) { + if (_cycle != this) + res = QString("%1 <cycle %2>").arg(res).arg(_cycle->cycleNo()); + else + res = QString("<cycle %2>").arg(_cycle->cycleNo()); + } + + + return res; +} + +/* + * Returns location string: ELF object and source file(s). + */ +QString TraceFunction::location(int maxFiles) const +{ + QString loc; + + // add object file with address range + if (_object) { + loc = _object->shortName(); + +#if 0 + uint from = firstAddress(); + uint to = lastAddress(); + if (from != 0 && to != 0) { + if (from == to) + loc += QString(" (0x%1)").arg(to, 0, 16); + else + loc += QString(" (0x%1-0x%2)").arg(from, 0, 16).arg(to, 0, 16); + } +#endif + } + + // add all source files + int filesAdded = 0; + TraceFunctionSourceList list = _sourceFiles; + TraceFunctionSource* sourceFile = list.first(); + for (;sourceFile;sourceFile=list.next()) { + if (!sourceFile->file() || + (sourceFile->file()->name().isEmpty()) ) + continue; + + if (!loc.isEmpty()) + loc += (filesAdded>0) ? ", " : ": "; + filesAdded++; + + if ((maxFiles>0) && (filesAdded>maxFiles)) { + loc += "..."; + break; + } + loc += sourceFile->file()->shortName(); + +#if 0 + from = sourceFile->firstLineno(); + to = sourceFile->lastLineno(); + if (from != 0 && to != 0) { + if (from == to) + loc += QString(" (%1)").arg(to); + else + loc += QString(" (%1-%2)").arg(from).arg(to); + } +#endif + } + + return loc; +} + +// pretty version is allowed to mangle the string... +QString TraceFunction::prettyLocation(int maxFiles) const +{ + QString l = location(maxFiles); + if (l.isEmpty()) return i18n("(unknown)"); + + return l; +} + +void TraceFunction::addPrettyLocation(QString& s, int maxFiles) const +{ + QString l = location(maxFiles); + if (l.isEmpty()) return; + + s += QString(" (%1)").arg(l); +} + +QString TraceFunction::prettyNameWithLocation(int maxFiles) const +{ + QString l = location(maxFiles); + if (l.isEmpty()) return prettyName(); + + return QString("%1 (%2)").arg(prettyName()).arg(l); +} + +QString TraceFunction::info() const +{ + QString l = location(); + if (l.isEmpty()) + return QString("Function %1").arg(name()); + + return QString("Function %1 (location %2)") + .arg(name()).arg(l); +} + + +Addr TraceFunction::firstAddress() const +{ + // ignore address 0 here + if (!_instrMap || _instrMap->count() == 0) return 0; + TraceInstrMap::ConstIterator it = _instrMap->begin(); + return (*it).addr(); +} + +Addr TraceFunction::lastAddress() const +{ + // ignore address 0 here + if (!_instrMap || _instrMap->count() == 0) return 0; + TraceInstrMap::ConstIterator it = _instrMap->end(); + --it; + return (*it).addr(); +} + +/* factory */ +TraceInstr* TraceFunction::instr(Addr addr, bool createNew) +{ + // address 0 not allowed + if (addr == Addr(0)) return 0; + + if (!createNew) { + if (!_instrMap) return 0; + TraceInstrMap::Iterator it = _instrMap->find(addr); + if (it == _instrMap->end()) + return 0; + return &(it.data()); + } + + if (!_instrMap) _instrMap = new TraceInstrMap; + + TraceInstr& i = (*_instrMap)[addr]; + if (!i.isValid()) { + i.setAddr(addr); + i.setFunction(this); + +#if TRACE_DEBUG + qDebug("Created %s [TraceFunction::instr]", + i.fullName().ascii()); +#endif + } + return &i; +} + +void TraceFunction::addCaller(TraceCall* caller) +{ +#if TRACE_ASSERTIONS + if (caller->called() != this) { + qDebug("Can't add call to another line!\n"); + return; + } + + if (_callers.findRef(caller)>=0) return; +#endif + + _callers.append(caller); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Caller\n %s (now %d)", + fullName().ascii(), caller->fullName().ascii(), _callers.count()); +#endif +} + + + +TraceCall* TraceFunction::calling(TraceFunction* called) +{ + TraceCallMap::Iterator it = _callingMap.find(called); + TraceCall* calling = (it == _callingMap.end()) ? 0 : it.data(); + + if (!calling) { + calling = new TraceCall(this, called); + + _callingMap.insert(called, calling); + _callings.append(calling); + + // we have to invalidate ourself so invalidations from item propagate up + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceFunction::calling]", calling->fullName().ascii()); +#endif + called->addCaller(calling); + } + return calling; +} + +TraceFunctionSource* TraceFunction::sourceFile(TraceFile* file, + bool createNew) +{ + if (!file) file = _file; + + TraceFunctionSource* sourceFile = _sourceFiles.first(); + for (;sourceFile;sourceFile=_sourceFiles.next()) + if (sourceFile->file() == file) break; + + if (!sourceFile && createNew) { + sourceFile = new TraceFunctionSource(this, file); + + _sourceFiles.append(sourceFile); + + // we have to invalidate ourself so invalidations from item propagate up + invalidate(); + +#if TRACE_DEBUG + qDebug("Created SourceFile %s [TraceFunction::line]", + file->name().ascii()); +#endif + file->addSourceFile(sourceFile); + } + return sourceFile; +} + +TraceLine* TraceFunction::line(TraceFile* file, uint lineno, + bool createNew) +{ + Q_ASSERT(file!=0); + + TraceFunctionSource* sf = sourceFile(file, createNew); + if (!sf) + return 0; + else + return sf->line(lineno, createNew); +} + + +TracePartFunction* TraceFunction::partFunction(TracePart* part, + TracePartFile* partFile, + TracePartObject* partObject) +{ + TracePartFunction* item = (TracePartFunction*) findDepFromPart(part); + if (!item) { + item = new TracePartFunction(this, partObject, partFile); + item->setPosition(part); + addDep(item); +#if USE_FIXCOST + part->addDep(item); +#endif + + if (_cls) { + TracePartClass* partClass = _cls->partClass(part); + partClass->addPartFunction(item); + item->setPartClass(partClass); + } + + partFile->addPartFunction(item); + if (partObject) + partObject->addPartFunction(item); + } + else if (item->partObject()==0 && partObject) { + item->setPartObject(partObject); + partObject->addPartFunction(item); + } + + return item; +} + + +SubCost TraceFunction::calledCount() +{ + if (_dirty) update(); + + return _calledCount; +} + +int TraceFunction::calledContexts() +{ + if (_dirty) update(); + + return _calledContexts; +} + +SubCost TraceFunction::callingCount() +{ + if (_dirty) update(); + + return _callingCount; +} + +int TraceFunction::callingContexts() +{ + if (_dirty) update(); + + return _callingContexts; +} + +QString TraceFunction::prettyCalledCount() +{ + return _calledCount.pretty(); +} + +QString TraceFunction::prettyCallingCount() +{ + return _callingCount.pretty(); +} + + +TraceCallList TraceFunction::callers(bool skipCycle) const +{ + if (skipCycle) return _callers; + + // fake the callers for cycle members + if (_cycle && (_cycle != this)) { + TraceCallList l; + TraceCall* c; + + // inner-cycle-callers + TraceCallList list=_callers; + for (c=list.first();c;c=list.next()) + if (c->caller()->cycle() == _cycle) + l.append(c); + + // call from cycle itself + for (c=_cycle->_callings.first();c;c=_cycle->_callings.next()) + if (c->called() == this) { + l.append(c); + return l; + } + } + + return _callers; +} + +const TraceCallList& TraceFunction::callings(bool /* skipCycle */) const +{ + return _callings; +} + +void TraceFunction::invalidateDynamicCost() +{ + TraceCall* c; + for (c=_callings.first();c;c=_callings.next()) + c->invalidateDynamicCost(); + + TraceFunctionSource* sf; + for (sf=_sourceFiles.first();sf;sf=_sourceFiles.next()) + sf->invalidateDynamicCost(); + + if (_instrMap) { + TraceInstrMap::Iterator iit; + for ( iit = _instrMap->begin(); + iit != _instrMap->end(); ++iit ) + (*iit).invalidate(); + } + + invalidate(); +} + +void TraceFunction::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("Update %s (Callers %d, sourceFiles %d, instrs %d)", + _name.ascii(), _callers.count(), + _sourceFiles.count(), _instrMap ? _instrMap->count():0); +#endif + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + clear(); + + // context count is NOT the sum of part contexts + TraceCall *caller, *calling; + for (caller=_callers.first();caller;caller=_callers.next()) { + // FIXME + if (caller->subCost(0)>0) + _calledContexts++; + _calledCount += caller->callCount(); + } + + for (calling=_callings.first();calling;calling=_callings.next()) { + // FIXME + if (calling->subCost(0)>0) _callingContexts++; + _callingCount += calling->callCount(); + } + + if (data()->inFunctionCycleUpdate() || !_cycle) { + // usual case (no cycle member) + TraceInclusiveCost* item; + for (item=_deps.first();item;item=_deps.next()) { + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addInclusive(item->inclusive()); + } + } + else { + // this is a cycle or cycle member + for (calling=_callings.first();calling;calling=_callings.next()) { + + // ignore inner-cycle member calls for inclusive cost + if ((_cycle != this) && + (calling->inCycle()>0)) continue; + + addInclusive(calling); + } + + // self cost + if (type() == FunctionCycle) { + // cycle: self cost is sum of cycle member self costs, but + // doesn't add to inclusive cost + TraceFunctionList mList = ((TraceFunctionCycle*)this)->members(); + TraceFunction* m; + for (m=mList.first();m;m=mList.next()) + addCost(m); + } + else { + // cycle member + TraceInclusiveCost* item; + for (item=_deps.first();item;item=_deps.next()) { + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + _dirty = false; // don't recurse + addInclusive(this); + } + } + _dirty = false; + +#if TRACE_DEBUG + qDebug("> %s", costString(0).ascii()); +#endif +} + +bool TraceFunction::isCycle() +{ + return _cycle == this; +} + +bool TraceFunction::isCycleMember() +{ + return _cycle && (_cycle != this); +} + +void TraceFunction::cycleReset() +{ + _cycle = 0; + _cycleStackDown = 0; + _cycleLow = 0; +} + +// this doesn't mark functions calling themself ! +void TraceFunction::cycleDFS(int d, int& pNo, TraceFunction** pTop) +{ + if (_cycleLow != 0) return; + + if (0) + qDebug("%s D%02d > %s (%d)", + QString().fill(' ', d).ascii(), d, prettyName().ascii(), pNo+1); + + + + // initialize with prefix order + pNo++; + int prefixNo = pNo; + _cycleLow = prefixNo; + + // put myself on stack + _cycleStackDown = *pTop; + *pTop = this; + + /* cycle cut heuristic: + * skip calls for cycle detection if they make less than _cycleCut + * percent of the cost of the function. + * FIXME: Which cost type to use for this heuristic ?! + */ + + SubCost base = 0; + if (_callers.count()>0) { + TraceCallList l = _callers; + TraceCall *caller; + + for (caller=l.first();caller;caller=l.next()) + if (caller->subCost(0) > base) + base = caller->subCost(0); + } + else base = inclusive()->subCost(0); + + SubCost cutLimit = SubCost(base * Configuration::cycleCut()); + + if (0) + qDebug("%s Cum. %s, Max Caller %s, cut limit %s", + QString().fill(' ', d).ascii(), + inclusive()->subCost(0).pretty().ascii(), + base.pretty().ascii(), + cutLimit.pretty().ascii()); + + TraceCall *calling; + TraceCallList l = _callings; + for (calling=l.first();calling;calling=l.next()) { + TraceFunction* called = calling->called(); + + // cycle cut heuristic + if (calling->subCost(0) < cutLimit) { + if (0) qDebug("%s Cut call to %s (cum. %s)", + QString().fill(' ', d).ascii(), + called->prettyName().ascii(), + calling->subCost(0).pretty().ascii()); + + continue; + } + + if (called->_cycleLow==0) { + // not visited yet + called->cycleDFS(d+1, pNo, pTop); + if (called->_cycleLow < _cycleLow) + _cycleLow = called->_cycleLow; + } + else if (called->_cycleStackDown) { + // backlink to same SCC (still in stack) + if (called->_cycleLow < _cycleLow) + _cycleLow = called->_cycleLow; + + if (0) + qDebug("%s D%02d - %s (%d)", + QString().fill(' ', d+1).ascii(), d+1, + called->prettyName().ascii(), called->_cycleLow); + } + else { + if (0) + qDebug("%s D%02d - %s (%d) [Not on stack]", + QString().fill(' ', d+1).ascii(), d+1, + called->prettyName().ascii(), called->_cycleLow); + } + } + + if (prefixNo == _cycleLow) { + // this is the base of a SCC. + + if (*pTop == this) { + *pTop = _cycleStackDown; + _cycleStackDown = 0; + } + else { + // a SCC with >1 members + + TraceFunctionCycle* cycle = data()->functionCycle(this); + if (0) qDebug("BASE CYC %d %s", + cycle->cycleNo(), prettyName().ascii()); + while(*pTop) { + TraceFunction* top = *pTop; + cycle->add(top); + + // remove from stack + *pTop = top->_cycleStackDown; + top->_cycleStackDown = 0; + + if (0) qDebug("CYC %s", top->prettyName().ascii()); + if (top == this) break; + } + } + } + if (0) + qDebug("%s D%02d < %s (%d)", + QString().fill(' ', d).ascii(), d, + prettyName().ascii(), _cycleLow); +} + + +TraceInstrMap* TraceFunction::instrMap() +{ +#if USE_FIXCOST + + if (_instrMapFilled) return _instrMap; + _instrMapFilled = true; + if (!_instrMap) + _instrMap = new TraceInstrMap; + + TraceLine* l = 0; + TraceInstr* i = 0; + TracePartInstr* pi = 0; + TraceInstrCall* ic = 0; + TracePartInstrCall* pic = 0; + + TraceInclusiveCostList pfList = deps(); + TracePartFunction* pf = (TracePartFunction*) pfList.first(); + for(; pf; pf = (TracePartFunction*) pfList.next()) { + + if (0) qDebug("PartFunction %s:%d", + pf->function()->name().ascii(), pf->part()->partNumber()); + + FixCost* fc = pf->firstFixCost(); + for(; fc; fc = fc->nextCostOfPartFunction()) { + if (fc->addr() == 0) continue; + + if (!l || (l->lineno() != fc->line()) || + (l->functionSource() != fc->functionSource())) + l = fc->functionSource()->line(fc->line(),true); + + if (!i || i->addr() != fc->addr()) { + i = &(*_instrMap)[fc->addr()]; + if (!i->isValid()) { + i->setFunction(this); + i->setAddr(fc->addr()); + i->setLine(l); + } + pi = 0; + } + if (!pi || pi->part() != fc->part()) + pi = i->partInstr(fc->part(), pf); + fc->addTo(pi); + } + + TraceInstr* to = 0; + TraceInstrJump* ij; + TracePartInstrJump* pij; + FixJump* fj = pf->firstFixJump(); + for(; fj; fj = fj->nextJumpOfPartFunction()) { + if (fj->addr() == 0) continue; + + if (!l || (l->lineno() != fj->line()) || + (l->functionSource() != fj->source())) + l = fj->source()->line(fj->line(),true); + + if (!i || i->addr() != fj->addr()) { + i = &(*_instrMap)[fj->addr()]; + if (!i->isValid()) { + i->setFunction(this); + i->setAddr(fj->addr()); + i->setLine(l); + } + } + + to = fj->targetFunction()->instr(fj->targetAddr(), true); + + ij = i->instrJump(to, fj->isCondJump()); + pij = ij->partInstrJump(fj->part()); + + fj->addTo(pij); + } + + TracePartCallList pcList = pf->partCallings(); + TracePartCall* pc = pcList.first(); + for(; pc; pc = pcList.next()) { + + if (0) qDebug("PartCall %s:%d", + pc->call()->name().ascii(), + pf->part()->partNumber()); + + FixCallCost* fcc = pc->firstFixCallCost(); + for(; fcc; fcc = fcc->nextCostOfPartCall()) { + if (fcc->addr() == 0) continue; + + if (!l || (l->lineno() != fcc->line()) || + (l->functionSource() != fcc->functionSource())) + l = fcc->functionSource()->line(fcc->line(),true); + + if (!i || i->addr() != fcc->addr()) { + i = &(*_instrMap)[fcc->addr()]; + if (!i->isValid()) { + i->setFunction(this); + i->setAddr(fcc->addr()); + i->setLine(l); + } + } + if (!ic || ic->call() != pc->call() || ic->instr() != i) { + ic = pc->call()->instrCall(i); + pic = 0; + } + if (!pic || pic->part() != fcc->part()) + pic = ic->partInstrCall(fcc->part(), pc); + + fcc->addTo(pic); + if (0) qDebug("Add FixCallCost %s:%d/0x%s, CallCount %s", + fcc->functionSource()->file()->shortName().ascii(), + fcc->line(), fcc->addr().toString().ascii(), + fcc->callCount().pretty().ascii()); + } + } + } + +#endif + + return _instrMap; +} + + + +//--------------------------------------------------- +// TraceFunctionCycle + +TraceFunctionCycle::TraceFunctionCycle(TraceFunction* f, int n) +{ + _base = f; + _cycleNo = n; + _cycle = this; + + setPosition(f->data()); + setName(QString("<cycle %1>").arg(n)); + + // reset to attributes of base function + setFile(_base->file()); + setClass(_base->cls()); + setObject(_base->object()); +} + +void TraceFunctionCycle::init() +{ + _members.clear(); + _memberSet.clear(); + _callers.clear(); + // this deletes all TraceCall's to members + _callings.clear(); + + invalidate(); +} + +void TraceFunctionCycle::add(TraceFunction* f) +{ + _members.append(f); + _memberSet.insert(f,1); +} + +void TraceFunctionCycle::setup() +{ + if (_members.count()==0) return; + + TraceFunction* f; + for (f=_members.first();f;f=_members.next()) { + + // the cycle takes all outside callers from its members + TraceCall *call; + TraceCallList l = f->callers(); + for (call=l.first();call;call=l.next()) { + if ( _memberSet.contains(call->caller()) ) continue; + _callers.append(call); + } + + // the cycle has a call to each member + call = new TraceCall(this, f); + call->invalidate(); + _callings.append(call); + + // now do some faking... + f->setCycle(this); + } + invalidate(); +} + + +//--------------------------------------------------- +// TraceClass + +TraceClass::TraceClass() +{ + // we are the owner of items generated in our factory + _deps.setAutoDelete(true); +} + +TraceClass::~TraceClass() +{} + +QString TraceClass::prettyName() const +{ + if (_name.isEmpty()) + return QString("(global)"); + return _name; +} + +TracePartClass* TraceClass::partClass(TracePart* part) +{ + TracePartClass* item = (TracePartClass*) findDepFromPart(part); + if (!item) { + item = new TracePartClass(this); + item->setPosition(part); + addDep(item); + } + return item; +} + +void TraceClass::addFunction(TraceFunction* function) +{ +#if TRACE_ASSERTIONS + if (function->cls() != this) { + qDebug("Can't add function to a class not enclosing this function\n"); + return; + } + + if (_functions.findRef(function)>=0) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), + function->fullName().ascii(), _functions.count()); +#endif +} + + + +//--------------------------------------------------- +// TraceFile + +TraceFile::TraceFile() +{ + // we are the owner of items generated in our factory + _deps.setAutoDelete(true); +} + +TraceFile::~TraceFile() +{} + +TracePartFile* TraceFile::partFile(TracePart* part) +{ + TracePartFile* item = (TracePartFile*) findDepFromPart(part); + if (!item) { + item = new TracePartFile(this); + item->setPosition(part); + addDep(item); + } + return item; +} + +void TraceFile::addFunction(TraceFunction* function) +{ +#if TRACE_ASSERTIONS + if (function->file() != this) { + qDebug("Can't add function to a file not enclosing this function\n"); + return; + } + + if (_functions.findRef(function)>=0) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), + function->fullName().ascii(), _functions.count()); +#endif +} + + +void TraceFile::addSourceFile(TraceFunctionSource* sourceFile) +{ +#if TRACE_ASSERTIONS + if (sourceFile->file() != this) { + qDebug("Can't add sourceFile to a file not having lines for it\n"); + return; + } +#endif + + _sourceFiles.append(sourceFile); + // not truely needed, as we don't use the sourceFiles for cost update + invalidate(); + +#if TRACE_DEBUG + qDebug("%s \n added SourceFile %s (now %d)", + fullName().ascii(), sourceFile->fullName().ascii(), + _sourceFiles.count()); +#endif +} + + + +void TraceFile::setDirectory(const QString& dir) +{ + if (dir.endsWith("/")) + _dir = dir.left(dir.length()-1); + else + _dir = dir; +} + +QString TraceFile::directory() +{ + if (!_dir.isEmpty()) return _dir; + + int lastIndex = 0, index; + while ( (index=_name.find("/", lastIndex)) >=0) + lastIndex = index+1; + + if (lastIndex==0) return QString::null; + + // without ending "/" + return _name.left(lastIndex-1); +} + + +QString TraceFile::shortName() const +{ + int lastIndex = 0, index; + while ( (index=_name.find("/", lastIndex)) >=0) + lastIndex = index+1; + + return _name.mid(lastIndex); +} + +QString TraceFile::prettyName() const +{ + QString sn = shortName(); + + if (sn.isEmpty()) + return i18n("(unknown)"); + + return sn; +} + +QString TraceFile::prettyLongName() const +{ + if (_name.isEmpty()) + return i18n("(unknown)"); + return _name; +} + + +//--------------------------------------------------- +// TraceObject + +TraceObject::TraceObject() +{ + // we are the owner of items generated in our factory + _deps.setAutoDelete(true); +} + +TraceObject::~TraceObject() +{} + +TracePartObject* TraceObject::partObject(TracePart* part) +{ + TracePartObject* item = (TracePartObject*) findDepFromPart(part); + if (!item) { + item = new TracePartObject(this); + item->setPosition(part); + addDep(item); + } + return item; +} + +void TraceObject::addFunction(TraceFunction* function) +{ +#if TRACE_ASSERTIONS + if (function->object() != this) { + qDebug("Can't add function to an object not enclosing this function\n"); + return; + } + + if (_functions.findRef(function)>=0) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + fullName().ascii(), + function->fullName().ascii(), _functions.count()); +#endif +} + +// strip path +void TraceObject::setName(const QString& name) +{ + _name = name; + + int lastIndex = 0, index; + while ( (index=_name.find("/", lastIndex)) >=0) + lastIndex = index+1; + + _shortName = _name.mid(lastIndex); +} + +QString TraceObject::prettyName() const +{ + if (_shortName.isEmpty()) + return i18n("(unknown)"); + + return _shortName; +} + +//--------------------------------------------------- +// TracePart + +TracePart::TracePart(TraceData* data, QFile* file) +{ + setPosition(data); + + _dep = data; + _file = file; + if (_file) + _name = _file->name(); + _active = true; + + _number = 0; + _tid = 0; + _pid = 0; + + _fixSubMapping = 0; +} + +TracePart::~TracePart() +{ + delete _file; + + delete _fixSubMapping; +} + +void TracePart::setPartNumber(int n) +{ + if (data()->maxPartNumber() <n) data()->setMaxPartNumber(n); + _number = n; +} + +void TracePart::setThreadID(int tid) +{ + if (data()->maxThreadID() <tid) data()->setMaxThreadID(tid); + _tid = tid; +} + +void TracePart::setProcessID(int pid) +{ + _pid = pid; +} + + + +// strip path +QString TracePart::shortName() const +{ + int lastIndex = 0, index; + while ( (index=_name.find("/", lastIndex)) >=0) + lastIndex = index+1; + + return _name.mid(lastIndex); +} + +QString TracePart::prettyName() const +{ + QString name = QString("%1.%2").arg(_pid).arg(_number); + if (data()->maxThreadID()>1) + name += QString("-%3").arg(_tid); + return name; +} + +bool TracePart::activate(bool active) +{ + if (_active == active) return false; + _active = active; + + // to be done by the client of this function + // data()->invalidateDynamicCost(); + // So better use the TraceData functions... + + return true; +} + + + +//--------------------------------------------------- +// TracePartList + +int TracePartList::compareItems ( Item item1, Item item2 ) +{ + TracePart* p1 = (TracePart*) item1; + TracePart* p2 = (TracePart*) item2; + int mTID = p1->data()->maxThreadID()+1; + int mNum = p1->data()->maxPartNumber()+1; + + return + (p1->processID() - p2->processID()) * mTID * mNum + + (p1->partNumber() - p2->partNumber()) * mTID + + (p1->threadID() - p2->threadID()); +} + +QString TracePartList::names() const +{ + QString res; + TracePart* p; + TracePartList l = *this; + for (p=l.first();p;p=l.next()) { + if (!res.isEmpty()) res += ", "; + res += p->shortName(); + } + + return res; +} + + + +//--------------------------------------------------- +// TraceData + + +// create vectors with reasonable default sizes, but not wasting memory +TraceData::TraceData(TopLevel* top) +{ + _topLevel = top; + init(); +} + +TraceData::TraceData(const QString& base) +{ + _topLevel = 0; + init(); + load(base); +} + +void TraceData::init() +{ + _parts.setAutoDelete(true); + + _functionCycleCount = 0; + _inFunctionCycleUpdate = false; + + _maxThreadID = 0; + _maxPartNumber = 0; + _fixPool = 0; + _dynPool = 0; +} + +TraceData::~TraceData() +{ + if (_fixPool) delete _fixPool; + if (_dynPool) delete _dynPool; +} + +QString TraceData::shortTraceName() const +{ + int lastIndex = 0, index; + while ( (index=_traceName.find("/", lastIndex)) >=0) + lastIndex = index+1; + + return _traceName.mid(lastIndex); +} + +FixPool* TraceData::fixPool() +{ + if (!_fixPool) + _fixPool = new FixPool(); + + return _fixPool; +} + +DynPool* TraceData::dynPool() +{ + if (!_dynPool) + _dynPool = new DynPool(); + + return _dynPool; +} + + +/** + * Two cases: + * + * - <base> is a directory: Load first profile data file available + * - <base> is a file name without part/thread suffixes + */ +void TraceData::load(const QString& base) +{ + bool baseExisting = true; + + _traceName = base; + QFileInfo finfo(base); + QString file = finfo.fileName(); + QDir dir = finfo.dir(); + + if (!finfo.exists()) { + baseExisting = false; + } + else if (finfo.isDir()) { + // search for first profile data file in directory + dir = QDir(base); + + QStringList prefixList; + prefixList << "callgrind.out" << "cachegrind.out"; + for ( QStringList::Iterator it = prefixList.begin(); + it != prefixList.end(); ++it ) { + file = *it; + + // search for ".pid" + QStringList strList = dir.entryList(file+".*", QDir::Files); + if (strList.count()>0) { + int l = file.length(); + file = strList.first(); + l++; + while(file[l] >= '0' && file[l] <= '9') l++; + file = file.left(l); + break; + } + } + + _traceName = dir.path() + "/" + file; + } + + QStringList strList; + strList += dir.entryList(file+".*", QDir::Files); + strList += dir.entryList(file+"-*", QDir::Files); + + baseExisting = QFile::exists(_traceName); + if (baseExisting) + strList << file; + + if (strList.count() == 0) { + _traceName = base + "/" + file + " " + i18n("(not found)"); + return; + } + + + // try to guess pid from file name + unsigned int pos = file.length(); + unsigned int pid = 0, f=1; + pos--; + while(pos>0) { + if (file[pos] < '0' || file[pos] > '9') break; + pid += f * (file[pos].latin1() - '0'); + pos--; + f *= 10; + } + + QStringList::Iterator it; + unsigned int maxNumber = 0; + for (it = strList.begin(); it != strList.end(); ++it ) { + TracePart* p = addPart( dir.path(), *it ); + + if (!p) { + kdDebug() << "Error loading " << *it << endl; + continue; + } + + const QString& str = *it; + unsigned int pos = file.length(); + + // try to guess part number from file name + unsigned int n = 0; + if ((str.length() > pos) && (str[pos] == '.')) { + pos++; + while(str.length()>pos) { + if (str[pos] < '0' || str[pos] > '9') break; + n = 10*n + (str[pos++] - '0'); + } + } + + // try to guess thread number from file name + unsigned int t = 0; + if ((str.length() > pos) && (str[pos] == '-')) { + pos++; + while(str.length()>pos) { + if (str[pos] < '0' || str[pos] > '9') break; + t = 10*t + (str[pos++] - '0'); + } + } + + //qDebug("File %s: Part %d, Thread %d", (*it).ascii(), n, t); + + if (p->partNumber()>0) n = p->partNumber(); + if (n>maxNumber) maxNumber = n; + if (n==0) n = maxNumber+1; + p->setPartNumber(n); + + if (p->threadID()==0) p->setThreadID(t); + if (p->processID()==0) p->setProcessID(pid); + + _parts.append(p); + } + _parts.sort(); + + invalidateDynamicCost(); + updateFunctionCycles(); + + // clear loading messages from status bar + if (_topLevel) _topLevel->showStatus(QString::null, 0); +} + +TracePart* TraceData::addPart(const QString& dir, const QString& name) +{ + QString filename = QString("%1/%2").arg(dir).arg(name); +#if TRACE_DEBUG + qDebug("TraceData::addPart('%s')", filename.ascii()); +#endif + + QFile* file = new QFile(filename); + + Loader* l = Loader::matchingLoader(file); + if (!l) return 0; + + if (_topLevel) + _topLevel->connect(l, SIGNAL(updateStatus(QString, int)), + SLOT(showStatus(QString, int))); + + TracePart* part = new TracePart(this, file); + + if (! l->loadTrace(part)) { + delete part; + part = 0; + } + + if (_topLevel) l->disconnect(_topLevel); + + return part; +} + +bool TraceData::activateParts(const TracePartList& l) +{ + bool changed = false; + + TracePart* part; + for (part=_parts.first();part;part=_parts.next()) + if (part->activate(l.containsRef(part)>0)) + changed = true; + + if (changed) { + // because active parts have changed, throw away calculated + // costs... + invalidateDynamicCost(); + updateFunctionCycles(); + } + + return changed; +} + + +bool TraceData::activateParts(TracePartList l, bool active) +{ + bool changed = false; + + TracePart* part; + for (part=l.first();part;part=l.next()) + if (_parts.findRef(part)>=0) + if (part->activate(active)) + changed = true; + + if (changed) { + invalidateDynamicCost(); + updateFunctionCycles(); + } + + return changed; +} + +bool TraceData::activatePart(TracePart* p, bool active) +{ + return p->activate(active); +} + +bool TraceData::activateAll(bool active) +{ + return activateParts(_parts, active); +} + + +TracePart* TraceData::part(QString& name) +{ + TracePart* part; + for (part=_parts.first();part;part=_parts.next()) + if (part->name() == name) + return part; + return 0; +} + +QString TraceData::activePartRange() +{ + QString res; + int r1=-1, r2=-1, count=1; + TracePart* part; + for (part=_parts.first();part;part=_parts.next(), count++) + if (part->isActive()) { + if (r1<0) { r1 = r2 = count; } + else if (r2 == count-1) { r2 = count; } + else { + if (!res.isEmpty()) res += ";"; + if (r1==r2) res += QString::number(r1); + else res += QString("%1-%2").arg(r1).arg(r2); + r1 = r2 = count; + } + } + if (r1>=0) { + if (!res.isEmpty()) res += ";"; + if (r1==r2) res += QString::number(r1); + else res += QString("%1-%2").arg(r1).arg(r2); + } + + return res; +} + +void TraceData::invalidateDynamicCost() +{ + // invalidate all dynamic costs + + TraceObjectMap::Iterator oit; + for ( oit = _objectMap.begin(); + oit != _objectMap.end(); ++oit ) + (*oit).invalidate(); + + TraceClassMap::Iterator cit; + for ( cit = _classMap.begin(); + cit != _classMap.end(); ++cit ) + (*cit).invalidate(); + + TraceFileMap::Iterator fit; + for ( fit = _fileMap.begin(); + fit != _fileMap.end(); ++fit ) + (*fit).invalidate(); + + TraceFunctionMap::Iterator it; + for ( it = _functionMap.begin(); + it != _functionMap.end(); ++it ) { + (*it).invalidateDynamicCost(); + } + + invalidate(); + +} + + +TraceObject* TraceData::object(const QString& name) +{ + TraceObject& o = _objectMap[name]; + if (!o.data()) { + // was created + o.setPosition(this); + o.setName(name); + +#if TRACE_DEBUG + qDebug("Created %s [TraceData::object]", + o.fullName().ascii()); +#endif + } + return &o; +} + + +TraceFile* TraceData::file(const QString& name) +{ + TraceFile& f = _fileMap[name]; + if (!f.data()) { + // was created + f.setPosition(this); + f.setName(name); + +#if TRACE_DEBUG + qDebug("Created %s [TraceData::file]", + f.fullName().ascii()); +#endif + } + return &f; +} + + +// usually only called by function() +TraceClass* TraceData::cls(const QString& fnName, QString& shortName) +{ + int lastIndex = 0, index, pIndex; + + // we ignore any "::" after a '(' or a space + pIndex=fnName.find("(", 0); + +#if 0 + int sIndex=fnName.find(" ", 0); + if (sIndex>=0) + if ((pIndex == -1) || (sIndex < pIndex)) + pIndex = sIndex; +#endif + + while ((index=fnName.find("::", lastIndex)) >=0) { + if (pIndex>=0 && pIndex<index) break; + lastIndex = index+2; + } + + QString clsName = (lastIndex < 3) ? QString::null : + fnName.left(lastIndex-2); + shortName = fnName.mid(lastIndex); + + TraceClass& c = _classMap[clsName]; + if (!c.data()) { + // was created + c.setPosition(this); + c.setName(clsName); + +#if TRACE_DEBUG + qDebug("Created %s [TraceData::cls]", + c.fullName().ascii()); +#endif + } + return &c; +} + + +// name is inclusive class/namespace prefix +TraceFunction* TraceData::function(const QString& name, + TraceFile* file, TraceObject* object) +{ + // strip class name + QString shortName; + TraceClass* c = cls(name, shortName); + + if (!file || !object || !c) { + qDebug("ERROR - no file/object/class for %s ?!", name.ascii()); + return 0; + } + + // Don't use file in key: A function can go over many files + // (inlined parts), but still is ONE function. + QString key = name + object->shortName(); + + TraceFunctionMap::Iterator it; + it = _functionMap.find(key); + if (it == _functionMap.end()) { + it = _functionMap.insert(key, TraceFunction()); + TraceFunction& f = it.data(); + + f.setPosition(this); + f.setName(name); + f.setClass(c); + f.setObject(object); + f.setFile(file); + f.setMapIterator(it); + +#if TRACE_DEBUG + qDebug("Created %s [TraceData::function]\n for %s, %s, %s", + f.fullName().ascii(), + c->fullName().ascii(), file->fullName().ascii(), + object ? object->fullName().ascii() : "(unknown object)"); +#endif + + c->addFunction(&f); + object->addFunction(&f); + file->addFunction(&f); + } + + return &(it.data()); +} + +TraceFunctionMap::Iterator TraceData::functionIterator(TraceFunction* f) +{ + + // IMPORTANT: build as SAME key as used in function() above !! + QString key; + + if (f->cls()) key = f->cls()->name() + "::"; + key += f->name(); + key += f->object()->shortName(); + + return _functionMap.find(key); +} + +TraceFunctionMap::ConstIterator TraceData::functionBeginIterator() const +{ + return _functionMap.begin(); +} + +TraceFunctionMap::ConstIterator TraceData::functionEndIterator() const +{ + return _functionMap.end(); +} + + +void TraceData::resetSourceDirs() +{ + TraceFileMap::Iterator fit; + for ( fit = _fileMap.begin(); + fit != _fileMap.end(); ++fit ) + (*fit).resetDirectory(); +} + +void TraceData::update() +{ + if (!_dirty) return; + + clear(); + _totals.clear(); + + TracePart* part; + for (part=_parts.first();part;part=_parts.next()) { + _totals.addCost(part->totals()); + if (part->isActive()) + addCost(part->totals()); + } + + _dirty = false; +} + +TraceCost* TraceData::search(TraceItem::CostType t, QString name, + TraceCostType* ct, TraceCost* parent) +{ + TraceCost* result = 0; + TraceItem::CostType pt = parent ? parent->type() : NoCostType; + SubCost sc, scTop = 0; + + switch(t) { + case Function: + { + TraceFunction *f; + TraceFunctionMap::Iterator it; + for ( it = _functionMap.begin(); + it != _functionMap.end(); ++it ) { + f = &(*it); + + if (f->name() != name) continue; + + if ((pt == Class) && (parent != f->cls())) continue; + if ((pt == File) && (parent != f->file())) continue; + if ((pt == Object) && (parent != f->object())) continue; + + if (ct) { + sc = f->inclusive()->subCost(ct); + if (sc <= scTop) continue; + scTop = sc; + } + + result = f; + } + } + break; + + case File: + { + TraceFile *f; + TraceFileMap::Iterator it; + for ( it = _fileMap.begin(); + it != _fileMap.end(); ++it ) { + f = &(*it); + if (f->name() != name) continue; + if (ct) { + sc = f->subCost(ct); + if (sc <= scTop) continue; + scTop = sc; + } + result = f; + } + } + break; + + case Class: + { + TraceClass *c; + TraceClassMap::Iterator it; + for ( it = _classMap.begin(); + it != _classMap.end(); ++it ) { + c = &(*it); + if (c->name() != name) continue; + if (ct) { + sc = c->subCost(ct); + if (sc <= scTop) continue; + scTop = sc; + } + result = c; + } + } + break; + + case Object: + { + TraceObject *o; + TraceObjectMap::Iterator it; + for ( it = _objectMap.begin(); + it != _objectMap.end(); ++it ) { + o = &(*it); + if (o->name() != name) continue; + if (ct) { + sc = o->subCost(ct); + if (sc <= scTop) continue; + scTop = sc; + } + result = o; + } + } + break; + + case Instr: + if (pt == Function) { + TraceInstrMap* instrMap = ((TraceFunction*)parent)->instrMap(); + if (!instrMap) break; + + TraceInstr *instr; + TraceInstrMap::Iterator it; + for ( it = instrMap->begin(); + it != instrMap->end(); ++it ) { + instr = &(*it); + if (instr->name() != name) continue; + result = instr; + } + } + break; + + case Line: + { + TraceFunctionSourceList sList; + if (pt == Function) + sList = ((TraceFunction*)parent)->sourceFiles(); + else if (pt == FunctionSource) + sList.append((TraceFunctionSource*) parent); + else break; + + TraceLineMap* lineMap; + TraceLine* line; + TraceLineMap::Iterator it; + TraceFunctionSource* fs; + for(fs = sList.first(); fs; fs = sList.next()) { + lineMap = fs->lineMap(); + if (!lineMap) continue; + + for ( it = lineMap->begin(); + it != lineMap->end(); ++it ) { + line = &(*it); + if (line->name() != name) continue; + result = line; + } + } + } + break; + + default: + break; + } + + return result; +} + + +TraceFunctionCycle* TraceData::functionCycle(TraceFunction* f) +{ + TraceFunctionCycle* cycle; + for (cycle=_functionCycles.first();cycle;cycle=_functionCycles.next()) + if (cycle->base() == f) return cycle; + + _functionCycleCount++; + cycle = new TraceFunctionCycle(f, _functionCycleCount); + + _functionCycles.append(cycle); + return cycle; +} + + +void TraceData::updateFunctionCycles() +{ + //qDebug("Updating cycles..."); + + // init cycle info + TraceFunctionCycle* cycle; + for (cycle=_functionCycles.first();cycle;cycle=_functionCycles.next()) + cycle->init(); + + TraceFunctionMap::Iterator it; + for ( it = _functionMap.begin(); it != _functionMap.end(); ++it ) + (*it).cycleReset(); + + if (!Configuration::showCycles()) return; + + _inFunctionCycleUpdate = true; + + +#if 0 + int fCount = _functionMap.size(), fNo = 0, progress=0, p; + QString msg = i18n("Recalculating Function Cycles..."); + if (_topLevel) _topLevel->showStatus(msg,0); +#endif + + // DFS and collapse strong connected components (Tarjan) + int pNo = 0; + TraceFunction* stackTop; + for ( it = _functionMap.begin(); it != _functionMap.end(); ++it ) { + +#if 0 + if (_topLevel) { + fNo++; + p = 100*fNo/fCount; + if (p> progress) { + progress = p; + _topLevel->showStatus(msg, p); + } + } +#endif + + stackTop = 0; + (*it).cycleDFS(1, pNo, &stackTop); + } + + // postprocess cycles + for (cycle=_functionCycles.first();cycle;cycle=_functionCycles.next()) + cycle->setup(); + + _inFunctionCycleUpdate = false; + // we have to invalidate costs because cycles are now taken into account + invalidateDynamicCost(); + +#if 0 + if (0) if (_topLevel) _topLevel->showStatus(QString::null,0); +#endif +} + +void TraceData::updateObjectCycles() +{ +} + + +void TraceData::updateClassCycles() +{ +} + + +void TraceData::updateFileCycles() +{ +} + + diff --git a/kcachegrind/kcachegrind/tracedata.h b/kcachegrind/kcachegrind/tracedata.h new file mode 100644 index 00000000..21922a05 --- /dev/null +++ b/kcachegrind/kcachegrind/tracedata.h @@ -0,0 +1,1967 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Classes holding profiling data for + * multiple tracefiles for one command. + * See class TraceData first. + */ + +#ifndef TRACEDATA_H +#define TRACEDATA_H + +#include <qstring.h> +#include <qstringlist.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qptrvector.h> +#include <qcolor.h> + +#include "subcost.h" +#include "utils.h" + +class QFile; + +/** + * All cost items are classes prefixed with "Trace". + * "TraceCost" holds basic cost metrics for the simplest, smallest + * trace entity: These are events counted for an instruction at + * a specific memory address of the traced program. + * All other cost items are derived from TraceCost, and add needed + * cost metrics, e.g. for a call the number of calls that happened. + * + * Abstract, i.e. never instantiated cost items are + * - TraceCost: Basic cost metrics (instr/read/write access + cache events) + * - TraceCallCost: Additional call count cost metric. + * - TraceInclusiveCost: Additional TraceCost aggregated. + * - TraceListCost: Adds dependency to a list of TraceCost's + * - TraceCallListCost: same for list of TraceCallCost's + * - TraceInclusiveListCost: same for list of TraceInclusiveCost's + * - TraceCostItem: Base for cost items for "interesting" costs: + * TraceFunction, TraceClass, TraceFile, TraceObject + * + * The smallest Cachegrind output is trace data indexed by a source + * line number, a TracePartLine. Another one is a call from one + * source line of a function to another function, a TracePartLineCall. + * All other cost items derive the value by summation of cost metrics + * from TraceLineItem and TracePartLineCall costs; their cost is + * calculated lazy on demand and cached afterwards. + * + * For cost items, which are sums over all trace files read in, the + * summed cost metrics change when e.g. a new trace file is read. + * Thus, their cached costs are invalidated, and again recalculated + * only on demand. In the following list, theses cost items are called + * "dynamic", the other "fixed" (but neverless calculated lazy). + * + * Cost Item Type Summation of ... + * + * TracePartLineCall fixed Read from trace file + * TracePartLine fixed Read from trace file + * TracePartCall fixed TracePartLineCall's + * TraceLineCall dynamic TracePartLineCall's + * TraceCall dynamic TraceLineCall's + * TraceLine dynamic TracePartLine's and TraceLineCall's + * TracePartFunction fixed TracePartLine's / TracePartCall's + * TraceFunction dynamic TraceLine's / TraceCall's (called from) + * TracePartClass fixed TracePartFunction's + * TraceClass dynamic TraceFunction's + * TracePartFile fixed TracePartFunction's + * TraceFile dynamic TraceFunction's + * TracePartObject fixed TracePartFunction's + * TraceObject dynamic TraceFunction's + * TracePart fixed TracePartLine's + * TraceData dynamic TracePart's + * + * As there exists only one TraceData object for a traced program, its the + * owner of some "high level" cost items. The following shows the owner + * relationship of the cost item classes, together with references. + * + * Cost Item Owner (& back ref) Other References to + * + * TracePartLineCall TraceLineCall + * TracePartCall TraceCall TracePartLineCall's + * TracePartLine TraceLine TracePartLineCall's + * TracePartFunction TraceFunction + * TracePartClass TraceClass TracePart + * TracePartFile TraceFile TracePart + * TracePartObject TraceObject TracePart + * TraceLineCall TraceCall TracePartLineCall's + * TraceCall TraceFunction TracePartCall's + * TraceLine TraceData TraceLineCall's + * TraceFunction TraceData TraceCall's (calling) + * TraceClass TraceData + * TraceFile TraceData + * TraceObject TraceData + * TracePart TraceData + * TraceData Main Application + * + * Convention: + * - The owner has a factory method for owned objects, + * and calls addXXX() to install references in other objects + * - The owner is first arg in a constructor. + */ + + +class FixString; +class FixCost; +class FixCallCost; +class FixJump; +class FixPool; +class DynPool; +class TopLevel; + +class TraceCost; +class TraceCostType; +class TraceCostMapping; +class TraceSubMapping; +class TraceJumpCost; +class TraceCallCost; +class TraceInclusiveCost; + +class TracePartInstr; +class TracePartInstrCall; +class TracePartLine; +class TracePartLineCall; +class TracePartCall; +class TracePartLineRegion; +class TracePartFunction; +class TracePartClass; +class TracePartObject; +class TracePartFile; + +class TraceInstr; +class TraceInstrJump; +class TraceInstrCall; +class TraceLine; +class TraceLineJump; +class TraceLineCall; +class TraceCall; +class TraceLineRegion; +class TraceFunctionSource; +class TraceFunction; +class TraceFunctionCycle; +class TraceClass; +class TraceObject; +class TraceFile; +class TracePart; +class TraceData; + +typedef QPtrList<TraceCost> TraceCostList; +typedef QPtrList<TraceJumpCost> TraceJumpCostList; +typedef QPtrList<TraceCallCost> TraceCallCostList; +typedef QPtrList<TraceInclusiveCost> TraceInclusiveCostList; + +typedef QPtrList<TracePartCall> TracePartCallList; +typedef QPtrList<TracePartInstr> TracePartInstrList; +typedef QPtrList<TracePartLine> TracePartLineList; +typedef QPtrList<TracePartLineRegion> TracePartLineRegionList; +typedef QPtrList<TracePartFunction> TracePartFunctionList; +typedef QPtrList<TracePartInstrCall> TracePartInstrCallList; +typedef QPtrList<TracePartLineCall> TracePartLineCallList; + + +typedef QPtrList<TraceInstr> TraceInstrList; +typedef QPtrList<TraceLine> TraceLineList; +typedef QPtrList<TraceInstrCall> TraceInstrCallList; +typedef QPtrList<TraceLineCall> TraceLineCallList; +typedef QPtrList<TraceCall> TraceCallList; +typedef QPtrList<TraceFile> TraceFileList; +typedef QPtrList<TraceLineRegion> TraceLineRegionList; +typedef QPtrList<TraceFunctionSource> TraceFunctionSourceList; +typedef QPtrList<TraceFunction> TraceFunctionList; +typedef QPtrList<TraceFunctionCycle> TraceFunctionCycleList; +typedef QMap<QString, TraceObject> TraceObjectMap; +typedef QMap<QString, TraceClass> TraceClassMap; +typedef QMap<QString, TraceFile> TraceFileMap; +typedef QMap<QString, TraceFunction> TraceFunctionMap; +typedef QMap<uint, TraceLine> TraceLineMap; + + +/** + * Addresses are 64bit values like costs to be able + * to always load profile data produced on 64bit + * architectures. + */ +class Addr +{ + public: + Addr() { _v=0; } + Addr(uint64 v) { _v = v; } + + // Interpretes char data at s as hex (without "0x" prefix) + // and return number of interpreted chars. + int set(const char *s); + bool set(FixString& s); + QString toString() const; + // similar to toString(), but adds a space every 4 digits + QString pretty() const; + + // returns true if this address is in [a-distance;a+distance] + bool isInRange(Addr a, int distance); + + bool operator==(const Addr& a) const { return (_v == a._v); } + bool operator!=(const Addr& a) const { return (_v != a._v); } + bool operator>(const Addr& a) const { return _v > a._v; } + bool operator>=(const Addr& a) const { return _v >= a._v; } + bool operator<(const Addr& a) const { return _v < a._v; } + bool operator<=(const Addr& a) const { return _v <= a._v; } + + Addr operator+(int d) const { return Addr(_v + d); } + Addr operator-(int d) const { return Addr(_v - d); } + + private: + uint64 _v; +}; + +typedef QMap<Addr, TraceInstr> TraceInstrMap; + + +/** + * Base class for cost items. + */ +class TraceItem +{ +public: + + // RTTI for trace item classes, using type() method + enum CostType { Item, Cost, + PartInstr, Instr, + PartLine, Line, + PartInstrJump, InstrJump, + PartLineJump, LineJump, + PartInstrCall, InstrCall, + PartLineCall, LineCall, + PartCall, Call, + PartLineRegion, LineRegion, + PartFunction, FunctionSource, Function, FunctionCycle, + PartClass, Class, ClassCycle, + PartFile, File, FileCycle, + PartObject, Object, ObjectCycle, + Part, Data, + MaxCostType, NoCostType }; + + TraceItem(); + virtual ~TraceItem(); + + virtual CostType type() const { return Item; } + + // conversion of item type to locale independent string (e.g. for config) + static QString typeName(CostType); + static CostType costType(QString); + // the versions below should be used for user visible strings, as + // these use localisation settings + static QString i18nTypeName(CostType); + static CostType i18nCostType(QString); + // clean up some static data + static void cleanup(); + + /** + * Returns dynamic name info (without type) + */ + virtual QString name() const; + + /** + * Same as name, but sometimes nicer for humans :-) + */ + virtual QString prettyName() const; + + /** + * Returns text of all cost metrics + */ + virtual QString costString(TraceCostMapping*); + + /** + * Returns type name + dynamic name + */ + QString fullName() const; + + /** + * Returns full name + cost text + */ + QString toString(); + + /** + * Set all cost counters to zero + */ + virtual void clear(); + + /** Invalidate the cost attributes. + * An invalidated object needs to be recalculated when a cost + * attribute is requested (e.g. by subCost()). + * Has to be overwritten by subclasses when the cost influences costs of + * other cost items. If only one item depends on the cost of this item, + * it can by set with setDependant() without a need for overwriting. + */ + virtual void invalidate(); + + /** + * Sets a dependant to be invalidated when this cost is invalidated. + * Call this function directly after the constructor. + */ + void setDependant(TraceItem* d) { _dep = d; } + + TraceItem* dependant() { return _dep; } + + /** + * If this item is from a single profile data file, position + * points to a TracePart, otherwise to a TraceData object. + */ + void setPosition(TraceItem* p) { _position = p; } + + // getters for specific positions, to be overwritten + virtual TracePart* part(); + virtual const TracePart* part() const; + virtual TraceData* data(); + virtual const TraceData* data() const; + + protected: + /** Updates cost attributes. + * This has to be called by subclasses that access cost attributes + * directly + */ + virtual void update(); + + bool _dirty; + + TraceItem* _position; + TraceItem* _dep; + + private: + static QString *_typeName, *_i18nTypeName; +}; + + + +/** + * An array of basic cost metrics for a trace item. + * + * The semantic of specific indexes is stored in the + * TraceCostMapping of the TraceData object holding this TraceCost. + */ +class TraceCost: public TraceItem +{ +public: + /** + * The maximal number of subcosts a TraceCost can have. + */ + static const int MaxRealIndex; +#define MaxRealIndexValue 13 + static const int InvalidIndex; + + + TraceCost(); + virtual ~TraceCost(); + + virtual CostType type() const { return Cost; } + virtual QString costString(TraceCostMapping*); + + virtual void clear(); + + // set the cost according to a submapping and a list of ASCII numbers + void set(TraceSubMapping*, const char*); + void set(TraceSubMapping*, FixString&); + // add a cost according to a submapping and a list of ASCII numbers + void addCost(TraceSubMapping*, const char*); + void addCost(TraceSubMapping*, FixString&); + // add the cost of another item + void addCost(TraceCost* item); + void addCost(int index, SubCost value); + + // maximal cost + void maxCost(TraceSubMapping*, FixString&); + void maxCost(TraceCost* item); + void maxCost(int index, SubCost value); + TraceCost diff(TraceCost* item); + + virtual void invalidate(); + + /** Returns a sub cost. This automatically triggers + * a call to update() if needed. + */ + SubCost subCost(TraceCostType*); + + /** + * Same as above, but only for real types + */ + SubCost subCost(int); + + /** Returns a cost attribute converted to a string + * (with space after every 3 digits) + */ + QString prettySubCost(TraceCostType*); + + protected: + virtual void update(); + + SubCost _cost[MaxRealIndexValue]; + int _count; // only _count first indexes of _cost are used + + // cache last virtual subcost for faster access + SubCost _cachedCost; + TraceCostType* _cachedType; +}; + + + +/** + * A cost type, e.g. "L1 Read Miss", short "l1rm". + * + * We distinguish "real" cost types, where the values come + * from the trace file, and "virtual" cost types, which + * are calculated from the real ones. + * + * For a virtual cost type, set a formula to calculate it: + * e.g. for "Read Misses" : "l1rm + l2rm". + * To allow for parsing, you must specify a TraceCostMapping + * with according cost types (e.g. "l1rm" and "l2rm" for above formula). + * + * The cost type with empty name is the "const" cost type. + */ +class TraceCostType +{ +public: + + /** + * <name> is a short (non-localized) identifier for the cost type, + * e.g. "l1rm". + * <longName> is a long localized string, e.g. "L1 Read Miss" + * <formula> uses short names to reference other types + */ + TraceCostType(QString name, + QString longName = QString::null, + QString formula = QString::null); + + void setName(QString n) { _name = n; } + void setLongName(QString n) { _longName = n; } + void setMapping(TraceCostMapping* m); + void setFormula(QString); + // default arg is for specifying a real type, but index unknown + void setRealIndex(int r = TraceCost::MaxRealIndex); + + const QString& name() { return _name; } + const QString& longName() { return _longName; } + const QString& formula() { return _formula; } + TraceCostMapping* mapping() { return _mapping; } + int realIndex() { return _realIndex; } + bool isReal() { return _formula.isEmpty(); } + QColor color(); + + /* + * returns true if all cost type names can be resolved in formula + */ + bool parseFormula(); + QString parsedFormula(); + + SubCost subCost(TraceCost*); + + /* + * For virtual costs, returns a histogram for use with + * partitionPixmap(). + * Returns maximal real index. + */ + int histCost(TraceCost* c, double total, double* hist); + + // application wide known types, referenced by short name + // next 2 functions return a new type object instance + static TraceCostType* knownRealType(QString); + static TraceCostType* knownVirtualType(QString); + static void add(TraceCostType*); + static bool remove(QString); + static int knownTypeCount(); + static TraceCostType* knownType(int); + +private: + + QString _name, _longName, _formula; + TraceCostMapping* _mapping; + bool _parsed, _inParsing; + // index MaxRealIndex is for constant addition + int _coefficient[MaxRealIndexValue]; + int _realIndex; + + static QPtrList<TraceCostType>* _knownTypes; +}; + + +/** + * A class for managing a set of cost types. + * + * Each cost type has an index: + * - Real costs are in range [0 .. TraceCost:MaxRealIndex[ + * - Virtual costs are in range [MaxRealIndex, ...] + */ +class TraceCostMapping +{ +public: + TraceCostMapping(); + ~TraceCostMapping(); + + /** + * Defines a sub mapping with a list of real types + * If <create> is false, checks if this is a existing sub mapping. + */ + TraceSubMapping* subMapping(QString types, bool create = true); + + // "knows" about some real types + int addReal(QString); + int add(TraceCostType*); + bool remove(TraceCostType*); + int realCount() { return _realCount; } + int virtualCount() { return _virtualCount; } + int minVirtualIndex() { return TraceCost::MaxRealIndex; } + TraceCostType* type(int); + TraceCostType* realType(int); + TraceCostType* virtualType(int); + TraceCostType* type(QString); + TraceCostType* typeForLong(QString); + int realIndex(QString); + int index(QString); + QColor* realColors() { return _realColor; } + + /** + * Adds all known virtual types that can be parsed + */ + int addKnownVirtualTypes(); + +private: + // we support only a fixed number of real and virtual types + TraceCostType* _real[MaxRealIndexValue]; + QColor _realColor[MaxRealIndexValue]; + TraceCostType* _virtual[MaxRealIndexValue]; + int _realCount, _virtualCount; +}; + +/** + * A submapping of a TraceCostMapping + * + * This is a fixed ordered list of indexes for real cost types + * in a mapping. + * + * You can define a mapping by requesting submappings. Undefined cost + * types will get a new real type index. + * TraceCostMapping m; + * sm1 = m.subMapping("Event1 Cost1 Cost2"); // returns submap [0,1,2] + * sm2 = m.subMapping("Event2 Cost3 Event1"); // returns submap [3,4,0] + * Real types of m will be: + * (0:Event1, 1:Cost1, 2:Cost2, 3:Event2, 4:Cost3) + */ +class TraceSubMapping +{ +public: + TraceSubMapping(TraceCostMapping*); + + bool append(QString, bool create=true); + bool append(int); + void clear(); + + /** + * Get number of used indexes + */ + int count() { return _count; } + + /** + * Is this submapping the identity( i.e. realIndex(i)=i ) ? + * This often allows for optimizations. + */ + bool isIdentity() { return _isIdentity; } + int realIndex(int i) + { return (i<0 || i>=_count) ? TraceCost::InvalidIndex : _realIndex[i]; } + + /* + * Allows an iteration over the sorted list of all real indexes not used in + * this submapping, up to topIndex (use TraceCost::MaxRealIndex for all). + * Usage: for(i = firstUnused(); i < topIndex; i = nextUnused(i)) { LOOP } + */ + int firstUnused() { return _firstUnused; } + int nextUnused(int i) { + if (i<0 || i>=TraceCost::MaxRealIndex) return TraceCost::InvalidIndex; + return _nextUnused[i]; } + +private: + TraceCostMapping* _mapping; + int _count, _firstUnused; + bool _isIdentity; + int _realIndex[MaxRealIndexValue]; + int _nextUnused[MaxRealIndexValue]; +}; + + +/** + * Cost of a (conditional) jump. + */ +class TraceJumpCost: public TraceItem +{ + public: + TraceJumpCost(); + virtual ~TraceJumpCost(); + + // reimplementations for cost addition + virtual QString costString(TraceCostMapping* m); + virtual void clear(); + + void addCost(TraceJumpCost*); + + // additional cost metrics + SubCost followedCount(); + SubCost executedCount(); + void addFollowedCount(SubCost c) { _followedCount += c; } + void addExecutedCount(SubCost c) { _executedCount += c; } + + protected: + SubCost _executedCount, _followedCount; +}; + + + +/** + * Cost item with additional call count metric. + */ +class TraceCallCost: public TraceCost +{ + public: + TraceCallCost(); + virtual ~TraceCallCost(); + + // reimplementations for cost addition + virtual QString costString(TraceCostMapping* m); + virtual void clear(); + + // additional cost metric + SubCost callCount(); + QString prettyCallCount(); + void addCallCount(SubCost c); + + protected: + SubCost _callCount; +}; + + +/** + * Cost item with additional inclusive metric + */ +class TraceInclusiveCost: public TraceCost +{ + public: + TraceInclusiveCost(); + virtual ~TraceInclusiveCost(); + + // reimplementations for cost addition + virtual QString costString(TraceCostMapping* m); + virtual void clear(); + + // additional cost metric + TraceCost* inclusive(); + void addInclusive(TraceCost*); + + protected: + TraceCost _inclusive; +}; + + +/** + * Cost Item + * dependend on a list of cost items. + */ +class TraceListCost: public TraceCost +{ + public: + TraceListCost(); + virtual ~TraceListCost(); + + // reimplementation for dependency list + virtual void update(); + + TraceCostList& deps() { return _deps; } + void addDep(TraceCost*); + TraceCost* findDepFromPart(TracePart*); + + protected: + // overwrite in subclass to change update behaviour + virtual bool onlyActiveParts() { return false; } + + TraceCostList _deps; + + private: + // very temporary: cached + TraceCost* _lastDep; +}; + + +/** + * Jump Cost Item + * dependend on a list of Jump cost items. + */ +class TraceJumpListCost: public TraceJumpCost +{ + public: + TraceJumpListCost(); + virtual ~TraceJumpListCost(); + + // reimplementation for dependency list + virtual void update(); + + TraceJumpCostList deps() { return _deps; } + void addDep(TraceJumpCost*); + TraceJumpCost* findDepFromPart(TracePart*); + + protected: + // overwrite in subclass to change update behaviour + virtual bool onlyActiveParts() { return false; } + + TraceJumpCostList _deps; + + private: + // very temporary: cached + TraceJumpCost* _lastDep; +}; + + + + +/** + * Call Cost Item + * dependend on a list of Call cost items. + */ +class TraceCallListCost: public TraceCallCost +{ + public: + TraceCallListCost(); + virtual ~TraceCallListCost(); + + // reimplementation for dependency list + virtual void update(); + + TraceCallCostList deps() { return _deps; } + void addDep(TraceCallCost*); + TraceCallCost* findDepFromPart(TracePart*); + + protected: + // overwrite in subclass to change update behaviour + virtual bool onlyActiveParts() { return false; } + + TraceCallCostList _deps; + + private: + // very temporary: cached + TraceCallCost* _lastDep; +}; + + +/** + * Inclusive Cost Item dependend on a list of inclusive cost items. + */ +class TraceInclusiveListCost: public TraceInclusiveCost +{ + public: + TraceInclusiveListCost(); + virtual ~TraceInclusiveListCost(); + + // reimplementation for dependency + virtual void update(); + + TraceInclusiveCostList deps() { return _deps; } + void addDep(TraceInclusiveCost*); + TraceInclusiveCost* findDepFromPart(TracePart*); + + protected: + // overwrite in subclass to change update behaviour + virtual bool onlyActiveParts() { return false; } + + TraceInclusiveCostList _deps; + + private: + // very temporary: cached + TraceInclusiveCost* _lastDep; +}; + + + + + +/*----------------------------------------------------------------- + * Classes for cost items of one trace file, i.e. a "trace part" + *----------------------------------------------------------------- + */ + +/** + * Cost of jump at a instruction code address from a trace file. + */ +class TracePartInstrJump: public TraceJumpCost +{ + public: + TracePartInstrJump(TraceInstrJump*, TracePartInstrJump*); + virtual ~TracePartInstrJump(); + + virtual CostType type() const { return PartInstrJump; } + // fix cost item + virtual void update() {} + TraceInstrJump* instrJump() const { return (TraceInstrJump*) _dep; } + TracePartInstrJump* next() const { return _next; } + + private: + // chaining all parts for InstrJump + TracePartInstrJump* _next; +}; + + +/** + * Cost of a call at a instruction code address from a trace file. + * Cost is always up to date, no lazy update needed. + */ +class TracePartInstrCall: public TraceCallCost +{ +public: + TracePartInstrCall(TraceInstrCall*); + virtual ~TracePartInstrCall(); + + virtual CostType type() const { return PartInstrCall; } + // fix cost item + virtual void update() {} + TraceInstrCall* instrCall() const { return (TraceInstrCall*) _dep; } +}; + + +/** + * Cost of a code instruction address from a trace file. + * Cost is always up to date, no lazy update needed. + */ +class TracePartInstr: public TraceCost +{ +public: + TracePartInstr(TraceInstr*); + virtual ~TracePartInstr(); + + virtual CostType type() const { return PartInstr; } + // fix cost item + virtual void update() {} + + TraceInstr* instr() const { return (TraceInstr*)_dep; } +}; + + +/** + * Cost of jump at a source line from a trace file. + */ +class TracePartLineJump: public TraceJumpCost +{ + public: + TracePartLineJump(TraceLineJump*); + virtual ~TracePartLineJump(); + + virtual CostType type() const { return PartLineJump; } + // fix cost item + virtual void update() {} + TraceLineJump* lineJump() const { return (TraceLineJump*) _dep; } +}; + + +/** + * Cost of a call at a line from a trace file. + * Cost is always up to date, no lazy update needed. + */ +class TracePartLineCall: public TraceCallCost +{ +public: + TracePartLineCall(TraceLineCall*); + virtual ~TracePartLineCall(); + + virtual CostType type() const { return PartLineCall; } + // fix cost item + virtual void update() {} + TraceLineCall* lineCall() const { return (TraceLineCall*) _dep; } +}; + + + +/** + * Cost of a line from a trace file. + * Cost is always up to date, no lazy update needed. + */ +class TracePartLine: public TraceCost +{ +public: + TracePartLine(TraceLine*); + virtual ~TracePartLine(); + + virtual CostType type() const { return PartLine; } + // fix cost item + virtual void update() {} + + TraceLine* line() const { return (TraceLine*)_dep; } +}; + + +/** + * Cost of a source region. + */ +class TracePartLineRegion: public TraceInclusiveCost +{ +public: + TracePartLineRegion(TraceLineRegion*); + virtual ~TracePartLineRegion(); + + virtual CostType type() const { return PartLineRegion; } + virtual void update(); + + TraceLineRegion* region() const { return (TraceLineRegion*)_dep; } +}; + + +/** + * Cost of a call at a function to another function, + * from a single trace file. + */ +class TracePartCall: public TraceCallListCost +{ +public: + TracePartCall(TraceCall* call); + virtual ~TracePartCall(); + + virtual CostType type() const { return PartCall; } + // calls a function itself? + bool isRecursion(); + + // reimplementation for dependency list + virtual void update(); + + TraceCall* call() const { return (TraceCall*)_dep; } + + FixCallCost* setFirstFixCallCost(FixCallCost* fc) + { FixCallCost* t = _firstFixCallCost; _firstFixCallCost = fc; return t; } + FixCallCost* firstFixCallCost() const { return _firstFixCallCost; } + +private: + FixCallCost* _firstFixCallCost; +}; + + +/** + * Cost of a function, + * from a single trace file. + */ +class TracePartFunction: public TraceInclusiveCost +{ +public: + TracePartFunction(TraceFunction*, + TracePartObject*, TracePartFile*); + virtual ~TracePartFunction(); + + virtual CostType type() const { return PartFunction; } + virtual void update(); + virtual QString costString(TraceCostMapping* m); + + void addPartInstr(TracePartInstr*); + void addPartLine(TracePartLine*); + void addPartCaller(TracePartCall*); + void addPartCalling(TracePartCall*); + + TraceFunction* function() { return (TraceFunction*) _dep; } + TracePartObject* partObject() { return _partObject; } + TracePartClass* partClass() { return _partClass; } + TracePartFile* partFile() { return _partFile; } + const TracePartCallList& partCallers() { return _partCallers; } + const TracePartCallList& partCallings() { return _partCallings; } + void setPartObject(TracePartObject* o) { _partObject = o; } + void setPartClass(TracePartClass* c) { _partClass = c; } + void setPartFile(TracePartFile* f) { _partFile = f; } + + /* for linked list of FixXXX objects */ + FixCost* setFirstFixCost(FixCost* fc) + { FixCost* t = _firstFixCost; _firstFixCost = fc; return t; } + FixCost* firstFixCost() const { return _firstFixCost; } + FixJump* setFirstFixJump(FixJump* fj) + { FixJump* t = _firstFixJump; _firstFixJump = fj; return t; } + FixJump* firstFixJump() const { return _firstFixJump; } + + // additional cost metrics + SubCost calledCount(); + SubCost callingCount(); + QString prettyCalledCount(); + QString prettyCallingCount(); + int calledContexts(); + int callingContexts(); + +private: + TracePartObject* _partObject; + TracePartClass* _partClass; + TracePartFile* _partFile; + + TracePartCallList _partCallings; + TracePartCallList _partCallers; + TracePartInstrList _partInstr; + TracePartLineList _partLines; + + // cached + SubCost _calledCount, _callingCount; + int _calledContexts, _callingContexts; + + FixCost* _firstFixCost; + FixJump* _firstFixJump; +}; + + +/** + * Cost of a class, + * from a single trace file. + */ +class TracePartClass: public TraceInclusiveListCost +{ +public: + TracePartClass(TraceClass*); + virtual ~TracePartClass(); + + virtual CostType type() const { return PartClass; } + QString prettyName() const; + + TraceClass* cls() { return (TraceClass*)_dep; } + void addPartFunction(TracePartFunction* f) { addDep(f); } +}; + + +/** + * Cost of a source file, + * from a single trace file. + */ +class TracePartFile: public TraceInclusiveListCost +{ +public: + TracePartFile(TraceFile*); + virtual ~TracePartFile(); + + virtual CostType type() const { return PartFile; } + TraceFile* file() { return (TraceFile*)_dep; } + void addPartFunction(TracePartFunction* f) { addDep(f); } +}; + + +/** + * Cost of a object, + * from a single trace file. + */ +class TracePartObject: public TraceInclusiveListCost +{ +public: + TracePartObject(TraceObject*); + virtual ~TracePartObject(); + + virtual CostType type() const { return PartObject; } + TraceObject* object() const { return (TraceObject*)_dep; } + void addPartFunction(TracePartFunction* f) { addDep(f); } +}; + + + +/** + * A Trace Part: All data read from a trace file, containing all costs + * that happened in a specified time interval of the executed command. + */ +class TracePart: public TraceListCost +{ +public: + TracePart(TraceData*, QFile* file); + virtual ~TracePart(); + + virtual CostType type() const { return Part; } + virtual TracePart* part() { return this; } + virtual const TracePart* part() const { return this; } + + QString shortName() const; + QString prettyName() const; + QFile* file() const { return _file; } + QString name() const { return _name; } + QString description() const { return _descr; } + QString trigger() const { return _trigger; } + QString timeframe() const { return _timeframe; } + QString version() const { return _version; } + int partNumber() { return _number; } + int threadID() { return _tid; } + int processID() { return _pid; } + void setDescription(const QString& d) { _descr = d; } + void setTrigger(const QString& t) { _trigger = t; } + void setTimeframe(const QString& t) { _timeframe = t; } + void setVersion(const QString& v) { _version = v; } + void setPartNumber(int n); + void setThreadID(int t); + void setProcessID(int p); + TraceCost* totals() { return &_totals; } + /* we get owner of the submapping */ + void setFixSubMapping(TraceSubMapping* sm) { _fixSubMapping = sm; } + TraceSubMapping* fixSubMapping() { return _fixSubMapping; } + + // returns true if something changed + bool activate(bool); + bool isActive() { return _active; } + +private: + QFile* _file; + QString _name; + QString _descr; + QString _trigger; + QString _timeframe; + QString _version; + + int _number, _tid, _pid; + + bool _active; + + // the totals line + TraceCost _totals; + + // submapping for all fix costs of this part + TraceSubMapping* _fixSubMapping; +}; + + +class TracePartList: public QPtrList<TracePart> +{ + public: + QString names() const; + protected: + int compareItems ( Item item1, Item item2 ); +}; + + +/*----------------------------------------------------------------- + * Classes for cost items summed up from multiple trace parts + *----------------------------------------------------------------- + */ + + +/** + * A jump from an instruction to another inside of a function + */ +class TraceInstrJump: public TraceJumpCost +{ +public: + TraceInstrJump(TraceInstr* instrFrom, TraceInstr* instrTo, + bool isCondJump); + virtual ~TraceInstrJump(); + + virtual CostType type() const { return InstrJump; } + virtual QString name() const; + + virtual void update(); + + TraceInstr* instrFrom() const { return _instrFrom; } + TraceInstr* instrTo() const { return _instrTo; } + bool isCondJump() const { return _isCondJump; } + + // part factory + TracePartInstrJump* partInstrJump(TracePart*); + + private: + TraceInstr *_instrFrom, *_instrTo; + bool _isCondJump; + // list of parts for this InstrJump + TracePartInstrJump* _first; +}; + +class TraceInstrJumpList: public QPtrList<TraceInstrJump> +{ + public: + TraceInstrJumpList() { _sortLow = true; } + void setSortLow(bool s) { _sortLow = s; } + + protected: + int compareItems ( Item item1, Item item2 ); + + private: + bool _sortLow; +}; + + +/** + * A jump from one line to another inside of a function. + */ +class TraceLineJump: public TraceJumpListCost +{ + public: + TraceLineJump(TraceLine* lineFrom, TraceLine* lineTo, + bool isCondJump); + virtual ~TraceLineJump(); + + virtual CostType type() const { return LineJump; } + virtual QString name() const; + + TraceLine* lineFrom() const { return _lineFrom; } + TraceLine* lineTo() const { return _lineTo; } + bool isCondJump() { return _isCondJump; } + + // part factory + TracePartLineJump* partLineJump(TracePart*); + + protected: + bool onlyActiveParts() { return true; } + + private: + TraceLine *_lineFrom, *_lineTo; + bool _isCondJump; +}; + + +class TraceLineJumpList: public QPtrList<TraceLineJump> +{ + public: + TraceLineJumpList() { _sortLow = true; } + void setSortLow(bool s) { _sortLow = s; } + + protected: + int compareItems ( Item item1, Item item2 ); + + private: + bool _sortLow; +}; + + +/** + * A call from an instruction of one function to another function + */ +class TraceInstrCall: public TraceCallListCost +{ + public: + TraceInstrCall(TraceCall* call, TraceInstr* instr); + virtual ~TraceInstrCall(); + + virtual CostType type() const { return InstrCall; } + virtual QString name() const; + + TraceInstr* instr() const { return _instr; } + TraceCall* call() const { return _call; } + + // part factory + TracePartInstrCall* partInstrCall(TracePart*, TracePartCall*); + + protected: + bool onlyActiveParts() { return true; } + + private: + TraceInstr* _instr; + TraceCall* _call; +}; + + +/** + * A call from a line of one function to another function. + */ +class TraceLineCall: public TraceCallListCost +{ + public: + TraceLineCall(TraceCall* call, TraceLine* line); + virtual ~TraceLineCall(); + + virtual CostType type() const { return LineCall; } + virtual QString name() const; + + TraceLine* line() const { return _line; } + TraceCall* call() const { return _call; } + + // part factory + TracePartLineCall* partLineCall(TracePart*, TracePartCall*); + + protected: + bool onlyActiveParts() { return true; } + + private: + TraceLine* _line; + TraceCall* _call; +}; + + +/** + * A call from one to another function. + * Consists of a list a TraceLineCalls + */ +class TraceCall: public TraceCallListCost +{ + public: + TraceCall(TraceFunction* caller, TraceFunction* called); + virtual ~TraceCall(); + + virtual CostType type() const { return Call; } + virtual QString name() const; + + // calls a function itself? + bool isRecursion() { return _caller == _called; } + + // return cycle number >0 if call is inside of a cycle + int inCycle(); + // we need some special handling for cycle calls + void update(); + + void invalidateDynamicCost(); + + // factories + TracePartCall* partCall(TracePart*, + TracePartFunction*, TracePartFunction*); + TraceLineCall* lineCall(TraceLine*); + TraceInstrCall* instrCall(TraceInstr*); + + TraceFunction* caller(bool skipCycle=false) const; + TraceFunction* called(bool skipCycle=false) const; + QString callerName(bool skipCycle=false) const; + QString calledName(bool skipCycle=false) const; + const TraceLineCallList& lineCalls() const { return _lineCalls; } + const TraceInstrCallList& instrCalls() const { return _instrCalls; } + + FixCallCost* setFirstFixCost(FixCallCost* fc) + { FixCallCost* t = _firstFixCost; _firstFixCost = fc; return t; } + + protected: + bool onlyActiveParts() { return true; } + + private: + TraceInstrCallList _instrCalls; + TraceLineCallList _lineCalls; + TraceFunction* _caller; + TraceFunction* _called; + + FixCallCost* _firstFixCost; +}; + + +/** + * A code instruction address of the program. + * Consists of a list a TracePartInstr from different trace files + * and a list of TraceInstrCalls if there are calls from this address. + */ +class TraceInstr: public TraceListCost +{ + public: + TraceInstr(); + virtual ~TraceInstr(); + + virtual CostType type() const { return Instr; } + virtual QString name() const; + QString prettyName() const; + + bool isValid() { return _addr != Addr(0); } + + // factories + TracePartInstr* partInstr(TracePart* part, + TracePartFunction* partFunction); + TraceInstrJump* instrJump(TraceInstr* to, bool isCondJump); + + void addInstrCall(TraceInstrCall*); + + Addr addr() const { return _addr; } + TraceFunction* function() const { return _function; } + TraceLine* line() const { return _line; } + const TraceInstrJumpList& instrJumps() const { return _instrJumps; } + const TraceInstrCallList& instrCalls() const { return _instrCalls; } + bool hasCost(TraceCostType*); + + // only to be called after default constructor + void setAddr(const Addr addr) { _addr = addr; } + void setFunction(TraceFunction* f) { _function = f; } + void setLine(TraceLine* l) { _line = l; } + + protected: + bool onlyActiveParts() { return true; } + + private: + Addr _addr; + TraceFunction* _function; + TraceLine* _line; + + TraceInstrJumpList _instrJumps; + TraceInstrCallList _instrCalls; +}; + + +/** + * A source line of the program. + * Consists of a list a TracePartLines from different trace files + * and a list of TraceLineCalls if there are calls from this line. + */ +class TraceLine: public TraceListCost +{ +public: + TraceLine(); + virtual ~TraceLine(); + + virtual CostType type() const { return Line; } + virtual QString name() const; + QString prettyName() const; + + // factories + TracePartLine* partLine(TracePart* part, + TracePartFunction* partFunction); + TraceLineJump* lineJump(TraceLine* to, bool isCondJump); + + void addLineCall(TraceLineCall*); + + + bool isValid() { return _sourceFile != 0; } + bool hasCost(TraceCostType*); + TraceFunctionSource* functionSource() const { return _sourceFile; } + uint lineno() const { return _lineno; } + const TraceLineCallList& lineCalls() const { return _lineCalls; } + const TraceLineJumpList& lineJumps() const { return _lineJumps; } + + // only to be called after default constructor + void setSourceFile(TraceFunctionSource* sf) { _sourceFile = sf; } + void setLineno(uint lineno) { _lineno = lineno; } + + protected: + bool onlyActiveParts() { return true; } + + private: + TraceFunctionSource* _sourceFile; + uint _lineno; + + TraceLineJumpList _lineJumps; + TraceLineCallList _lineCalls; +}; + + +/* + * Base class for all costs which + * represent "interesting" items or group of items + * with settable name and inclusive cost + */ +class TraceCostItem: public TraceInclusiveListCost +{ + public: + TraceCostItem(); + virtual ~TraceCostItem(); + + virtual QString name() const { return _name; } + virtual void setName(const QString& name) { _name = name; } + + protected: + bool onlyActiveParts() { return true; } + + protected: + QString _name; +}; + + +/** + * Cost of a source region. + */ +class TraceLineRegion: public TraceInclusiveListCost +{ +public: + TraceLineRegion(uint from, uint to, QString name); + virtual ~TraceLineRegion(); + + virtual CostType type() const { return LineRegion; } + virtual void update(); + + uint from() const { return _from; } + uint to() const { return _to; } + QString name() const { return _name; } + + // factories + TracePartLine* partLineRegion(TracePart* part, + TracePartFunction* partFunction); + private: + uint _from, _to; + QString _name; +}; + + +/** + * A container helper class for TraceFunction for source lines + * where a function is implemented in. + * With inlining, lines of the same function can come from + * different source files. + * An instance of this class holds all lines of one source file + * for a function in a map + */ +class TraceFunctionSource: public TraceCost +{ +public: + TraceFunctionSource(TraceFunction*, TraceFile*); + virtual ~TraceFunctionSource(); + + virtual CostType type() const { return FunctionSource; } + virtual QString name() const; + + // reimplementation for dependency map + virtual void update(); + + TraceFile* file() const { return _file; } + TraceFunction* function() const { return _function; } + uint firstLineno(); + uint lastLineno(); + TraceLineMap* lineMap(); + + void invalidateDynamicCost(); + + /* factories */ + TraceLine* line(uint lineno, bool createNew = true); + TraceLineRegion* region(uint from, uint to, QString name, + bool createNew = true); + + private: + TraceFile* _file; + TraceFunction* _function; + TraceLineMap* _lineMap; + TraceLine* _line0; + TraceLineRegionList* _regions; + + bool _lineMapFilled; +}; + + +/** + * For temporary assoziation of objects with TraceFunctions. + * Used in coverage analysis and TreeMap drawing. + */ +class TraceAssoziation +{ + public: + /** + * Creates an invalid assoziation. + */ + TraceAssoziation(); + virtual ~TraceAssoziation(); + + // for runtime detection + virtual int rtti() { return 0; } + + /** + * Could we set the function assoziation to ourself? + * This only can return false if this is a unique assoziation. + */ + bool isAssoziated(); + + /** + * reset function to assoziate this object to. + * returns true if assoziation could be established + */ + bool setFunction(TraceFunction*); + TraceFunction* function() { return _function; } + + void invalidate() { _valid = false; } + bool isValid() { return _valid; } + + /** + * Delete all assoziations in TraceFunctions of data with + * rtti runtime info. rtti = 0: delete ALL assoziations. + */ + static void clear(TraceData* data, int rtti); + + /** + * Invalidate all assoziations in TraceFunctions of data with + * rtti runtime info. rtti = 0: Invalidate ALL assoziations. + */ + static void invalidate(TraceData* data, int rtti); + + protected: + TraceFunction* _function; + bool _valid; +}; + +typedef QPtrList<TraceAssoziation> TraceAssoziationList; +typedef QMap<TraceFunction*, TraceCall*> TraceCallMap; + +/** + * A traced function + * + * References to functions are stored in + * (1) a function map in TraceData (by value) + * (2) a TraceClass + */ +class TraceFunction: public TraceCostItem +{ + public: + TraceFunction(); + TraceFunction(TraceData* data, const QString& name, + TraceClass* cls, TraceFile* file, TraceObject* object); + virtual ~TraceFunction(); + + virtual CostType type() const { return Function; } + virtual void update(); + + // this invalidate all subcosts of function depending on + // active status of parts + void invalidateDynamicCost(); + + void addCaller(TraceCall*); + + // factories + TraceCall* calling(TraceFunction* called); + TraceLine* line(TraceFile*, uint lineno, bool createNew = true); + TraceInstr* instr(Addr addr, bool createNew = true); + TracePartFunction* partFunction(TracePart*, + TracePartFile*, TracePartObject*); + + /** + * Returns empty string if location is fully unknown. + * Use prettyLocation for single user-visible string. + * A function can have a lot of code from different sources (inlined); + * maxItems limits this list. Default is full list + */ + QString location(int maxFiles = 0) const; + + QString prettyName() const; + QString prettyLocation(int maxFiles = 0) const; + QString prettyNameWithLocation(int maxFiles = 1) const; + void addPrettyLocation(QString&, int maxFiles = 1) const; + // type + name + location + QString info() const; + + TraceClass* cls() const { return _cls; } + TraceFile* file() const { return _file; } + TraceObject* object() const { return _object; } + // get the source file with lines from function declaration (not inlined) + TraceFunctionSource* sourceFile(TraceFile* file = 0, + bool createNew = false); + const TraceFunctionSourceList& sourceFiles() const + { return _sourceFiles; } + TraceCallList callers(bool skipCycle=false) const; + const TraceCallList& callings(bool skipCycle=false) const; + + Addr firstAddress() const; + Addr lastAddress() const; + TraceInstrMap* instrMap(); + + // cost metrics + SubCost calledCount(); + SubCost callingCount(); + QString prettyCalledCount(); + QString prettyCallingCount(); + int calledContexts(); + int callingContexts(); + + // only to be called after default constructor + void setFile(TraceFile* file) { _file = file; } + void setObject(TraceObject* object) { _object = object; } + void setClass(TraceClass* cls) { _cls = cls; } + void setMapIterator(TraceFunctionMap::Iterator it) { _myMapIterator = it; } + + // see TraceFunctionAssoziation + void addAssoziation(TraceAssoziation* a); + void removeAssoziation(TraceAssoziation* a); + void removeAssoziation(int rtti, bool reallyDelete = true); + void invalidateAssoziation(int rtti); + TraceAssoziation* assoziation(int rtti); + + // cycles + void setCycle(TraceFunctionCycle* c) { _cycle = c; } + TraceFunctionCycle* cycle() { return _cycle; } + bool isCycle(); + bool isCycleMember(); + void cycleReset(); + void cycleDFS(int d, int& pNo, TraceFunction** pTop); + + protected: + TraceCallList _callers; // list of calls we are called from + TraceCallList _callings; // list of calls we are calling (we are owner) + TraceCallMap _callingMap; // contains the same as _callings in a map + TraceFunctionCycle* _cycle; + + private: + bool isUniquePrefix(QString) const; + TraceFunctionMap::Iterator _myMapIterator; + + TraceClass* _cls; + TraceObject* _object; + TraceFile* _file; + + TraceFunctionSourceList _sourceFiles; // we are owner + TraceInstrMap* _instrMap; // we are owner + bool _instrMapFilled; + + // see TraceAssoziation + TraceAssoziationList _assoziations; + + // for cycle detection + int _cycleLow; + TraceFunction* _cycleStackDown; + + // cached + SubCost _calledCount, _callingCount; + int _calledContexts, _callingContexts; +}; + +typedef QMap<TraceFunction*,int> TraceFunctionSet; + +/** + * A cycle of recursive calling functions. + * + * This is itself shown as a function + */ +class TraceFunctionCycle: public TraceFunction +{ + public: + TraceFunctionCycle(TraceFunction*, int n); + + virtual CostType type() const { return FunctionCycle; } + + // this removes all members from this cycle + void init(); + void add(TraceFunction*); + // this sets up the cycle once members are added + void setup(); + + TraceFunction* base() const { return _base; } + int cycleNo() const { return _cycleNo; } + const TraceFunctionList& members() const { return _members; } + + private: + TraceFunction* _base; + int _cycleNo; + + TraceFunctionList _members; + TraceFunctionSet _memberSet; +}; + + +/** + * A C++ Class / Namespace + * + * If a function symbol has a prefix ending in "::", + * the prefix is supposed to be a class/namespace specifier. + * Without such a prefix, we put a symbol in the "(global)" namespace. + */ +class TraceClass: public TraceCostItem +{ + public: + TraceClass(); + virtual ~TraceClass(); + + virtual CostType type() const { return Class; } + virtual QString prettyName() const; + + void addFunction(TraceFunction*); + const TraceFunctionList& functions() const { return _functions; } + + // part factory + TracePartClass* partClass(TracePart*); + + private: + TraceFunctionList _functions; +}; + + + +/** + * A source file containing function definitions + */ +class TraceFile: public TraceCostItem +{ + public: + TraceFile(); + virtual ~TraceFile(); + + virtual CostType type() const { return File; } + void setDirectory(const QString& dir); + void resetDirectory() { _dir = QString::null; } + QString directory(); + + void addFunction(TraceFunction*); + void addSourceFile(TraceFunctionSource*); + + // without path + QString shortName() const; + QString prettyName() const; + QString prettyLongName() const; + const TraceFunctionList& functions() const { return _functions; } + const TraceFunctionSourceList& sourceFiles() const + { return _sourceFiles; } + + // part factory + TracePartFile* partFile(TracePart*); + + private: + TraceFunctionList _functions; + TraceFunctionSourceList _sourceFiles; + QString _dir; +}; + + +/** + * A object containing a text segment (shared lib/executable) + * with defined functions + */ +class TraceObject: public TraceCostItem +{ + public: + TraceObject(); + virtual ~TraceObject(); + + virtual CostType type() const { return Object; } + + void addFunction(TraceFunction*); + + virtual void setName(const QString& name); + QString shortName() const { return _shortName; } + QString prettyName() const; + const TraceFunctionList& functions() const { return _functions; } + + // part factory + TracePartObject* partObject(TracePart*); + + private: + TraceFunctionList _functions; + QString _shortName; +}; + + + +/** + * This class holds profiling data of multiple tracefiles + * generated with cachegrind on one command. + * + */ +class TraceData: public TraceCost +{ + public: + TraceData(TopLevel* top = 0); + TraceData(const QString& base); + virtual ~TraceData(); + + virtual CostType type() const { return Data; } + virtual TraceData* data() { return this; } + virtual const TraceData* data() const { return this; } + + /** + * Loads a trace file. + * + * This adjusts the TraceCostMapping according to given cost types + */ + void load(const QString&); + + /** returns true if something changed. These do NOT + * invalidate the dynamic costs on a activation change, + * i.e. all cost items dependend on active parts. + * This has to be done by the caller when true is returned by + * calling invalidateDynamicCost(). + */ + bool activateParts(const TracePartList&); + bool activateParts(TracePartList, bool active); + bool activatePart(TracePart*, bool active); + bool activateAll(bool active=true); + + TracePartList parts() const { return _parts; } + TracePart* part(QString& name); + + // with path + QString traceName() const { return _traceName; } + + // without path + QString shortTraceName() const; + QString activePartRange(); + + TraceCostMapping* mapping() { return &_mapping; } + + // memory pools + FixPool* fixPool(); + DynPool* dynPool(); + + // factories for object/file/class/function/line instances + TraceObject* object(const QString& name); + TraceFile* file(const QString& name); + TraceClass* cls(const QString& fnName, QString& shortName); + // function creation involves class creation if needed + TraceFunction* function(const QString& name, TraceFile*, TraceObject*); + // factory for function cycles + TraceFunctionCycle* functionCycle(TraceFunction*); + + /** + * Search for item with given name and highest subcost of given cost type. + * + * For some items, they will only be found if the parent cost is given: + * Instr, Line, Call => need parent of type Function + * For Function, a parent of type Obj/File/Class can be given, but + * isn't needed. + */ + TraceCost* search(TraceItem::CostType, QString, + TraceCostType* ct = 0, TraceCost* parent = 0); + + // for pretty function names without signature if unique... + TraceFunctionMap::Iterator functionIterator(TraceFunction*); + TraceFunctionMap::ConstIterator functionBeginIterator() const; + TraceFunctionMap::ConstIterator functionEndIterator() const; + + TraceObjectMap& objectMap() { return _objectMap; } + TraceFileMap& fileMap() { return _fileMap; } + TraceClassMap& classMap() { return _classMap; } + TraceFunctionMap& functionMap() { return _functionMap; } + + const TraceFunctionCycleList& functionCycles() { return _functionCycles; } + + TraceCost* callMax() { return &_callMax; } + + void setCommand(const QString& command) { _command = command; } + QString command() const { return _command; } + TraceCost* totals() { return &_totals; } + void setMaxThreadID(int tid) { _maxThreadID = tid; } + int maxThreadID() const { return _maxThreadID; } + void setMaxPartNumber(int n) { _maxPartNumber = n; } + int maxPartNumber() const { return _maxPartNumber; } + + // reset all manually set directories for source files + void resetSourceDirs(); + + virtual void update(); + + // invalidates all cost items dependant on active state of parts + void invalidateDynamicCost(); + + // cycle detection + void updateFunctionCycles(); + void updateObjectCycles(); + void updateClassCycles(); + void updateFileCycles(); + bool inFunctionCycleUpdate() { return _inFunctionCycleUpdate; } + + private: + void init(); + // add trace part: events from one trace file + TracePart* addPart(const QString& dir, const QString& file); + + // for progress bar callbacks + TopLevel* _topLevel; + + TracePartList _parts; + + // The mapping for all costs + TraceCostMapping _mapping; + + FixPool* _fixPool; + DynPool* _dynPool; + + // always the trace totals (not dependent on active parts) + TraceCost _totals; + int _maxThreadID; + int _maxPartNumber; + + TraceObjectMap _objectMap; + TraceClassMap _classMap; + TraceFileMap _fileMap; + TraceFunctionMap _functionMap; + QString _command; + QString _traceName; + + // Max of all costs of calls: This allows to see if the incl. cost can + // be hidden for a cost type, as it's always the same as self cost + TraceCost _callMax; + + // cycles + TraceFunctionCycleList _functionCycles; + int _functionCycleCount; + bool _inFunctionCycleUpdate; +}; + + + +#endif diff --git a/kcachegrind/kcachegrind/traceitemview.cpp b/kcachegrind/kcachegrind/traceitemview.cpp new file mode 100644 index 00000000..8ee2faac --- /dev/null +++ b/kcachegrind/kcachegrind/traceitemview.cpp @@ -0,0 +1,443 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Trace Item View + */ + +#include <qwidget.h> +#include <kconfig.h> +#include <klocale.h> +#include <kdebug.h> + +#include "traceitemview.h" +#include "toplevel.h" + +#define TRACE_UPDATES 0 + +TraceItemView::TraceItemView(TraceItemView* parentView, TopLevel* top) +{ + _parentView = parentView; + _topLevel = top ? top : parentView->topLevel(); + + _data = _newData = 0; + // _partList and _newPartList is empty + _activeItem = _newActiveItem = 0; + _selectedItem = _newSelectedItem = 0; + _costType = _newCostType = 0; + _costType2 = _newCostType2 = 0; + _groupType = _newGroupType = TraceItem::NoCostType; + + _status = nothingChanged; + _inUpdate = false; + _pos = Hidden; +} + +QString TraceItemView::whatsThis() const +{ + return i18n("No description available"); +} + +void TraceItemView::select(TraceItem* i) +{ + _newSelectedItem = i; +} + +KConfigGroup* TraceItemView::configGroup(KConfig* c, + QString group, QString post) +{ + QStringList gList = c->groupList(); + if (gList.contains((group+post).ascii()) ) group += post; + return new KConfigGroup(c, group); +} + +void TraceItemView::writeConfigEntry(KConfigBase* c, const char* pKey, + QString value, const char* def, bool bNLS) +{ + if (!c) return; + if ((value.isEmpty() && ((def == 0) || (*def == 0))) || + (value == QString(def))) + c->deleteEntry(pKey); + else + c->writeEntry(pKey, value, true, false, bNLS); +} + +void TraceItemView::writeConfigEntry(KConfigBase* c, const char* pKey, + int value, int def) +{ + if (!c) return; + if (value == def) + c->deleteEntry(pKey); + else + c->writeEntry(pKey, value); +} + +void TraceItemView::writeConfigEntry(KConfigBase* c, const char* pKey, + double value, double def) +{ + if (!c) return; + if (value == def) + c->deleteEntry(pKey); + else + c->writeEntry(pKey, value); +} + +void TraceItemView::writeConfigEntry(KConfigBase* c, const char* pKey, + bool value, bool def) +{ + if (!c) return; + if (value == def) + c->deleteEntry(pKey); + else + c->writeEntry(pKey, value); +} + +void TraceItemView::readViewConfig(KConfig*, QString, QString, bool) +{} + +#if 1 +void TraceItemView::saveViewConfig(KConfig*, QString, QString, bool) +{} +#else +void TraceItemView::saveViewConfig(KConfig* c, + QString prefix, QString postfix, bool) +{ + // write a dummy config entry to see missing virtual functions + KConfigGroup g(c, (prefix+postfix).ascii()); + g.writeEntry("SaveNotImplemented", true); +} +#endif + +bool TraceItemView::activate(TraceItem* i) +{ + i = canShow(i); + _newActiveItem = i; + + return (i != 0); +} + +TraceFunction* TraceItemView::activeFunction() +{ + TraceItem::CostType t = _activeItem->type(); + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + return (TraceFunction*) _activeItem; + default: + break; + } + return 0; +} + +bool TraceItemView::set(int changeType, TraceData* d, + TraceCostType* t1, TraceCostType* t2, + TraceItem::CostType g, const TracePartList& l, + TraceItem* a, TraceItem* s) +{ + _status |= changeType; + _newData = d; + _newGroupType = g; + _newCostType = t1; + _newCostType2 = t2; + _newPartList = l; + _newSelectedItem = s; + _newActiveItem = canShow(a); + if (!_newActiveItem) { + _newSelectedItem = 0; + return false; + } + + return true; +} + + +bool TraceItemView::isViewVisible() +{ + QWidget* w = widget(); + if (w) + return w->isVisible(); + return false; +} + +void TraceItemView::setData(TraceData* d) +{ + _newData = d; + + // invalidate all pointers to old data + _activeItem = _newActiveItem = 0; + _selectedItem = _newSelectedItem = 0; + _costType = _newCostType = 0; + _costType2 = _newCostType2 = 0; + _groupType = _newGroupType = TraceItem::NoCostType; + _partList.clear(); + _newPartList.clear(); + + // updateView will change this to dataChanged + _status = nothingChanged; +} + +void TraceItemView::updateView(bool force) +{ + if (!force && !isViewVisible()) return; + + if (_newData != _data) { + _status |= dataChanged; + _data = _newData; + } + else { + _status &= ~dataChanged; + + // if there's no data change and data is 0, no update needed + if (!_data) return; + } + + if (!(_newPartList == _partList)) { + _status |= partsChanged; + _partList = _newPartList; + } + else + _status &= ~partsChanged; + + if (_newActiveItem != _activeItem) { + + // when setting a new active item, there's no selection + _selectedItem = 0; + + _status |= activeItemChanged; + _activeItem = _newActiveItem; + } + else + _status &= ~activeItemChanged; + + if (_newCostType != _costType) { + _status |= costTypeChanged; + _costType = _newCostType; + } + else + _status &= ~costTypeChanged; + + if (_newCostType2 != _costType2) { + _status |= costType2Changed; + _costType2 = _newCostType2; + } + else + _status &= ~costType2Changed; + + if (_newGroupType != _groupType) { + _status |= groupTypeChanged; + _groupType = _newGroupType; + } + else + _status &= ~groupTypeChanged; + + + if (_newSelectedItem != _selectedItem) { + _status |= selectedItemChanged; + _selectedItem = _newSelectedItem; + } + else + _status &= ~selectedItemChanged; + + + if (!force && (_status == nothingChanged)) return; + +#if TRACE_UPDATES + kdDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::doUpdate ( " + << ((_status & dataChanged) ? "data ":"") + << ((_status & configChanged) ? "config ":"") + << ")" << endl; + + if (_status & partsChanged) + kdDebug() << " Part List " + << _partList.names() + << endl; + + if (_status & costTypeChanged) + kdDebug() << " Cost type " + << (_costType ? _costType->name().ascii() : "?") + << endl; + + if (_status & costType2Changed) + kdDebug() << " Cost type 2 " + << (_costType2 ? _costType2->name().ascii() : "?") + << endl; + + if (_status & groupTypeChanged) + kdDebug() << " Group type " + << TraceItem::typeName(_groupType) + << endl; + + if (_status & activeItemChanged) + kdDebug() << " Active: " + << (_activeItem ? _activeItem->fullName().ascii() : "?") + << endl; + + if (_status & selectedItemChanged) + kdDebug() << " Selected: " + << (_selectedItem ? _selectedItem->fullName().ascii() : "?") + << endl; +#endif + + int st = _status; + _status = nothingChanged; + doUpdate(st); + return; + + if (_inUpdate) return; + _inUpdate = true; + doUpdate(_status); + _inUpdate = false; +} + + +void TraceItemView::selected(TraceItemView* /*sender*/, TraceItem* i) +{ +#if TRACE_UPDATES + kdDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::selected " + << (i ? i->name().ascii(): "(nil)") + << ", sender " + << sender->widget()->name() << endl; +#endif + + if (_parentView) _parentView->selected(this, i); +} + +void TraceItemView::selected(TraceItemView* /*sender*/, const TracePartList& l) +{ +#if TRACE_UPDATES + kdDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::selected " + << l.names() + << ", sender " + << sender->widget()->name() << endl; +#endif + + if (_parentView) + _parentView->selected(this, l); + else + if (_topLevel) _topLevel->activePartsChangedSlot(l); +} + +void TraceItemView::activated(TraceItemView* /*sender*/, TraceItem* i) +{ +#if TRACE_UPDATES + kdDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::activated " + << (i ? i->name().ascii(): "(nil)") + << ", sender " + << sender->widget()->name() << endl; +#endif + + if (_parentView) + _parentView->activated(this, i); + else + if (_topLevel) _topLevel->setTraceItemDelayed(i); +} + +void TraceItemView::selectedCostType(TraceItemView*, TraceCostType* t) +{ + if (_parentView) + _parentView->selectedCostType(this, t); + else + if (_topLevel) _topLevel->setCostTypeDelayed(t); +} + +void TraceItemView::selectedCostType2(TraceItemView*, TraceCostType* t) +{ + if (_parentView) + _parentView->selectedCostType2(this, t); + else + if (_topLevel) _topLevel->setCostType2Delayed(t); +} + +void TraceItemView::activated(TraceItemView*, Direction d) +{ + if (_parentView) + _parentView->activated(this, d); + else + if (_topLevel) _topLevel->setDirectionDelayed(d); +} + +void TraceItemView::doUpdate(int) +{ +} + +void TraceItemView::selected(TraceItem* i) +{ + if (_parentView) + _parentView->selected(this, i); + +} + +void TraceItemView::selected(const TracePartList& l) +{ + if (_parentView) + _parentView->selected(this, l); + else + if (_topLevel) _topLevel->activePartsChangedSlot(l); +} + +void TraceItemView::activated(TraceItem* i) +{ +#if TRACE_UPDATES + kdDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::activated " + << (i ? i->name().ascii(): "(nil)") << endl; +#endif + + if (_parentView) + _parentView->activated(this, i); + else + if (_topLevel) _topLevel->setTraceItemDelayed(i); +} + +void TraceItemView::selectedCostType(TraceCostType* t) +{ + if (_parentView) + _parentView->selectedCostType(this, t); + else + if (_topLevel) _topLevel->setCostTypeDelayed(t); +} + +void TraceItemView::selectedCostType2(TraceCostType* t) +{ + if (_parentView) + _parentView->selectedCostType2(this, t); + else + if (_topLevel) _topLevel->setCostType2Delayed(t); +} + +void TraceItemView::activated(Direction d) +{ + if (_parentView) + _parentView->activated(this, d); + else + if (_topLevel) _topLevel->setDirectionDelayed(d); +} + +void TraceItemView::addCostMenu(QPopupMenu* p, bool withCost2) +{ + if (_topLevel) _topLevel->addCostMenu(p, withCost2); +} + +void TraceItemView::addGoMenu(QPopupMenu* p) +{ + if (_topLevel) _topLevel->addGoMenu(p); +} diff --git a/kcachegrind/kcachegrind/traceitemview.h b/kcachegrind/kcachegrind/traceitemview.h new file mode 100644 index 00000000..e74a009c --- /dev/null +++ b/kcachegrind/kcachegrind/traceitemview.h @@ -0,0 +1,206 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Trace Item View + */ + +#ifndef TRACEITEMVIEW_H +#define TRACEITEMVIEW_H + +#include "tracedata.h" + +class QWidget; +class QPopupMenu; + +class KConfig; +class KConfigGroup; +class KConfigBase; + +class TopLevel; + +/** + * Abstract Base Class for KCachegrind Views + * + * This class delivers the shared functionality of all KCachegrind + * Views for one TraceItem (like Function, Object...), the "active" + * item. Additional view attributes are current primary cost type, + * an optional secondary cost type, group type, + * and possibly a selected costitem in this view. + * Note that there is a difference in changing the selected item of + * a view (this usually changes selection in other views, too), and + * activating that item. + */ +class TraceItemView +{ +public: + + /** + * Change type for update functions + * - <dataChanged> is used if e.g. cycles are recalculated + */ + enum { nothingChanged = 0, + costTypeChanged = 1, + costType2Changed = 2, + groupTypeChanged = 4, + partsChanged = 8, + activeItemChanged = 16, + selectedItemChanged = 32, + dataChanged = 64, + configChanged = 128 }; + + enum Direction { None, Back, Forward, Up }; + + // a TraceItemView can have a position in a parent container + enum Position { Hidden, Top, Right, Left, Bottom }; + + TraceItemView(TraceItemView* parentView, TopLevel* top = 0); + virtual ~TraceItemView() {} + + virtual QString whatsThis() const; + + static KConfigGroup* configGroup(KConfig*, QString prefix, QString postfix); + static void writeConfigEntry(KConfigBase*, const char* pKey, QString value, + const char* def, bool bNLS = false); + static void writeConfigEntry(KConfigBase*, const char* pKey, + int value, int def); + static void writeConfigEntry(KConfigBase*, const char* pKey, + bool value, bool def); + static void writeConfigEntry(KConfigBase*, const char* pKey, + double value, double def); + virtual void readViewConfig(KConfig*, QString prefix, QString postfix, + bool withOptions); + virtual void saveViewConfig(KConfig*, QString prefix, QString postfix, + bool withOptions); + + // Immediate remove all references to old data, and set the new. + // This resets the visualization state. + // A GUI update has to be triggered with updateView(). + // Overwrite in container views to also set new data for all members. + virtual void setData(TraceData* d); + + // change from parent, call updateView() to update lazily (only if visible) + void setCostType(TraceCostType* t) { _newCostType = t; } + void setCostType2(TraceCostType* t) { _newCostType2 = t; } + void set(TraceItem::CostType g) { _newGroupType = g; } + void set(const TracePartList& l) { _newPartList = l; } + // returns false if nothing can be shown for this trace item + bool activate(TraceItem* i); + void select(TraceItem* i); + void notifyChange(int changeType) { _status |= changeType; } + // all in one + bool set(int, TraceData*, TraceCostType*, TraceCostType*, + TraceItem::CostType, const TracePartList&, + TraceItem*, TraceItem*); + + // general update request, call if view is/gets visible + void updateView(bool force = false); + + /** + * Notification from child views. + * Default implementation notifies parent + */ + virtual void selected(TraceItemView* sender, TraceItem*); + virtual void selected(TraceItemView* sender, const TracePartList&); + virtual void activated(TraceItemView* sender, Direction); + virtual void selectedCostType(TraceItemView* sender, TraceCostType*); + virtual void selectedCostType2(TraceItemView* sender, TraceCostType*); + virtual void activated(TraceItemView* sender, TraceItem*); + + // getters... + // always get the newest values + TraceData* data() const { return _newData; } + TraceItem* activeItem() const { return _newActiveItem; } + TraceItem* selectedItem() const { return _newSelectedItem; } + TraceCostType* costType() const { return _newCostType; } + TraceCostType* costType2() const { return _newCostType2; } + TraceItem::CostType groupType() const { return _newGroupType; } + const TracePartList& partList() const { return _newPartList; } + + TraceFunction* activeFunction(); + int status() const { return _status; } + + // pointer to top level window to e.g. show status messages + void setTopLevel(TopLevel* t) { _topLevel = t; } + TopLevel* topLevel() const { return _topLevel; } + + void setPosition(Position p) { _pos = p; } + Position position() const { return _pos; } + + void setTitle(QString t) { _title = t; } + QString title() const { return _title; } + + // We depend on derived class to be a widget. + // Force overiding by making this abstract. + virtual QWidget* widget() = 0; + + /** + * This function is called when a new item should become active. + * Reimplement this in subclasses. + * + * Returns the real item to become active. You can call select() here. + * Return 0 if nothing can be shown. + * Use the methods like data() instead of _data here, as + * _data possibly will give old/wrong information. + */ + virtual TraceItem* canShow(TraceItem* i) { return i; } + + /* convenience functions for often used context menu items */ + void addCostMenu(QPopupMenu*,bool withCost2 = true); + void addGoMenu(QPopupMenu*); + +protected: + // helpers to call selected()/activated() of parentView + void selected(TraceItem*); + void selected(const TracePartList&); + void activated(TraceItem*); + void selectedCostType(TraceCostType*); + void selectedCostType2(TraceCostType*); + void activated(Direction); + + /* Is this view visible? + * if not, doUpdate() won't be called by updateView() + */ + virtual bool isViewVisible(); + + // update handler (to be reimplemented) + virtual void doUpdate(int changeType); + + TraceItemView* _parentView; + TopLevel* _topLevel; + + TraceData* _data; + TracePartList _partList; + TraceItem *_activeItem, *_selectedItem; + TraceCostType *_costType, *_costType2; + TraceItem::CostType _groupType; + +private: + TraceData* _newData; + TracePartList _newPartList; + TraceItem *_newActiveItem, *_newSelectedItem; + TraceCostType *_newCostType, *_newCostType2; + TraceItem::CostType _newGroupType; + + QString _title; + int _status; + bool _inUpdate; + Position _pos; +}; + +#endif diff --git a/kcachegrind/kcachegrind/treemap.cpp b/kcachegrind/kcachegrind/treemap.cpp new file mode 100644 index 00000000..3ccc34f9 --- /dev/null +++ b/kcachegrind/kcachegrind/treemap.cpp @@ -0,0 +1,3214 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* + * A Widget for visualizing hierarchical metrics as areas. + * The API is similar to QListView. + */ + +#include <math.h> + +#include <qpainter.h> +#include <qtooltip.h> +#include <qregexp.h> +#include <qstyle.h> +#include <qpopupmenu.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kdebug.h> + +#include "treemap.h" + + +// set this to 1 to enable debug output +#define DEBUG_DRAWING 0 +#define MAX_FIELD 12 + + +// +// StoredDrawParams +// +StoredDrawParams::StoredDrawParams() +{ + _selected = false; + _current = false; + _shaded = true; + _rotated = false; + + _backColor = Qt::white; + + // field array has size 0 +} + +StoredDrawParams::StoredDrawParams(QColor c, + bool selected, bool current) +{ + _backColor = c; + + _selected = selected; + _current = current; + _shaded = true; + _rotated = false; + _drawFrame = true; + + // field array has size 0 +} + +QString StoredDrawParams::text(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return QString::null; + + return _field[f].text; +} + +QPixmap StoredDrawParams::pixmap(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return QPixmap(); + + return _field[f].pix; +} + +DrawParams::Position StoredDrawParams::position(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return Default; + + return _field[f].pos; +} + +int StoredDrawParams::maxLines(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return 0; + + return _field[f].maxLines; +} + +const QFont& StoredDrawParams::font() const +{ + static QFont* f = 0; + if (!f) f = new QFont(QApplication::font()); + + return *f; +} + +void StoredDrawParams::ensureField(int f) +{ + static Field* def = 0; + if (!def) { + def = new Field(); + def->pos = Default; + def->maxLines = 0; + } + + if (f<0 || f>=MAX_FIELD) return; + + if ((int)_field.size() < f+1) _field.resize(f+1, *def); +} + + +void StoredDrawParams::setField(int f, const QString& t, QPixmap pm, + Position p, int maxLines) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].text = t; + _field[f].pix = pm; + _field[f].pos = p; + _field[f].maxLines = maxLines; +} + +void StoredDrawParams::setText(int f, const QString& t) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].text = t; +} + +void StoredDrawParams::setPixmap(int f, const QPixmap& pm) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].pix = pm; +} + +void StoredDrawParams::setPosition(int f, Position p) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].pos = p; +} + +void StoredDrawParams::setMaxLines(int f, int m) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].maxLines = m; +} + + + +// +// RectDrawing +// + +RectDrawing::RectDrawing(QRect r) +{ + _fm = 0; + _dp = 0; + setRect(r); +} + + +RectDrawing::~RectDrawing() +{ + delete _fm; + delete _dp; +} + +DrawParams* RectDrawing::drawParams() +{ + if (!_dp) + _dp = new StoredDrawParams(); + + return _dp; +} + + +void RectDrawing::setDrawParams(DrawParams* dp) +{ + if (_dp) delete _dp; + _dp = dp; +} + +void RectDrawing::setRect(QRect r) +{ + _rect = r; + + _usedTopLeft = 0; + _usedTopCenter = 0; + _usedTopRight = 0; + _usedBottomLeft = 0; + _usedBottomCenter = 0; + _usedBottomRight = 0; + + _fontHeight = 0; +} + +QRect RectDrawing::remainingRect(DrawParams* dp) +{ + if (!dp) dp = drawParams(); + + if ((_usedTopLeft >0) || + (_usedTopCenter >0) || + (_usedTopRight >0)) { + if (dp->rotated()) + _rect.setLeft(_rect.left() + _fontHeight); + else + _rect.setTop(_rect.top() + _fontHeight); + } + + if ((_usedBottomLeft >0) || + (_usedBottomCenter >0) || + (_usedBottomRight >0)) { + if (dp->rotated()) + _rect.setRight(_rect.right() - _fontHeight); + else + _rect.setBottom(_rect.bottom() - _fontHeight); + } + return _rect; +} + + +void RectDrawing::drawBack(QPainter* p, DrawParams* dp) +{ + if (!dp) dp = drawParams(); + if (_rect.width()<=0 || _rect.height()<=0) return; + + QRect r = _rect; + QColor normal = dp->backColor(); + if (dp->selected()) normal = normal.light(); + bool isCurrent = dp->current(); + + if (dp->drawFrame() || isCurrent) { + // 3D raised/sunken frame effect... + QColor high = normal.light(); + QColor low = normal.dark(); + p->setPen( isCurrent ? low:high); + p->drawLine(r.left(), r.top(), r.right(), r.top()); + p->drawLine(r.left(), r.top(), r.left(), r.bottom()); + p->setPen( isCurrent ? high:low); + p->drawLine(r.right(), r.top(), r.right(), r.bottom()); + p->drawLine(r.left(), r.bottom(), r.right(), r.bottom()); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + } + if (r.width()<=0 || r.height()<=0) return; + + if (dp->shaded()) { + // some shading + bool goDark = qGray(normal.rgb())>128; + int rBase, gBase, bBase; + normal.rgb(&rBase, &gBase, &bBase); + p->setBrush(QBrush::NoBrush); + + // shade parameters: + int d = 7; + float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97; + + // coefficient corrections because of rectangle size + int s = r.width(); + if (s > r.height()) s = r.height(); + if (s<100) { + forth -= .3 * (100-s)/100; + back1 -= .2 * (100-s)/100; + back2 -= .02 * (100-s)/100; + } + + + // maximal color difference + int rDiff = goDark ? -rBase/d : (255-rBase)/d; + int gDiff = goDark ? -gBase/d : (255-gBase)/d; + int bDiff = goDark ? -bBase/d : (255-bBase)/d; + + QColor shadeColor; + while (factor<.95) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + factor = 1.0 - ((1.0 - factor) * forth); + } + + // and back (1st half) + while (factor>toBack2) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + factor = 1.0 - ((1.0 - factor) / back1); + } + + // and back (2nd half) + while ( factor>.01) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + + factor = factor * back2; + } + } + + // fill inside + p->setPen(QPen::NoPen); + p->setBrush(normal); + p->drawRect(r); +} + + +bool RectDrawing::drawField(QPainter* p, int f, DrawParams* dp) +{ + if (!dp) dp = drawParams(); + + if (!_fm) { + _fm = new QFontMetrics(dp->font()); + _fontHeight = _fm->height(); + } + + QRect r = _rect; + + if (0) kdDebug(90100) << "DrawField: Rect " << r.x() << "/" << r.y() + << " - " << r.width() << "x" << r.height() << endl; + + int h = _fontHeight; + bool rotate = dp->rotated(); + int width = (rotate ? r.height() : r.width()) -4; + int height = (rotate ? r.width() : r.height()); + int lines = height / h; + + // stop if we have no space available + if (lines<1) return false; + + // calculate free space in first line (<unused>) + int pos = dp->position(f); + if (pos == DrawParams::Default) { + switch(f%4) { + case 0: pos = DrawParams::TopLeft; break; + case 1: pos = DrawParams::TopRight; break; + case 2: pos = DrawParams::BottomRight; break; + case 3: pos = DrawParams::BottomLeft; break; + } + } + + int unused = 0; + bool isBottom = false; + bool isCenter = false; + bool isRight = false; + int* used = 0; + switch(pos) { + case DrawParams::TopLeft: + used = &_usedTopLeft; + if (_usedTopLeft == 0) { + if (_usedTopCenter) + unused = (width - _usedTopCenter)/2; + else + unused = width - _usedTopRight; + } + break; + + case DrawParams::TopCenter: + isCenter = true; + used = &_usedTopCenter; + if (_usedTopCenter == 0) { + if (_usedTopLeft > _usedTopRight) + unused = width - 2 * _usedTopLeft; + else + unused = width - 2 * _usedTopRight; + } + break; + + case DrawParams::TopRight: + isRight = true; + used = &_usedTopRight; + if (_usedTopRight == 0) { + if (_usedTopCenter) + unused = (width - _usedTopCenter)/2; + else + unused = width - _usedTopLeft; + } + break; + + case DrawParams::BottomLeft: + isBottom = true; + used = &_usedBottomLeft; + if (_usedBottomLeft == 0) { + if (_usedBottomCenter) + unused = (width - _usedBottomCenter)/2; + else + unused = width - _usedBottomRight; + } + break; + + case DrawParams::BottomCenter: + isCenter = true; + isBottom = true; + used = &_usedBottomCenter; + if (_usedBottomCenter == 0) { + if (_usedBottomLeft > _usedBottomRight) + unused = width - 2 * _usedBottomLeft; + else + unused = width - 2 * _usedBottomRight; + } + break; + + case DrawParams::BottomRight: + isRight = true; + isBottom = true; + used = &_usedBottomRight; + if (_usedBottomRight == 0) { + if (_usedBottomCenter) + unused = (width - _usedBottomCenter)/2; + else + unused = width - _usedBottomLeft; + } + break; + } + + if (isBottom) { + if ((_usedTopLeft >0) || + (_usedTopCenter >0) || + (_usedTopRight >0)) + lines--; + } + else if (!isBottom) { + if ((_usedBottomLeft >0) || + (_usedBottomCenter >0) || + (_usedBottomRight >0)) + lines--; + } + if (lines<1) return false; + + + int y = isBottom ? height - h : 0; + + if (unused < 0) unused = 0; + if (unused == 0) { + // no space available in last line at this position + y = isBottom ? (y-h) : (y+h); + lines--; + + if (lines<1) return false; + + // new line: reset used space + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + + unused = width; + } + + // stop as soon as possible when there's no space for "..." + static int dotW = 0; + if (!dotW) dotW = _fm->width("..."); + if (width < dotW) return false; + + // get text and pixmap now, only if we need to, because it is possible + // that they are calculated on demand (and this can take some time) + QString name = dp->text(f); + if (name.isEmpty()) return 0; + QPixmap pix = dp->pixmap(f); + + // check if pixmap can be drawn + int pixW = pix.width(); + int pixH = pix.height(); + int pixY = 0; + bool pixDrawn = true; + if (pixW>0) { + pixW += 2; // X distance from pix + if ((width < pixW + dotW) || (height < pixH)) { + // don't draw + pixW = 0; + } + else + pixDrawn = false; + } + + // width of text and pixmap to be drawn + int w = pixW + _fm->width(name); + + if (0) kdDebug(90100) << " For '" << name << "': Unused " << unused + << ", StrW " << w << ", Width " << width << endl; + + // if we have limited space at 1st line: + // use it only if whole name does fit in last line... + if ((unused < width) && (w > unused)) { + y = isBottom ? (y-h) : (y+h); + lines--; + + if (lines<1) return false; + + // new line: reset used space + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + } + + p->save(); + p->setPen( (qGray(dp->backColor().rgb())>100) ? Qt::black : Qt::white); + p->setFont(dp->font()); + if (rotate) { + //p->translate(r.x()+2, r.y()+r.height()); + p->translate(r.x(), r.y()+r.height()-2); + p->rotate(270); + } + else + p->translate(r.x()+2, r.y()); + + + // adjust available lines according to maxLines + int max = dp->maxLines(f); + if ((max > 0) && (lines>max)) lines = max; + + /* loop over name parts to break up string depending on available width. + * every char category change is supposed a possible break, + * with the exception Uppercase=>Lowercase. + * It's good enough for numbers, Symbols... + * + * If the text is to be written at the bottom, we start with the + * end of the string (so everything is reverted) + */ + QString remaining; + int origLines = lines; + while (lines>0) { + + if (w>width && lines>1) { + int lastBreakPos = name.length(), lastWidth = w; + int len = name.length(); + QChar::Category caOld, ca; + + if (!isBottom) { + // start with comparing categories of last 2 chars + caOld = name[len-1].category(); + while (len>2) { + len--; + ca = name[len-1].category(); + if (ca != caOld) { + // "Aa" has no break between... + if (ca == QChar::Letter_Uppercase && + caOld == QChar::Letter_Lowercase) { + caOld = ca; + continue; + } + caOld = ca; + lastBreakPos = len; + w = pixW + _fm->width(name, len); + lastWidth = w; + if (w <= width) break; + } + } + w = lastWidth; + remaining = name.mid(lastBreakPos); + // remove space on break point + if (name[lastBreakPos-1].category() == QChar::Separator_Space) + name = name.left(lastBreakPos-1); + else + name = name.left(lastBreakPos); + } + else { // bottom + int l = len; + caOld = name[l-len].category(); + while (len>2) { + len--; + ca = name[l-len].category(); + + if (ca != caOld) { + // "Aa" has no break between... + if (caOld == QChar::Letter_Uppercase && + ca == QChar::Letter_Lowercase) { + caOld = ca; + continue; + } + caOld = ca; + lastBreakPos = len; + w = pixW + _fm->width(name.right(len)); + lastWidth = w; + if (w <= width) break; + } + } + w = lastWidth; + remaining = name.left(l-lastBreakPos); + // remove space on break point + if (name[l-lastBreakPos].category() == QChar::Separator_Space) + name = name.right(lastBreakPos-1); + else + name = name.right(lastBreakPos); + } + } + else + remaining = QString::null; + + /* truncate and add ... if needed */ + if (w>width) { + int len = name.length(); + w += dotW; + while (len>2 && (w > width)) { + len--; + w = pixW + _fm->width(name, len) + dotW; + } + // stop drawing: we cannot draw 2 chars + "..." + if (w>width) break; + + name = name.left(len) + "..."; + } + + int x = 0; + if (isCenter) + x = (width - w)/2; + else if (isRight) + x = width - w; + + if (!pixDrawn) { + pixY = y+(h-pixH)/2; // default: center vertically + if (pixH > h) pixY = isBottom ? y-(pixH-h) : y; + + p->drawPixmap( x, pixY, pix); + + // for distance to next text + pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2); + pixDrawn = true; + } + + + if (0) kdDebug(90100) << " Drawing '" << name << "' at " + << x+pixW << "/" << y << endl; + + p->drawText( x+pixW, y, + width - pixW, h, + Qt::AlignLeft, name); + y = isBottom ? (y-h) : (y+h); + lines--; + + if (remaining.isEmpty()) break; + name = remaining; + w = pixW + _fm->width(name); + } + + // make sure the pix stays visible + if (pixDrawn && (pixY>0)) { + if (isBottom && (pixY<y)) y = pixY; + if (!isBottom && (pixY>y)) y = pixY; + } + + if (origLines > lines) { + // if only 1 line written, don't reset _used* vars + if (lines - origLines >1) { + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + } + + // take back one line + y = isBottom ? (y+h) : (y-h); + if (used) *used = w; + } + + // update free space + if (!isBottom) { + if (rotate) + _rect.setRect(r.x()+y, r.y(), r.width()-y, r.height()); + else + _rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y); + } + else { + if (rotate) + _rect.setRect(r.x(), r.y(), y+h, r.height()); + else + _rect.setRect(r.x(), r.y(), r.width(), y+h); + } + + p->restore(); + + return true; +} + + + + + + +// +// TreeMapItemList +// + +int TreeMapItemList::compareItems ( Item item1, Item item2 ) +{ + bool ascending; + int result; + + TreeMapItem* parent = ((TreeMapItem*)item1)->parent(); + // shouldn't happen + if (!parent) return 0; + + int textNo = parent->sorting(&ascending); + + if (textNo < 0) { + double diff = ((TreeMapItem*)item1)->value() - + ((TreeMapItem*)item2)->value(); + result = (diff < -.9) ? -1 : (diff > .9) ? 1 : 0; + } + else + result = (((TreeMapItem*)item1)->text(textNo) < + ((TreeMapItem*)item2)->text(textNo)) ? -1 : 1; + + return ascending ? result : -result; +} + + +TreeMapItem* TreeMapItemList::commonParent() +{ + TreeMapItem* parent, *item; + parent = first(); + if (parent) + while( (item = next()) != 0) + parent = parent->commonParent(item); + + return parent; +} + + +// TreeMapItem + +TreeMapItem::TreeMapItem(TreeMapItem* parent, double value) +{ + _value = value; + _parent = parent; + + _sum = 0; + _children = 0; + _widget = 0; + _index = -1; + _depth = -1; // not set + _unused_self = 0; + _freeRects = 0; + + if (_parent) { + // take sorting from parent + _sortTextNo = _parent->sorting(&_sortAscending); + _parent->addItem(this); + } + else { + _sortAscending = false; + _sortTextNo = -1; // default: no sorting + } +} + + +TreeMapItem::TreeMapItem(TreeMapItem* parent, double value, + QString text1, QString text2, + QString text3, QString text4) +{ + _value = value; + _parent = parent; + + // this resizes the text vector only if needed + if (!text4.isEmpty()) setText(3, text4); + if (!text3.isEmpty()) setText(2, text3); + if (!text2.isEmpty()) setText(1, text2); + setText(0, text1); + + _sum = 0; + _children = 0; + _widget = 0; + _index = -1; + _depth = -1; // not set + _unused_self = 0; + _freeRects = 0; + + if (_parent) _parent->addItem(this); +} + +TreeMapItem::~TreeMapItem() +{ + if (_children) delete _children; + if (_freeRects) delete _freeRects; + + // finally, notify widget about deletion + if (_widget) _widget->deletingItem(this); +} + +void TreeMapItem::setParent(TreeMapItem* p) +{ + _parent = p; + if (p) _widget = p->_widget; +} + +bool TreeMapItem::isChildOf(TreeMapItem* item) +{ + if (!item) return false; + + TreeMapItem* i = this; + while (i) { + if (item == i) return true; + i = i->_parent; + } + return false; +} + +TreeMapItem* TreeMapItem::commonParent(TreeMapItem* item) +{ + while (item && !isChildOf(item)) { + item = item->parent(); + } + return item; +} + +void TreeMapItem::redraw() +{ + if (_widget) + _widget->redraw(this); +} + +void TreeMapItem::clear() +{ + if (_children) { + // delete selected items below this item from selection + if (_widget) _widget->clearSelection(this); + + delete _children; + _children = 0; + } +} + + +// invalidates current children and forces redraw +// this is only usefull when children are created on demand in items() +void TreeMapItem::refresh() +{ + clear(); + redraw(); +} + + +QStringList TreeMapItem::path(int textNo) const +{ + QStringList list(text(textNo)); + + TreeMapItem* i = _parent; + while (i) { + QString text = i->text(textNo); + if (!text.isEmpty()) + list.prepend(i->text(textNo)); + i = i->_parent; + } + return list; +} + +int TreeMapItem::depth() const +{ + if (_depth>0) return _depth; + + if (_parent) + return _parent->depth() + 1; + return 1; +} + + +bool TreeMapItem::initialized() +{ + if (!_children) { + _children = new TreeMapItemList; + _children->setAutoDelete(true); + return false; + } + return true; +} + +void TreeMapItem::addItem(TreeMapItem* i) +{ + if (!i) return; + + if (!_children) { + _children = new TreeMapItemList; + _children->setAutoDelete(true); + } + i->setParent(this); + + if (sorting(0) == -1) + _children->append(i); // preserve insertion order + else + _children->inSort(i); +} + + +// default implementations of virtual functions + +double TreeMapItem::value() const +{ + return _value; +} + +double TreeMapItem::sum() const +{ + return _sum; +} + +DrawParams::Position TreeMapItem::position(int f) const +{ + Position p = StoredDrawParams::position(f); + if (_widget && (p == Default)) + p = _widget->fieldPosition(f); + + return p; +} + +// use widget font +const QFont& TreeMapItem::font() const +{ + return _widget->currentFont(); +} + + +bool TreeMapItem::isMarked(int) const +{ + return false; +} + + +int TreeMapItem::borderWidth() const +{ + if (_widget) + return _widget->borderWidth(); + + return 2; +} + +int TreeMapItem::sorting(bool* ascending) const +{ + if (ascending) *ascending = _sortAscending; + return _sortTextNo; +} + +// do *not* set sorting recursively +void TreeMapItem::setSorting(int textNo, bool ascending) +{ + if (_sortTextNo == textNo) { + if(_sortAscending == ascending) return; + if (textNo == -1) { + // when no sorting is done, order change doesn't do anything + _sortAscending = ascending; + return; + } + } + _sortAscending = ascending; + _sortTextNo = textNo; + + if (_children && _sortTextNo != -1) _children->sort(); +} + +void TreeMapItem::resort(bool recursive) +{ + if (!_children) return; + + if (_sortTextNo != -1) _children->sort(); + + if (recursive) + for (TreeMapItem* i=_children->first(); i; i=_children->next()) + i->resort(recursive); +} + + +TreeMapItem::SplitMode TreeMapItem::splitMode() const +{ + if (_widget) + return _widget->splitMode(); + + return Best; +} + +int TreeMapItem::rtti() const +{ + return 0; +} + +TreeMapItemList* TreeMapItem::children() +{ + if (!_children) { + _children = new TreeMapItemList; + _children->setAutoDelete(true); + } + return _children; +} + +void TreeMapItem::clearItemRect() +{ + _rect = QRect(); + clearFreeRects(); +} + +void TreeMapItem::clearFreeRects() +{ + if (_freeRects) _freeRects->clear(); +} + +void TreeMapItem::addFreeRect(const QRect& r) +{ + // don't add invalid rects + if ((r.width() < 1) || (r.height() < 1)) return; + + if (!_freeRects) { + _freeRects = new QPtrList<QRect>; + _freeRects->setAutoDelete(true); + } + + if (0) kdDebug(90100) << "addFree(" << path(0).join("/") << ", " + << r.x() << "/" << r.y() << "-" + << r.width() << "x" << r.height() << ")" << endl; + + QRect* last = _freeRects->last(); + if (!last) { + _freeRects->append(new QRect(r)); + return; + } + + // join rect with last rect if possible + // this saves memory and doesn't make the tooltip flicker + + bool replaced = false; + if ((last->left() == r.left()) && (last->width() == r.width())) { + if ((last->bottom()+1 == r.top()) || (r.bottom()+1 == last->top())) { + *last |= r; + replaced = true; + } + } + else if ((last->top() == r.top()) && (last->height() == r.height())) { + if ((last->right()+1 == r.left()) || (r.right()+1 == last->left())) { + *last |= r; + replaced = true; + } + } + + if (!replaced) { + _freeRects->append(new QRect(r)); + return; + } + + if (0) kdDebug(90100) << " united with last to (" + << last->x() << "/" << last->y() << "-" + << last->width() << "x" << last->height() << ")" << endl; +} + + +// Tooltips for TreeMapWidget + +class TreeMapTip: public QToolTip +{ +public: + TreeMapTip( QWidget* p ):QToolTip(p) {} + +protected: + void maybeTip( const QPoint & ); +}; + +void TreeMapTip::maybeTip( const QPoint& pos ) +{ + if ( !parentWidget()->inherits( "TreeMapWidget" ) ) + return; + + TreeMapWidget* p = (TreeMapWidget*)parentWidget(); + TreeMapItem* i; + i = p->item(pos.x(), pos.y()); + QPtrList<QRect>* rList = i ? i->freeRects() : 0; + if (rList) { + QRect* r; + for(r=rList->first();r;r=rList->next()) + if (r->contains(pos)) + tip(*r, p->tipString(i)); + } +} + + + +// TreeMapWidget + +TreeMapWidget::TreeMapWidget(TreeMapItem* base, + QWidget* parent, const char* name) + : QWidget(parent, name) +{ + _base = base; + _base->setWidget(this); + + _font = font(); + _fontHeight = fontMetrics().height(); + + + // default behaviour + _selectionMode = Single; + _splitMode = TreeMapItem::AlwaysBest; + _visibleWidth = 2; + _reuseSpace = false; + _skipIncorrectBorder = false; + _drawSeparators = false; + _allowRotation = true; + _borderWidth = 2; + _shading = true; // beautiful is default! + _maxSelectDepth = -1; // unlimited + _maxDrawingDepth = -1; // unlimited + _minimalArea = -1; // unlimited + _markNo = 0; + + for(int i=0;i<4;i++) { + _drawFrame[i] = true; + _transparent[i] = false; + } + + // _stopAtText will be unset on resizing (per default) + // _textVisible will be true on resizing (per default) + // _forceText will be false on resizing (per default) + + // start state: _selection is an empty list + _current = 0; + _oldCurrent = 0; + _pressed = 0; + _lastOver = 0; + _needsRefresh = _base; + + setBackgroundMode(Qt::NoBackground); + setFocusPolicy(QWidget::StrongFocus); + _tip = new TreeMapTip(this); +} + +TreeMapWidget::~TreeMapWidget() +{ + delete _base; + delete _tip; +} + +const QFont& TreeMapWidget::currentFont() const +{ + return _font; +} + +void TreeMapWidget::setSplitMode(TreeMapItem::SplitMode m) +{ + if (_splitMode == m) return; + + _splitMode = m; + redraw(); +} + +TreeMapItem::SplitMode TreeMapWidget::splitMode() const +{ + return _splitMode; +} + +bool TreeMapWidget::setSplitMode(QString mode) +{ + if (mode == "Bisection") setSplitMode(TreeMapItem::Bisection); + else if (mode == "Columns") setSplitMode(TreeMapItem::Columns); + else if (mode == "Rows") setSplitMode(TreeMapItem::Rows); + else if (mode == "AlwaysBest") setSplitMode(TreeMapItem::AlwaysBest); + else if (mode == "Best") setSplitMode(TreeMapItem::Best); + else if (mode == "HAlternate") setSplitMode(TreeMapItem::HAlternate); + else if (mode == "VAlternate") setSplitMode(TreeMapItem::VAlternate); + else if (mode == "Horizontal") setSplitMode(TreeMapItem::Horizontal); + else if (mode == "Vertical") setSplitMode(TreeMapItem::Vertical); + else return false; + + return true; +} + +QString TreeMapWidget::splitModeString() const +{ + QString mode; + switch(splitMode()) { + case TreeMapItem::Bisection: mode = "Bisection"; break; + case TreeMapItem::Columns: mode = "Columns"; break; + case TreeMapItem::Rows: mode = "Rows"; break; + case TreeMapItem::AlwaysBest: mode = "AlwaysBest"; break; + case TreeMapItem::Best: mode = "Best"; break; + case TreeMapItem::HAlternate: mode = "HAlternate"; break; + case TreeMapItem::VAlternate: mode = "VAlternate"; break; + case TreeMapItem::Horizontal: mode = "Horizontal"; break; + case TreeMapItem::Vertical: mode = "Vertical"; break; + default: mode = "Unknown"; break; + } + return mode; +} + + +void TreeMapWidget::setShadingEnabled(bool s) +{ + if (_shading == s) return; + + _shading = s; + redraw(); +} + +void TreeMapWidget::drawFrame(int d, bool b) +{ + if ((d<0) || (d>=4) || (_drawFrame[d]==b)) return; + + _drawFrame[d] = b; + redraw(); +} + +void TreeMapWidget::setTransparent(int d, bool b) +{ + if ((d<0) || (d>=4) || (_transparent[d]==b)) return; + + _transparent[d] = b; + redraw(); +} + +void TreeMapWidget::setAllowRotation(bool enable) +{ + if (_allowRotation == enable) return; + + _allowRotation = enable; + redraw(); +} + +void TreeMapWidget::setVisibleWidth(int width, bool reuseSpace) +{ + if (_visibleWidth == width && _reuseSpace == reuseSpace) return; + + _visibleWidth = width; + _reuseSpace = reuseSpace; + redraw(); +} + +void TreeMapWidget::setSkipIncorrectBorder(bool enable) +{ + if (_skipIncorrectBorder == enable) return; + + _skipIncorrectBorder = enable; + redraw(); +} + +void TreeMapWidget::setBorderWidth(int w) +{ + if (_borderWidth == w) return; + + _borderWidth = w; + redraw(); +} + +void TreeMapWidget::setMaxDrawingDepth(int d) +{ + if (_maxDrawingDepth == d) return; + + _maxDrawingDepth = d; + redraw(); +} + +QString TreeMapWidget::defaultFieldType(int f) const +{ + return i18n("Text %1").arg(f+1); +} + +QString TreeMapWidget::defaultFieldStop(int) const +{ + return QString(); +} + +bool TreeMapWidget::defaultFieldVisible(int f) const +{ + return (f<2); +} + +bool TreeMapWidget::defaultFieldForced(int) const +{ + return false; +} + +DrawParams::Position TreeMapWidget::defaultFieldPosition(int f) const +{ + switch(f%4) { + case 0: return DrawParams::TopLeft; + case 1: return DrawParams::TopRight; + case 2: return DrawParams::BottomRight; + case 3: return DrawParams::BottomLeft; + default:break; + } + return DrawParams::TopLeft; +} + +bool TreeMapWidget::resizeAttr(int size) +{ + if (size<0 || size>=MAX_FIELD) return false; + + if (size>(int)_attr.size()) { + struct FieldAttr a; + int oldSize = _attr.size(); + _attr.resize(size, a); + while (oldSize<size) { + _attr[oldSize].type = defaultFieldType(oldSize); + _attr[oldSize].stop = defaultFieldStop(oldSize); + _attr[oldSize].visible = defaultFieldVisible(oldSize); + _attr[oldSize].forced = defaultFieldForced(oldSize); + _attr[oldSize].pos = defaultFieldPosition(oldSize); + oldSize++; + } + } + return true; +} + +void TreeMapWidget::setFieldType(int f, QString type) +{ + if (((int)_attr.size() < f+1) && + (type == defaultFieldType(f))) return; + if (resizeAttr(f+1)) _attr[f].type = type; + + // no need to redraw: the type string is not visible in the TreeMap +} + +QString TreeMapWidget::fieldType(int f) const +{ + if (f<0 || (int)_attr.size()<f+1) return defaultFieldType(f); + return _attr[f].type; +} + +void TreeMapWidget::setFieldStop(int f, QString stop) +{ + if (((int)_attr.size() < f+1) && + (stop == defaultFieldStop(f))) return; + if (resizeAttr(f+1)) { + _attr[f].stop = stop; + redraw(); + } +} + +QString TreeMapWidget::fieldStop(int f) const +{ + if (f<0 || (int)_attr.size()<f+1) return defaultFieldStop(f); + return _attr[f].stop; +} + +void TreeMapWidget::setFieldVisible(int f, bool enable) +{ + if (((int)_attr.size() < f+1) && + (enable == defaultFieldVisible(f))) return; + + if (resizeAttr(f+1)) { + _attr[f].visible = enable; + redraw(); + } +} + +bool TreeMapWidget::fieldVisible(int f) const +{ + if (f<0 || (int)_attr.size()<f+1) + return defaultFieldVisible(f); + + return _attr[f].visible; +} + +void TreeMapWidget::setFieldForced(int f, bool enable) +{ + if (((int)_attr.size() < f+1) && + (enable == defaultFieldForced(f))) return; + + if (resizeAttr(f+1)) { + _attr[f].forced = enable; + if (_attr[f].visible) redraw(); + } +} + +bool TreeMapWidget::fieldForced(int f) const +{ + if (f<0 || (int)_attr.size()<f+1) + return defaultFieldForced(f); + + return _attr[f].forced; +} + +void TreeMapWidget::setFieldPosition(int f, TreeMapItem::Position pos) +{ + if (((int)_attr.size() < f+1) && + (pos == defaultFieldPosition(f))) return; + + if (resizeAttr(f+1)) { + _attr[f].pos = pos; + if (_attr[f].visible) redraw(); + } +} + +DrawParams::Position TreeMapWidget::fieldPosition(int f) const +{ + if (f<0 || (int)_attr.size()<f+1) + return defaultFieldPosition(f); + + return _attr[f].pos; +} + +void TreeMapWidget::setFieldPosition(int f, QString pos) +{ + if (pos == "TopLeft") + setFieldPosition(f, DrawParams::TopLeft); + else if (pos == "TopCenter") + setFieldPosition(f, DrawParams::TopCenter); + else if (pos == "TopRight") + setFieldPosition(f, DrawParams::TopRight); + else if (pos == "BottomLeft") + setFieldPosition(f, DrawParams::BottomLeft); + else if (pos == "BottomCenter") + setFieldPosition(f, DrawParams::BottomCenter); + else if (pos == "BottomRight") + setFieldPosition(f, DrawParams::BottomRight); + else if (pos == "Default") + setFieldPosition(f, DrawParams::Default); +} + +QString TreeMapWidget::fieldPositionString(int f) const +{ + TreeMapItem::Position pos = fieldPosition(f); + if (pos == DrawParams::TopLeft) return QString("TopLeft"); + if (pos == DrawParams::TopCenter) return QString("TopCenter"); + if (pos == DrawParams::TopRight) return QString("TopRight"); + if (pos == DrawParams::BottomLeft) return QString("BottomLeft"); + if (pos == DrawParams::BottomCenter) return QString("BottomCenter"); + if (pos == DrawParams::BottomRight) return QString("BottomRight"); + if (pos == DrawParams::Default) return QString("Default"); + return QString("unknown"); +} + +void TreeMapWidget::setMinimalArea(int area) +{ + if (_minimalArea == area) return; + + _minimalArea = area; + redraw(); +} + + +void TreeMapWidget::deletingItem(TreeMapItem* i) +{ + // remove any references to the item to be deleted + while(_selection.findRef(i) > -1) + _selection.remove(); + + while(_tmpSelection.findRef(i) > -1) + _tmpSelection.remove(); + + if (_current == i) _current = 0; + if (_oldCurrent == i) _oldCurrent = 0; + if (_pressed == i) _pressed = 0; + if (_lastOver == i) _lastOver = 0; + + // don't redraw a deleted item + if (_needsRefresh == i) { + // we can savely redraw the parent, as deleting order is + // from child to parent; i.e. i->parent() is existing. + _needsRefresh = i->parent(); + } +} + + +QString TreeMapWidget::tipString(TreeMapItem* i) const +{ + QString tip, itemTip; + + while (i) { + if (!i->text(0).isEmpty()) { + itemTip = i->text(0); + if (!i->text(1).isEmpty()) + itemTip += " (" + i->text(1) + ")"; + + if (!tip.isEmpty()) + tip += "\n"; + + tip += itemTip; + } + i = i->parent(); + } + return tip; +} + +TreeMapItem* TreeMapWidget::item(int x, int y) const +{ + TreeMapItem* p = _base; + TreeMapItem* i; + + if (!rect().contains(x, y)) return 0; + if (DEBUG_DRAWING) kdDebug(90100) << "item(" << x << "," << y << "):" << endl; + + while (1) { + TreeMapItemList* list = p->children(); + if (!list) + i = 0; + else { + int idx=0; + for (i=list->first();i;i=list->next(),idx++) { + + if (DEBUG_DRAWING) + kdDebug(90100) << " Checking " << i->path(0).join("/") << " (" + << i->itemRect().x() << "/" << i->itemRect().y() + << "-" << i->itemRect().width() + << "x" << i->itemRect().height() << ")" << endl; + + if (i->itemRect().contains(x, y)) { + + if (DEBUG_DRAWING) kdDebug(90100) << " .. Got. Index " << idx << endl; + + p->setIndex(idx); + break; + } + } + } + + if (!i) { + static TreeMapItem* last = 0; + if (p != last) { + last = p; + + if (DEBUG_DRAWING) + kdDebug(90100) << "item(" << x << "," << y << "): Got " + << p->path(0).join("/") << " (Size " + << p->itemRect().width() << "x" << p->itemRect().height() + << ", Val " << p->value() << ")" << endl; + } + + return p; + } + p = i; + } + return 0; +} + +TreeMapItem* TreeMapWidget::possibleSelection(TreeMapItem* i) const +{ + if (i) { + if (_maxSelectDepth>=0) { + int depth = i->depth(); + while(i && depth > _maxSelectDepth) { + i = i->parent(); + depth--; + } + } + } + return i; +} + +TreeMapItem* TreeMapWidget::visibleItem(TreeMapItem* i) const +{ + if (i) { + /* Must have a visible area */ + while(i && ((i->itemRect().width() <1) || + (i->itemRect().height() <1))) { + TreeMapItem* p = i->parent(); + if (!p) break; + int idx = p->children()->findRef(i); + idx--; + if (idx<0) + i = p; + else + i = p->children()->at(idx); + } + } + return i; +} + +void TreeMapWidget::setSelected(TreeMapItem* item, bool selected) +{ + item = possibleSelection(item); + setCurrent(item); + + TreeMapItem* changed = setTmpSelected(item, selected); + if (!changed) return; + + _selection = _tmpSelection; + if (_selectionMode == Single) + emit selectionChanged(item); + emit selectionChanged(); + redraw(changed); + + if (0) kdDebug(90100) << (selected ? "S":"Des") << "elected Item " + << (item ? item->path(0).join("") : QString("(null)")) + << " (depth " << (item ? item->depth() : -1) + << ")" << endl; +} + +void TreeMapWidget::setMarked(int markNo, bool redrawWidget) +{ + // if there's no marking, return + if ((_markNo == 0) && (markNo == 0)) return; + + _markNo = markNo; + if (!clearSelection() && redrawWidget) redraw(); +} + +/* Returns all items which appear only in one of the given lists */ +TreeMapItemList TreeMapWidget::diff(TreeMapItemList& l1, + TreeMapItemList& l2) +{ + TreeMapItemList l; + TreeMapItemListIterator it1(l1), it2(l2); + + TreeMapItem* item; + while ( (item = it1.current()) != 0 ) { + ++it1; + if (l2.containsRef(item) > 0) continue; + l.append(item); + } + while ( (item = it2.current()) != 0 ) { + ++it2; + if (l1.containsRef(item) > 0) continue; + l.append(item); + } + + return l; +} + +/* Only modifies _tmpSelection. + * Returns 0 when no change happened, otherwise the TreeMapItem that has + * to be redrawn for all changes. + */ +TreeMapItem* TreeMapWidget::setTmpSelected(TreeMapItem* item, bool selected) +{ + if (!item) return 0; + if (_selectionMode == NoSelection) return 0; + + TreeMapItemList old = _tmpSelection; + + if (_selectionMode == Single) { + _tmpSelection.clear(); + if (selected) _tmpSelection.append(item); + } + else { + if (selected) { + TreeMapItem* i=_tmpSelection.first(); + while (i) { + if (i->isChildOf(item) || item->isChildOf(i)) { + _tmpSelection.remove(); + i = _tmpSelection.current(); + } + else + i = _tmpSelection.next(); + } + _tmpSelection.append(item); + } + else + _tmpSelection.removeRef(item); + } + + return diff(old, _tmpSelection).commonParent(); +} + + +bool TreeMapWidget::clearSelection(TreeMapItem* parent) +{ + TreeMapItemList old = _selection; + + TreeMapItem* i=_selection.first(); + while (i) { + if (i->isChildOf(parent)) { + _selection.remove(); + i = _selection.current(); + } + else + i = _selection.next(); + } + + TreeMapItem* changed = diff(old, _selection).commonParent(); + if (changed) { + changed->redraw(); + emit selectionChanged(); + } + return (changed != 0); +} + +bool TreeMapWidget::isSelected(TreeMapItem* i) const +{ + return _selection.containsRef(i)>0; +} + +bool TreeMapWidget::isTmpSelected(TreeMapItem* i) +{ + return _tmpSelection.containsRef(i)>0; +} + + +void TreeMapWidget::setCurrent(TreeMapItem* i, bool kbd) +{ + TreeMapItem* old = _current; + _current = i; + + if (_markNo >0) { + // remove mark + _markNo = 0; + + if (1) kdDebug(90100) << "setCurrent(" << i->path(0).join("/") + << ") - mark removed" << endl; + + // always complete redraw needed to remove mark + redraw(); + + if (old == _current) return; + } + else { + if (old == _current) return; + + if (old) old->redraw(); + if (i) i->redraw(); + } + + //kdDebug(90100) << "Current Item " << (i ? i->path().ascii() : "(null)") << endl; + + emit currentChanged(i, kbd); +} + +void TreeMapWidget::setRangeSelection(TreeMapItem* i1, + TreeMapItem* i2, bool selected) +{ + i1 = possibleSelection(i1); + i2 = possibleSelection(i2); + setCurrent(i2); + + TreeMapItem* changed = setTmpRangeSelection(i1, i2, selected); + if (!changed) return; + + _selection = _tmpSelection; + if (_selectionMode == Single) + emit selectionChanged(i2); + emit selectionChanged(); + redraw(changed); +} + +TreeMapItem* TreeMapWidget::setTmpRangeSelection(TreeMapItem* i1, + TreeMapItem* i2, + bool selected) +{ + if ((i1 == 0) && (i2 == 0)) return 0; + if ((i1 == 0) || i1->isChildOf(i2)) return setTmpSelected(i2, selected); + if ((i2 == 0) || i2->isChildOf(i1)) return setTmpSelected(i1, selected); + + TreeMapItem* changed = setTmpSelected(i1, selected); + TreeMapItem* changed2 = setTmpSelected(i2, selected); + if (changed2) changed = changed2->commonParent(changed); + + TreeMapItem* commonParent = i1; + while (commonParent && !i2->isChildOf(commonParent)) { + i1 = commonParent; + commonParent = commonParent->parent(); + } + if (!commonParent) return changed; + while (i2 && i2->parent() != commonParent) + i2 = i2->parent(); + if (!i2) return changed; + + TreeMapItemList* list = commonParent->children(); + if (!list) return changed; + + TreeMapItem* i = list->first(); + bool between = false; + while (i) { + if (between) { + if (i==i1 || i==i2) break; + changed2 = setTmpSelected(i, selected); + if (changed2) changed = changed2->commonParent(changed); + } + else if (i==i1 || i==i2) + between = true; + i = list->next(); + } + + return changed; +} + +void TreeMapWidget::contextMenuEvent( QContextMenuEvent* e ) +{ + //kdDebug(90100) << "TreeMapWidget::contextMenuEvent" << endl; + + if ( receivers( SIGNAL(contextMenuRequested(TreeMapItem*, const QPoint &)) ) ) + e->accept(); + + if ( e->reason() == QContextMenuEvent::Keyboard ) { + QRect r = (_current) ? _current->itemRect() : _base->itemRect(); + QPoint p = QPoint(r.left() + r.width()/2, r.top() + r.height()/2); + emit contextMenuRequested(_current, p); + } + else { + TreeMapItem* i = item(e->x(), e->y()); + emit contextMenuRequested(i, e->pos()); + } +} + + +void TreeMapWidget::mousePressEvent( QMouseEvent* e ) +{ + //kdDebug(90100) << "TreeMapWidget::mousePressEvent" << endl; + + _oldCurrent = _current; + + TreeMapItem* i = item(e->x(), e->y()); + + _pressed = i; + + _inShiftDrag = e->state() & ShiftButton; + _inControlDrag = e->state() & ControlButton; + _lastOver = _pressed; + + TreeMapItem* changed = 0; + TreeMapItem* item = possibleSelection(_pressed); + + switch(_selectionMode) { + case Single: + changed = setTmpSelected(item, true); + break; + case Multi: + changed = setTmpSelected(item, !isTmpSelected(item)); + break; + case Extended: + if (_inControlDrag) + changed = setTmpSelected(item, !isTmpSelected(item)); + else if (_inShiftDrag) { + TreeMapItem* sCurrent = possibleSelection(_current); + changed = setTmpRangeSelection(sCurrent, item, + !isTmpSelected(item)); + } + else { + _selectionMode = Single; + changed = setTmpSelected(item, true); + _selectionMode = Extended; + } + break; + default: + break; + } + + // item under mouse always selected on right button press + if (e->button() == RightButton) { + TreeMapItem* changed2 = setTmpSelected(item, true); + if (changed2) changed = changed2->commonParent(changed); + } + + setCurrent(_pressed); + + if (changed) + redraw(changed); + + if (e->button() == RightButton) { + + // emit selection change + if (! (_tmpSelection == _selection)) { + _selection = _tmpSelection; + if (_selectionMode == Single) + emit selectionChanged(_lastOver); + emit selectionChanged(); + } + _pressed = 0; + _lastOver = 0; + emit rightButtonPressed(i, e->pos()); + } +} + +void TreeMapWidget::mouseMoveEvent( QMouseEvent* e ) +{ + //kdDebug(90100) << "TreeMapWidget::mouseMoveEvent" << endl; + + if (!_pressed) return; + TreeMapItem* over = item(e->x(), e->y()); + if (_lastOver == over) return; + + setCurrent(over); + if (over == 0) { + _lastOver = 0; + return; + } + + TreeMapItem* changed = 0; + TreeMapItem* item = possibleSelection(over); + + switch(_selectionMode) { + case Single: + changed = setTmpSelected(item, true); + break; + case Multi: + changed = setTmpSelected(item, !isTmpSelected(item)); + break; + case Extended: + if (_inControlDrag) + changed = setTmpSelected(item, !isTmpSelected(item)); + else { + TreeMapItem* sLast = possibleSelection(_lastOver); + changed = setTmpRangeSelection(sLast, item, true); + } + break; + + default: + break; + } + + _lastOver = over; + + if (changed) + redraw(changed); +} + +void TreeMapWidget::mouseReleaseEvent( QMouseEvent* ) +{ + //kdDebug(90100) << "TreeMapWidget::mouseReleaseEvent" << endl; + + if (!_pressed) return; + + if (!_lastOver) { + // take back + setCurrent(_oldCurrent); + TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent(); + _tmpSelection = _selection; + if (changed) + redraw(changed); + } + else { + if (! (_tmpSelection == _selection)) { + _selection = _tmpSelection; + if (_selectionMode == Single) + emit selectionChanged(_lastOver); + emit selectionChanged(); + } + if (!_inControlDrag && !_inShiftDrag && (_pressed == _lastOver)) + emit clicked(_lastOver); + } + + _pressed = 0; + _lastOver = 0; +} + + +void TreeMapWidget::mouseDoubleClickEvent( QMouseEvent* e ) +{ + TreeMapItem* over = item(e->x(), e->y()); + + emit doubleClicked(over); +} + + +/* returns -1 if nothing visible found */ +int nextVisible(TreeMapItem* i) +{ + TreeMapItem* p = i->parent(); + if (!p || p->itemRect().isEmpty()) return -1; + + int idx = p->children()->findRef(i); + if (idx<0) return -1; + + while (idx < (int)p->children()->count()-1) { + idx++; + QRect r = p->children()->at(idx)->itemRect(); + if (r.width()>1 && r.height()>1) + return idx; + } + return -1; +} + +/* returns -1 if nothing visible found */ +int prevVisible(TreeMapItem* i) +{ + TreeMapItem* p = i->parent(); + if (!p || p->itemRect().isEmpty()) return -1; + + int idx = p->children()->findRef(i); + if (idx<0) return -1; + + while (idx > 0) { + idx--; + QRect r = p->children()->at(idx)->itemRect(); + if (r.width()>1 && r.height()>1) + return idx; + } + return -1; +} + + + + +void TreeMapWidget::keyPressEvent( QKeyEvent* e ) +{ + if (e->key() == Key_Escape && _pressed) { + + // take back + if (_oldCurrent != _lastOver) + setCurrent(_oldCurrent); + if (! (_tmpSelection == _selection)) { + TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent(); + _tmpSelection = _selection; + if (changed) + redraw(changed); + } + _pressed = 0; + _lastOver = 0; + } + + if ((e->key() == Key_Space) || + (e->key() == Key_Return)) { + + switch(_selectionMode) { + case NoSelection: + break; + case Single: + setSelected(_current, true); + break; + case Multi: + setSelected(_current, !isSelected(_current)); + break; + case Extended: + if ((e->state() & ControlButton) || (e->state() & ShiftButton)) + setSelected(_current, !isSelected(_current)); + else { + _selectionMode = Single; + setSelected(_current, true); + _selectionMode = Extended; + } + } + + if (_current && (e->key() == Key_Return)) + emit returnPressed(_current); + + return; + } + + if (!_current) { + if (e->key() == Key_Down) { + setCurrent(_base, true); + } + return; + } + + TreeMapItem* old = _current, *newItem; + TreeMapItem* p = _current->parent(); + + bool goBack; + if (_current->sorting(&goBack) == -1) { + // noSorting + goBack = false; + } + + + if ((e->key() == Key_Backspace) || + (e->key() == Key_Up)) { + newItem = visibleItem(p); + setCurrent(newItem, true); + } + else if (e->key() == Key_Left) { + int newIdx = goBack ? nextVisible(_current) : prevVisible(_current); + if (p && newIdx>=0) { + p->setIndex(newIdx); + setCurrent(p->children()->at(newIdx), true); + } + } + else if (e->key() == Key_Right) { + int newIdx = goBack ? prevVisible(_current) : nextVisible(_current); + if (p && newIdx>=0) { + p->setIndex(newIdx); + setCurrent(p->children()->at(newIdx), true); + } + } + else if (e->key() == Key_Down) { + if (_current->children() && _current->children()->count()>0) { + int newIdx = _current->index(); + if (newIdx<0) + newIdx = goBack ? (_current->children()->count()-1) : 0; + if (newIdx>=(int)_current->children()->count()) + newIdx = _current->children()->count()-1; + newItem = visibleItem(_current->children()->at(newIdx)); + setCurrent(newItem, true); + } + } + + if (old == _current) return; + if (! (e->state() & ControlButton)) return; + if (! (e->state() & ShiftButton)) return; + + switch(_selectionMode) { + case NoSelection: + break; + case Single: + setSelected(_current, true); + break; + case Multi: + setSelected(_current, !isSelected(_current)); + break; + case Extended: + if (e->state() & ControlButton) + setSelected(_current, !isSelected(_current)); + else + setSelected(_current, isSelected(old)); + } +} + +void TreeMapWidget::fontChange( const QFont& ) +{ + redraw(); +} + + +void TreeMapWidget::resizeEvent( QResizeEvent * ) +{ + // this automatically redraws (as size is changed) + drawTreeMap(); +} + +void TreeMapWidget::paintEvent( QPaintEvent * ) +{ + drawTreeMap(); +} + +void TreeMapWidget::showEvent( QShowEvent * ) +{ + // refresh only if needed + drawTreeMap(); +} + +// Updates screen from shadow buffer, +// but redraws before if needed +void TreeMapWidget::drawTreeMap() +{ + // no need to draw if hidden + if (!isVisible()) return; + + if (_pixmap.size() != size()) + _needsRefresh = _base; + + if (_needsRefresh) { + + if (DEBUG_DRAWING) + kdDebug(90100) << "Redrawing " << _needsRefresh->path(0).join("/") << endl; + + if (_needsRefresh == _base) { + // redraw whole widget + _pixmap = QPixmap(size()); + _pixmap.fill(backgroundColor()); + } + QPainter p(&_pixmap); + if (_needsRefresh == _base) { + p.setPen(black); + p.drawRect(QRect(2, 2, QWidget::width()-4, QWidget::height()-4)); + _base->setItemRect(QRect(3, 3, QWidget::width()-6, QWidget::height()-6)); + } + else { + // only subitem + if (!_needsRefresh->itemRect().isValid()) return; + } + + // reset cached font object; it could have been changed + _font = font(); + _fontHeight = fontMetrics().height(); + + drawItems(&p, _needsRefresh); + _needsRefresh = 0; + } + + bitBlt( this, 0, 0, &_pixmap, 0, 0, + QWidget::width(), QWidget::height(), CopyROP, true); + + if (hasFocus()) { + QPainter p(this); + style().drawPrimitive( QStyle::PE_FocusRect, &p, + QRect(0, 0, QWidget::width(), QWidget::height()), + colorGroup() ); + } +} + + + +void TreeMapWidget::redraw(TreeMapItem* i) +{ + if (!i) return; + + if (!_needsRefresh) + _needsRefresh = i; + else { + if (!i->isChildOf(_needsRefresh)) + _needsRefresh = _needsRefresh->commonParent(i); + } + + if (isVisible()) { + // delayed drawing if we have multiple redraw requests + update(); + } +} + +void TreeMapWidget::drawItem(QPainter* p, + TreeMapItem* item) +{ + bool isSelected = false; + TreeMapItem* i; + + if (_markNo>0) { + for(i = item;i;i=i->parent()) + if (i->isMarked(_markNo)) break; + + isSelected = (i!=0); + } + else { + for (i=_tmpSelection.first();i;i=_tmpSelection.next()) + if (item->isChildOf(i)) break; + + isSelected = (i!=0); + } + + bool isCurrent = _current && item->isChildOf(_current); + int dd = item->depth(); + if (isTransparent(dd)) return; + + RectDrawing d(item->itemRect()); + item->setSelected(isSelected); + item->setCurrent(isCurrent); + item->setShaded(_shading); + item->drawFrame(drawFrame(dd)); + d.drawBack(p, item); +} + + +bool TreeMapWidget::horizontal(TreeMapItem* i, const QRect& r) +{ + switch(i->splitMode()) { + case TreeMapItem::HAlternate: + return (i->depth()%2)==1; + case TreeMapItem::VAlternate: + return (i->depth()%2)==0; + case TreeMapItem::Horizontal: + return true; + case TreeMapItem::Vertical: + return false; + default: + return r.width() > r.height(); + } + return false; +} + + +/** + * Draw TreeMapItems recursive, starting from item + */ +void TreeMapWidget::drawItems(QPainter* p, + TreeMapItem* item) +{ + if (DEBUG_DRAWING) + kdDebug(90100) << "+drawItems(" << item->path(0).join("/") << ", " + << item->itemRect().x() << "/" << item->itemRect().y() + << "-" << item->itemRect().width() << "x" + << item->itemRect().height() << "), Val " << item->value() + << ", Sum " << item->sum() << endl; + + drawItem(p, item); + item->clearFreeRects(); + + QRect origRect = item->itemRect(); + int bw = item->borderWidth(); + QRect r = QRect(origRect.x()+bw, origRect.y()+bw, + origRect.width()-2*bw, origRect.height()-2*bw); + + TreeMapItemList* list = item->children(); + TreeMapItem* i; + + bool stopDrawing = false; + + // only subdivide if there are children + if (!list || list->count()==0) + stopDrawing = true; + + // only subdivide if there is enough space + if (!stopDrawing && (r.width()<=0 || r.height()<=0)) + stopDrawing = true; + + // stop drawing if maximum depth is reached + if (!stopDrawing && + (_maxDrawingDepth>=0 && item->depth()>=_maxDrawingDepth)) + stopDrawing = true; + + // stop drawing if stopAtText is reached + if (!stopDrawing) + for (int no=0;no<(int)_attr.size();no++) { + QString stopAt = fieldStop(no); + if (!stopAt.isEmpty() && (item->text(no) == stopAt)) { + stopDrawing = true; + break; + } + } + + // area size is checked later... +#if 0 + // stop drawing if minimal area size is reached + if (!stopDrawing && + (_minimalArea > 0) && + (r.width() * r.height() < _minimalArea)) stopDrawing = true; +#endif + + if (stopDrawing) { + if (list) { + // invalidate rects + for (i=list->first();i;i=list->next()) + i->clearItemRect(); + } + // tooltip apears on whole item rect + item->addFreeRect(item->itemRect()); + + // if we have space for text... + if ((r.height() < _fontHeight) || (r.width() < _fontHeight)) return; + + RectDrawing d(r); + item->setRotated(_allowRotation && (r.height() > r.width())); + for (int no=0;no<(int)_attr.size();no++) { + if (!fieldVisible(no)) continue; + d.drawField(p, no, item); + } + r = d.remainingRect(item); + + if (DEBUG_DRAWING) + kdDebug(90100) << "-drawItems(" << item->path(0).join("/") << ")" << endl; + return; + } + + double user_sum, child_sum, self; + + // user supplied sum + user_sum = item->sum(); + + // own sum + child_sum = 0; + for (i=list->first();i;i=list->next()) { + child_sum += i->value(); + if (DEBUG_DRAWING) + kdDebug(90100) << " child: " << i->text(0) << ", value " + << i->value() << endl; + } + + QRect orig = r; + + // if we have space for text... + if ((r.height() >= _fontHeight) && (r.width() >= _fontHeight)) { + + RectDrawing d(r); + item->setRotated(_allowRotation && (r.height() > r.width())); + for (int no=0;no<(int)_attr.size();no++) { + if (!fieldVisible(no)) continue; + if (!fieldForced(no)) continue; + d.drawField(p, no, item); + } + r = d.remainingRect(item); + } + + if (orig.x() == r.x()) { + // Strings on top + item->addFreeRect(QRect(orig.x(), orig.y(), + orig.width(), orig.height()-r.height())); + } + else { + // Strings on the left + item->addFreeRect(QRect(orig.x(), orig.y(), + orig.width()-r.width(), orig.height())); + } + + if (user_sum == 0) { + // user didn't supply any sum + user_sum = child_sum; + self = 0; + } + else { + self = user_sum - child_sum; + + if (user_sum < child_sum) { + //kdDebug(90100) << "TreeMWidget " << + // item->path() << ": User sum " << user_sum << " < Child Items sum " << child_sum << endl; + + // invalid user supplied sum: ignore and use calculate sum + user_sum = child_sum; + self = 0.0; + } + else { + // Try to put the border waste in self + // percent of wasted space on border... + float borderArea = origRect.width() * origRect.height(); + borderArea = (borderArea - r.width()*r.height())/borderArea; + unsigned borderValue = (unsigned)(borderArea * user_sum); + + if (borderValue > self) { + if (_skipIncorrectBorder) { + r = origRect; + // should add my self to nested self and set my self =0 + } + else + self = 0.0; + } + else + self -= borderValue; + + user_sum = child_sum + self; + } + } + + bool rotate = (_allowRotation && (r.height() > r.width())); + int self_length = (int)( ((rotate) ? r.width() : r.height()) * + self / user_sum + .5); + if (self_length > 0) { + // take space for self cost + QRect sr = r; + if (rotate) { + sr.setWidth( self_length ); + r.setRect(r.x()+sr.width(), r.y(), r.width()-sr.width(), r.height()); + } + else { + sr.setHeight( self_length ); + r.setRect(r.x(), r.y()+sr.height(), r.width(), r.height()-sr.height()); + } + + // set selfRect (not occupied by children) for tooltip + item->addFreeRect(sr); + + if (0) kdDebug(90100) << "Item " << item->path(0).join("/") << ": SelfR " + << sr.x() << "/" << sr.y() << "-" << sr.width() + << "/" << sr.height() << ", self " << self << "/" + << user_sum << endl; + + if ((sr.height() >= _fontHeight) && (sr.width() >= _fontHeight)) { + + RectDrawing d(sr); + item->setRotated(_allowRotation && (r.height() > r.width())); + for (int no=0;no<(int)_attr.size();no++) { + if (!fieldVisible(no)) continue; + if (fieldForced(no)) continue; + d.drawField(p, no, item); + } + } + + user_sum -= self; + } + + bool goBack; + if (item->sorting(&goBack) == -1) { + // noSorting + goBack = false; + } + + TreeMapItemListIterator it(*list); + if (goBack) it.toLast(); + + if (item->splitMode() == TreeMapItem::Columns) { + int len = list->count(); + bool drawDetails = true; + + while (len>0 && user_sum>0) { + TreeMapItemListIterator first = it; + double valSum = 0; + int lenLeft = len; + int columns = (int)(sqrt((double)len * r.width()/r.height())+.5); + if (columns==0) columns = 1; //should never be needed + + while (lenLeft>0 && ((double)valSum*(len-lenLeft) < + (double)len*user_sum/columns/columns)) { + valSum += it.current()->value(); + if (goBack) --it; else ++it; + lenLeft--; + } + + // we always split horizontally + int nextPos = (int)((double)r.width() * valSum / user_sum); + QRect firstRect = QRect(r.x(), r.y(), nextPos, r.height()); + + if (nextPos < _visibleWidth) { + if (item->sorting(0) == -1) { + // fill current rect with hash pattern + drawFill(item, p, firstRect); + } + else { + // fill rest with hash pattern + drawFill(item, p, r, first, len, goBack); + break; + } + } + else { + drawDetails = drawItemArray(p, item, firstRect, + valSum, first, len-lenLeft, goBack); + } + r.setRect(r.x()+nextPos, r.y(), r.width()-nextPos, r.height()); + user_sum -= valSum; + len = lenLeft; + + if (!drawDetails) { + if (item->sorting(0) == -1) + drawDetails = true; + else { + drawFill(item, p, r, it, len, goBack); + break; + } + } + } + } + else if (item->splitMode() == TreeMapItem::Rows) { + int len = list->count(); + bool drawDetails = true; + + while (len>0 && user_sum>0) { + TreeMapItemListIterator first = it; + double valSum = 0; + int lenLeft = len; + int rows = (int)(sqrt((double)len * r.height()/r.width())+.5); + if (rows==0) rows = 1; //should never be needed + + while (lenLeft>0 && ((double)valSum*(len-lenLeft) < + (double)len*user_sum/rows/rows)) { + valSum += it.current()->value(); + if (goBack) --it; else ++it; + lenLeft--; + } + + // we always split horizontally + int nextPos = (int)((double)r.height() * valSum / user_sum); + QRect firstRect = QRect(r.x(), r.y(), r.width(), nextPos); + + if (nextPos < _visibleWidth) { + if (item->sorting(0) == -1) { + drawFill(item, p, firstRect); + } + else { + drawFill(item, p, r, first, len, goBack); + break; + } + } + else { + drawDetails = drawItemArray(p, item, firstRect, + valSum, first, len-lenLeft, goBack); + } + r.setRect(r.x(), r.y()+nextPos, r.width(), r.height()-nextPos); + user_sum -= valSum; + len = lenLeft; + + if (!drawDetails) { + if (item->sorting(0) == -1) + drawDetails = true; + else { + drawFill(item, p, r, it, len, goBack); + break; + } + } + } + } + else + drawItemArray(p, item, r, user_sum, it, list->count(), goBack); + + if (DEBUG_DRAWING) + kdDebug(90100) << "-drawItems(" << item->path(0).join("/") << ")" << endl; +} + +// fills area with a pattern if to small to draw children +void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, QRect& r) +{ + p->setBrush(Qt::Dense4Pattern); + p->setPen(Qt::NoPen); + p->drawRect(r); + i->addFreeRect(r); +} + +// fills area with a pattern if to small to draw children +void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, QRect& r, + TreeMapItemListIterator it, int len, bool goBack) +{ + if (DEBUG_DRAWING) + kdDebug(90100) << " +drawFill(" << r.x() << "/" << r.y() + << "-" << r.width() << "x" << r.height() + << ", len " << len << ")" << endl; + + p->setBrush(Qt::Dense4Pattern); + p->setPen(Qt::NoPen); + p->drawRect(r); + i->addFreeRect(r); + + // reset rects + while (len>0 && it.current()) { + + if (DEBUG_DRAWING) + kdDebug(90100) << " Reset Rect " << (*it)->path(0).join("/") << endl; + + (*it)->clearItemRect(); + if (goBack) --it; else ++it; + len--; + } + if (DEBUG_DRAWING) + kdDebug(90100) << " -drawFill(" << r.x() << "/" << r.y() + << "-" << r.width() << "x" << r.height() + << ", len " << len << ")" << endl; +} + +// returns false if rect gets to small +bool TreeMapWidget::drawItemArray(QPainter* p, TreeMapItem* item, + QRect& r, double user_sum, + TreeMapItemListIterator it, int len, + bool goBack) +{ + if (user_sum == 0) return false; + + static bool b2t = true; + + // stop recursive bisection for small rectangles + if (((r.height() < _visibleWidth) && + (r.width() < _visibleWidth)) || + ((_minimalArea > 0) && + (r.width() * r.height() < _minimalArea))) { + + drawFill(item, p, r, it, len, goBack); + return false; + } + + if (DEBUG_DRAWING) + kdDebug(90100) << " +drawItemArray(" << item->path(0).join("/") + << ", " << r.x() << "/" << r.y() << "-" << r.width() + << "x" << r.height() << ")" << endl; + + if (len>2 && (item->splitMode() == TreeMapItem::Bisection)) { + + TreeMapItemListIterator first = it; + double valSum = 0; + int lenLeft = len; + //while (lenLeft>0 && valSum<user_sum/2) { + while (lenLeft>len/2) { + valSum += it.current()->value(); + if (goBack) --it; else ++it; + lenLeft--; + } + + // draw first half... + bool drawOn; + + if (r.width() > r.height()) { + int halfPos = (int)((double)r.width() * valSum / user_sum); + QRect firstRect = QRect(r.x(), r.y(), halfPos, r.height()); + drawOn = drawItemArray(p, item, firstRect, + valSum, first, len-lenLeft, goBack); + r.setRect(r.x()+halfPos, r.y(), r.width()-halfPos, r.height()); + } + else { + int halfPos = (int)((double)r.height() * valSum / user_sum); + QRect firstRect = QRect(r.x(), r.y(), r.width(), halfPos); + drawOn = drawItemArray(p, item, firstRect, + valSum, first, len-lenLeft, goBack); + r.setRect(r.x(), r.y()+halfPos, r.width(), r.height()-halfPos); + } + + // if no sorting, don't stop drawing + if (item->sorting(0) == -1) drawOn = true; + + // second half + if (drawOn) + drawOn = drawItemArray(p, item, r, user_sum - valSum, + it, lenLeft, goBack); + else { + drawFill(item, p, r, it, len, goBack); + } + + if (DEBUG_DRAWING) + kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/") + << ")" << endl; + + return drawOn; + } + + bool hor = horizontal(item,r); + + TreeMapItem* i; + while (len>0) { + i = it.current(); + if (user_sum <= 0) { + + if (DEBUG_DRAWING) + kdDebug(90100) << "drawItemArray: Reset " << i->path(0).join("/") << endl; + + i->clearItemRect(); + if (goBack) --it; else ++it; + len--; + continue; + } + + // stop drawing for small rectangles + if (((r.height() < _visibleWidth) && + (r.width() < _visibleWidth)) || + ((_minimalArea > 0) && + (r.width() * r.height() < _minimalArea))) { + + drawFill(item, p, r, it, len, goBack); + if (DEBUG_DRAWING) + kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/") + << "): Stop" << endl; + return false; + } + + if (i->splitMode() == TreeMapItem::AlwaysBest) + hor = r.width() > r.height(); + + int lastPos = hor ? r.width() : r.height(); + double val = i->value(); + int nextPos = (user_sum <= 0.0) ? 0: (int)(lastPos * val / user_sum +.5); + if (nextPos>lastPos) nextPos = lastPos; + + if ((item->sorting(0) != -1) && (nextPos < _visibleWidth)) { + drawFill(item, p, r, it, len, goBack); + if (DEBUG_DRAWING) + kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/") + << "): Stop" << endl; + return false; + } + + QRect currRect = r; + + if (hor) + currRect.setWidth(nextPos); + else { + if (b2t) + currRect.setRect(r.x(), r.bottom()-nextPos+1, r.width(), nextPos); + else + currRect.setHeight(nextPos); + } + + // don't draw very small rectangles: + if (nextPos >= _visibleWidth) { + i->setItemRect(currRect); + drawItems(p, i); + } + else { + i->clearItemRect(); + drawFill(item, p, currRect); + } + + // draw Separator + if (_drawSeparators && (nextPos<lastPos)) { + p->setPen(black); + if (hor) { + if (r.top()<=r.bottom()) + p->drawLine(r.x() + nextPos, r.top(), r.x() + nextPos, r.bottom()); + } + else { + if (r.left()<=r.right()) + p->drawLine(r.left(), r.y() + nextPos, r.right(), r.y() + nextPos); + } + nextPos++; + } + + if (hor) + r.setRect(r.x() + nextPos, r.y(), lastPos-nextPos, r.height()); + else { + if (b2t) + r.setRect(r.x(), r.y(), r.width(), lastPos-nextPos); + else + r.setRect(r.x(), r.y() + nextPos, r.width(), lastPos-nextPos); + } + + user_sum -= val; + if (goBack) --it; else ++it; + len--; + } + + if (DEBUG_DRAWING) + kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/") + << "): Continue" << endl; + + return true; +} + + +/*---------------------------------------------------------------- + * Popup menus for option setting + */ + +void TreeMapWidget::splitActivated(int id) +{ + if (id == _splitID) setSplitMode(TreeMapItem::Bisection); + else if (id == _splitID+1) setSplitMode(TreeMapItem::Columns); + else if (id == _splitID+2) setSplitMode(TreeMapItem::Rows); + else if (id == _splitID+3) setSplitMode(TreeMapItem::AlwaysBest); + else if (id == _splitID+4) setSplitMode(TreeMapItem::Best); + else if (id == _splitID+5) setSplitMode(TreeMapItem::VAlternate); + else if (id == _splitID+6) setSplitMode(TreeMapItem::HAlternate); + else if (id == _splitID+7) setSplitMode(TreeMapItem::Horizontal); + else if (id == _splitID+8) setSplitMode(TreeMapItem::Vertical); +} + + +void TreeMapWidget::addSplitDirectionItems(QPopupMenu* popup, int id) +{ + _splitID = id; + popup->setCheckable(true); + + connect(popup, SIGNAL(activated(int)), + this, SLOT(splitActivated(int))); + + popup->insertItem(i18n("Recursive Bisection"), id); + popup->insertItem(i18n("Columns"), id+1); + popup->insertItem(i18n("Rows"), id+2); + popup->insertItem(i18n("Always Best"), id+3); + popup->insertItem(i18n("Best"), id+4); + popup->insertItem(i18n("Alternate (V)"), id+5); + popup->insertItem(i18n("Alternate (H)"), id+6); + popup->insertItem(i18n("Horizontal"), id+7); + popup->insertItem(i18n("Vertical"), id+8); + + switch(splitMode()) { + case TreeMapItem::Bisection: popup->setItemChecked(id,true); break; + case TreeMapItem::Columns: popup->setItemChecked(id+1,true); break; + case TreeMapItem::Rows: popup->setItemChecked(id+2,true); break; + case TreeMapItem::AlwaysBest: popup->setItemChecked(id+3,true); break; + case TreeMapItem::Best: popup->setItemChecked(id+4,true); break; + case TreeMapItem::VAlternate: popup->setItemChecked(id+5,true); break; + case TreeMapItem::HAlternate: popup->setItemChecked(id+6,true); break; + case TreeMapItem::Horizontal: popup->setItemChecked(id+7,true); break; + case TreeMapItem::Vertical: popup->setItemChecked(id+8,true); break; + default: break; + } +} + +void TreeMapWidget::visualizationActivated(int id) +{ + if (id == _visID+2) setSkipIncorrectBorder(!skipIncorrectBorder()); + else if (id == _visID+3) setBorderWidth(0); + else if (id == _visID+4) setBorderWidth(1); + else if (id == _visID+5) setBorderWidth(2); + else if (id == _visID+6) setBorderWidth(3); + else if (id == _visID+10) setAllowRotation(!allowRotation()); + else if (id == _visID+11) setShadingEnabled(!isShadingEnabled()); + else if (id<_visID+19 || id>_visID+100) return; + + id -= 20+_visID; + int f = id/10; + if ((id%10) == 1) setFieldVisible(f, !fieldVisible(f)); + else if ((id%10) == 2) setFieldForced(f, !fieldForced(f)); + else if ((id%10) == 3) setFieldPosition(f, DrawParams::TopLeft); + else if ((id%10) == 4) setFieldPosition(f, DrawParams::TopCenter); + else if ((id%10) == 5) setFieldPosition(f, DrawParams::TopRight); + else if ((id%10) == 6) setFieldPosition(f, DrawParams::BottomLeft); + else if ((id%10) == 7) setFieldPosition(f, DrawParams::BottomCenter); + else if ((id%10) == 8) setFieldPosition(f, DrawParams::BottomRight); +} + +void TreeMapWidget::addVisualizationItems(QPopupMenu* popup, int id) +{ + _visID = id; + + popup->setCheckable(true); + + QPopupMenu* bpopup = new QPopupMenu(); + bpopup->setCheckable(true); + + connect(popup, SIGNAL(activated(int)), + this, SLOT(visualizationActivated(int))); + connect(bpopup, SIGNAL(activated(int)), + this, SLOT(visualizationActivated(int))); + + QPopupMenu* spopup = new QPopupMenu(); + addSplitDirectionItems(spopup, id+100); + popup->insertItem(i18n("Nesting"), spopup, id); + + popup->insertItem(i18n("Border"), bpopup, id+1); + bpopup->insertItem(i18n("Correct Borders Only"), id+2); + bpopup->insertSeparator(); + bpopup->insertItem(i18n("Width %1").arg(0), id+3); + bpopup->insertItem(i18n("Width %1").arg(1), id+4); + bpopup->insertItem(i18n("Width %1").arg(2), id+5); + bpopup->insertItem(i18n("Width %1").arg(3), id+6); + bpopup->setItemChecked(id+2, skipIncorrectBorder()); + bpopup->setItemChecked(id+3, borderWidth()==0); + bpopup->setItemChecked(id+4, borderWidth()==1); + bpopup->setItemChecked(id+5, borderWidth()==2); + bpopup->setItemChecked(id+6, borderWidth()==3); + + popup->insertItem(i18n("Allow Rotation"), id+10); + popup->setItemChecked(id+10,allowRotation()); + popup->insertItem(i18n("Shading"), id+11); + popup->setItemChecked(id+11,isShadingEnabled()); + + if (_attr.size() ==0) return; + + popup->insertSeparator(); + int f; + QPopupMenu* tpopup; + id += 20; + for (f=0;f<(int)_attr.size();f++, id+=10) { + tpopup = new QPopupMenu(); + tpopup->setCheckable(true); + popup->insertItem(_attr[f].type, tpopup, id); + tpopup->insertItem(i18n("Visible"), id+1); + tpopup->insertItem(i18n("Take Space From Children"), id+2); + tpopup->insertSeparator(); + tpopup->insertItem(i18n("Top Left"), id+3); + tpopup->insertItem(i18n("Top Center"), id+4); + tpopup->insertItem(i18n("Top Right"), id+5); + tpopup->insertItem(i18n("Bottom Left"), id+6); + tpopup->insertItem(i18n("Bottom Center"), id+7); + tpopup->insertItem(i18n("Bottom Right"), id+8); + + tpopup->setItemChecked(id+1,_attr[f].visible); + tpopup->setItemEnabled(id+2,_attr[f].visible); + tpopup->setItemEnabled(id+3,_attr[f].visible); + tpopup->setItemEnabled(id+4,_attr[f].visible); + tpopup->setItemEnabled(id+5,_attr[f].visible); + tpopup->setItemEnabled(id+6,_attr[f].visible); + tpopup->setItemEnabled(id+7,_attr[f].visible); + tpopup->setItemEnabled(id+8,_attr[f].visible); + tpopup->setItemChecked(id+2,_attr[f].forced); + tpopup->setItemChecked(id+3,_attr[f].pos == DrawParams::TopLeft); + tpopup->setItemChecked(id+4,_attr[f].pos == DrawParams::TopCenter); + tpopup->setItemChecked(id+5,_attr[f].pos == DrawParams::TopRight); + tpopup->setItemChecked(id+6,_attr[f].pos == DrawParams::BottomLeft); + tpopup->setItemChecked(id+7,_attr[f].pos == DrawParams::BottomCenter); + tpopup->setItemChecked(id+8,_attr[f].pos == DrawParams::BottomRight); + + connect(tpopup, SIGNAL(activated(int)), + this, SLOT(visualizationActivated(int))); + } +} + +void TreeMapWidget::selectionActivated(int id) +{ + TreeMapItem* i = _menuItem; + id -= _selectionID; + while (id>0 && i) { + i=i->parent(); + id--; + } + if (i) + setSelected(i, true); +} + +void TreeMapWidget::addSelectionItems(QPopupMenu* popup, + int id, TreeMapItem* i) +{ + if (!i) return; + + _selectionID = id; + _menuItem = i; + + connect(popup, SIGNAL(activated(int)), + this, SLOT(selectionActivated(int))); + + while (i) { + QString name = i->text(0); + if (name.isEmpty()) break; + popup->insertItem(i->text(0), id++); + i = i->parent(); + } +} + +void TreeMapWidget::fieldStopActivated(int id) +{ + if (id == _fieldStopID) setFieldStop(0, QString::null); + else { + TreeMapItem* i = _menuItem; + id -= _fieldStopID+1; + while (id>0 && i) { + i=i->parent(); + id--; + } + if (i) + setFieldStop(0, i->text(0)); + } +} + +void TreeMapWidget::addFieldStopItems(QPopupMenu* popup, + int id, TreeMapItem* i) +{ + _fieldStopID = id; + + connect(popup, SIGNAL(activated(int)), + this, SLOT(fieldStopActivated(int))); + + popup->insertItem(i18n("No %1 Limit").arg(fieldType(0)), id); + popup->setItemChecked(id, fieldStop(0).isEmpty()); + _menuItem = i; + bool foundFieldStop = false; + if (i) { + popup->insertSeparator(); + + while (i) { + id++; + QString name = i->text(0); + if (name.isEmpty()) break; + popup->insertItem(i->text(0), id); + if (fieldStop(0) == i->text(0)) { + popup->setItemChecked(id, true); + foundFieldStop = true; + } + i = i->parent(); + } + } + + if (!foundFieldStop && !fieldStop(0).isEmpty()) { + popup->insertSeparator(); + popup->insertItem(fieldStop(0), id+1); + popup->setItemChecked(id+1, true); + } +} + +void TreeMapWidget::areaStopActivated(int id) +{ + if (id == _areaStopID) setMinimalArea(-1); + else if (id == _areaStopID+1) { + int area = _menuItem ? (_menuItem->width() * _menuItem->height()) : -1; + setMinimalArea(area); + } + else if (id == _areaStopID+2) setMinimalArea(100); + else if (id == _areaStopID+3) setMinimalArea(400); + else if (id == _areaStopID+4) setMinimalArea(1000); + else if (id == _areaStopID+5) setMinimalArea(minimalArea()*2); + else if (id == _areaStopID+6) setMinimalArea(minimalArea()/2); +} + +void TreeMapWidget::addAreaStopItems(QPopupMenu* popup, + int id, TreeMapItem* i) +{ + _areaStopID = id; + _menuItem = i; + + connect(popup, SIGNAL(activated(int)), + this, SLOT(areaStopActivated(int))); + + bool foundArea = false; + + popup->insertItem(i18n("No Area Limit"), id); + popup->setItemChecked(id, minimalArea() == -1); + + if (i) { + int area = i->width() * i->height(); + popup->insertSeparator(); + popup->insertItem(i18n("Area of '%1' (%2)") + .arg(i->text(0)).arg(area), id+1); + if (area == minimalArea()) { + popup->setItemChecked(id+1, true); + foundArea = true; + } + } + + popup->insertSeparator(); + int area = 100, count; + for (count=0;count<3;count++) { + popup->insertItem(i18n("1 Pixel", "%n Pixels", area), id+2+count); + if (area == minimalArea()) { + popup->setItemChecked(id+2+count, true); + foundArea = true; + } + area = (area==100) ? 400 : (area==400) ? 1000 : 4000; + } + + if (minimalArea()>0) { + popup->insertSeparator(); + if (!foundArea) { + popup->insertItem(i18n("1 Pixel", "%n Pixels", minimalArea()), id+10); + popup->setItemChecked(id+10, true); + } + + popup->insertItem(i18n("Double Area Limit (to %1)") + .arg(minimalArea()*2), id+5); + popup->insertItem(i18n("Halve Area Limit (to %1)") + .arg(minimalArea()/2), id+6); + } +} + + +void TreeMapWidget::depthStopActivated(int id) +{ + if (id == _depthStopID) setMaxDrawingDepth(-1); + else if (id == _depthStopID+1) { + int d = _menuItem ? _menuItem->depth() : -1; + setMaxDrawingDepth(d); + } + else if (id == _depthStopID+2) setMaxDrawingDepth(maxDrawingDepth()-1); + else if (id == _depthStopID+3) setMaxDrawingDepth(maxDrawingDepth()+1); +} + +void TreeMapWidget::addDepthStopItems(QPopupMenu* popup, + int id, TreeMapItem* i) +{ + _depthStopID = id; + _menuItem = i; + + connect(popup, SIGNAL(activated(int)), + this, SLOT(depthStopActivated(int))); + + bool foundDepth = false; + + popup->insertItem(i18n("No Depth Limit"), id); + popup->setItemChecked(id, maxDrawingDepth() == -1); + + if (i) { + int d = i->depth(); + popup->insertSeparator(); + popup->insertItem(i18n("Depth of '%1' (%2)") + .arg(i->text(0)).arg(d), id+1); + if (d == maxDrawingDepth()) { + popup->setItemChecked(id+1, true); + foundDepth = true; + } + } + + if (maxDrawingDepth()>1) { + popup->insertSeparator(); + if (!foundDepth) { + popup->insertItem(i18n("Depth %1").arg(maxDrawingDepth()), id+10); + popup->setItemChecked(id+10, true); + } + + popup->insertItem(i18n("Decrement (to %1)") + .arg(maxDrawingDepth()-1), id+2); + popup->insertItem(i18n("Increment (to %1)") + .arg(maxDrawingDepth()+1), id+3); + } +} + + + +/*---------------------------------------------------------------- + * Option saving/restoring + */ + +void TreeMapWidget::saveOptions(KConfigGroup* config, QString prefix) +{ + config->writeEntry(prefix+"Nesting", splitModeString()); + config->writeEntry(prefix+"AllowRotation", allowRotation()); + config->writeEntry(prefix+"ShadingEnabled", isShadingEnabled()); + config->writeEntry(prefix+"OnlyCorrectBorder", skipIncorrectBorder()); + config->writeEntry(prefix+"BorderWidth", borderWidth()); + config->writeEntry(prefix+"MaxDepth", maxDrawingDepth()); + config->writeEntry(prefix+"MinimalArea", minimalArea()); + + int f, fCount = _attr.size(); + config->writeEntry(prefix+"FieldCount", fCount); + for (f=0;f<fCount;f++) { + config->writeEntry(QString(prefix+"FieldVisible%1").arg(f), + _attr[f].visible); + config->writeEntry(QString(prefix+"FieldForced%1").arg(f), + _attr[f].forced); + config->writeEntry(QString(prefix+"FieldStop%1").arg(f), + _attr[f].stop); + config->writeEntry(QString(prefix+"FieldPosition%1").arg(f), + fieldPositionString(f)); + } +} + + +void TreeMapWidget::restoreOptions(KConfigGroup* config, QString prefix) +{ + bool enabled; + int num; + QString str; + + str = config->readEntry(prefix+"Nesting"); + if (!str.isEmpty()) setSplitMode(str); + + if (config->hasKey(prefix+"AllowRotation")) { + enabled = config->readBoolEntry(prefix+"AllowRotation", true); + setAllowRotation(enabled); + } + + if (config->hasKey(prefix+"ShadingEnabled")) { + enabled = config->readBoolEntry(prefix+"ShadingEnabled", true); + setShadingEnabled(enabled); + } + + if (config->hasKey(prefix+"OnlyCorrectBorder")) { + enabled = config->readBoolEntry(prefix+"OnlyCorrectBorder", false); + setSkipIncorrectBorder(enabled); + } + + num = config->readNumEntry(prefix+"BorderWidth", -2); + if (num!=-2) setBorderWidth(num); + + num = config->readNumEntry(prefix+"MaxDepth", -2); + if (num!=-2) setMaxDrawingDepth(num); + + num = config->readNumEntry(prefix+"MinimalArea", -2); + if (num!=-2) setMinimalArea(num); + + num = config->readNumEntry(prefix+"FieldCount", -2); + if (num<=0 || num>MAX_FIELD) return; + + int f; + for (f=0;f<num;f++) { + str = QString(prefix+"FieldVisible%1").arg(f); + if (config->hasKey(str)) + setFieldVisible(f, config->readBoolEntry(str)); + + str = QString(prefix+"FieldForced%1").arg(f); + if (config->hasKey(str)) + setFieldForced(f, config->readBoolEntry(str)); + + str = config->readEntry(QString(prefix+"FieldStop%1").arg(f)); + setFieldStop(f, str); + + str = config->readEntry(QString(prefix+"FieldPosition%1").arg(f)); + if (!str.isEmpty()) setFieldPosition(f, str); + } +} + +#include "treemap.moc" diff --git a/kcachegrind/kcachegrind/treemap.h b/kcachegrind/kcachegrind/treemap.h new file mode 100644 index 00000000..4bb0b4cd --- /dev/null +++ b/kcachegrind/kcachegrind/treemap.h @@ -0,0 +1,758 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * A Widget for visualizing hierarchical metrics as areas. + * The API is similar to QListView. + * + * This file defines the following classes: + * DrawParams, RectDrawing, TreeMapItem, TreeMapWidget + * + * DrawParams/RectDrawing allows reusing of TreeMap drawing + * functions in other widgets. + */ + +#ifndef TREEMAP_H +#define TREEMAP_H + +#include <qstring.h> +#include <qwidget.h> +#include <qpixmap.h> +#include <qptrlist.h> +#include <qvaluevector.h> +#include <qcolor.h> +#include <qapplication.h> +#include <qstringlist.h> + +class QPopupMenu; +class TreeMapTip; +class TreeMapWidget; +class TreeMapItem; +class TreeMapItemList; +class QString; + +class KConfigGroup; + + +/** + * Drawing parameters for an object. + * A Helper Interface for RectDrawing. + */ +class DrawParams +{ +public: + /** + * Positions for drawing into a rectangle. + * + * The specified position assumes no rotation. + * If there is more than one text for one position, it is put + * nearer to the center of the item. + * + * Drawing at top positions cuts free space from top, + * drawing at bottom positions cuts from bottom. + * Default usually gives positions clockwise according to field number. + */ + enum Position { TopLeft, TopCenter, TopRight, + BottomLeft, BottomCenter, BottomRight, + Default, Unknown}; + + // no constructor as this is an abstract class + virtual ~DrawParams() {} + + virtual QString text(int) const = 0; + virtual QPixmap pixmap(int) const = 0; + virtual Position position(int) const = 0; + // 0: no limit, negative: leave at least -maxLines() free + virtual int maxLines(int) const { return 0; } + virtual int fieldCount() const { return 0; } + + virtual QColor backColor() const { return Qt::white; } + virtual const QFont& font() const = 0; + + virtual bool selected() const { return false; } + virtual bool current() const { return false; } + virtual bool shaded() const { return true; } + virtual bool rotated() const { return false; } + virtual bool drawFrame() const { return true; } +}; + + +/* + * DrawParam with attributes stored + */ +class StoredDrawParams: public DrawParams +{ +public: + StoredDrawParams(); + StoredDrawParams(QColor c, + bool selected = false, bool current = false); + + // getters + QString text(int) const; + QPixmap pixmap(int) const; + Position position(int) const; + int maxLines(int) const; + int fieldCount() const { return _field.size(); } + + QColor backColor() const { return _backColor; } + bool selected() const { return _selected; } + bool current() const { return _current; } + bool shaded() const { return _shaded; } + bool rotated() const { return _rotated; } + bool drawFrame() const { return _drawFrame; } + + const QFont& font() const; + + // attribute setters + void setField(int f, const QString& t, QPixmap pm = QPixmap(), + Position p = Default, int maxLines = 0); + void setText(int f, const QString&); + void setPixmap(int f, const QPixmap&); + void setPosition(int f, Position); + void setMaxLines(int f, int); + void setBackColor(const QColor& c) { _backColor = c; } + void setSelected(bool b) { _selected = b; } + void setCurrent(bool b) { _current = b; } + void setShaded(bool b) { _shaded = b; } + void setRotated(bool b) { _rotated = b; } + void drawFrame(bool b) { _drawFrame = b; } + +protected: + QColor _backColor; + bool _selected :1; + bool _current :1; + bool _shaded :1; + bool _rotated :1; + bool _drawFrame :1; + +private: + // resize field array if needed to allow to access field <f> + void ensureField(int f); + + struct Field { + QString text; + QPixmap pix; + Position pos; + int maxLines; + }; + + QValueVector<Field> _field; +}; + + +/* State for drawing on a rectangle. + * + * Following drawing functions are provided: + * - background drawing with shading and 3D frame + * - successive pixmap/text drawing at various positions with wrap-around + * optimized for minimal space usage (e.g. if a text is drawn at top right + * after text on top left, the same line is used if space allows) + * + */ +class RectDrawing +{ +public: + RectDrawing(QRect); + ~RectDrawing(); + + // The default DrawParams object used. + DrawParams* drawParams(); + // we take control over the given object (i.e. delete at destruction) + void setDrawParams(DrawParams*); + + // draw on a given QPainter, use this class as info provider per default + void drawBack(QPainter*, DrawParams* dp = 0); + /* Draw field at position() from pixmap()/text() with maxLines(). + * Returns true if something was drawn + */ + bool drawField(QPainter*, int f, DrawParams* dp = 0); + + // resets rectangle for free space + void setRect(QRect); + + // Returns the rectangle area still free of text/pixmaps after + // a number of drawText() calls. + QRect remainingRect(DrawParams* dp = 0); + +private: + int _usedTopLeft, _usedTopCenter, _usedTopRight; + int _usedBottomLeft, _usedBottomCenter, _usedBottomRight; + QRect _rect; + + // temporary + int _fontHeight; + QFontMetrics* _fm; + DrawParams* _dp; +}; + + +class TreeMapItemList: public QPtrList<TreeMapItem> +{ +public: + TreeMapItem* commonParent(); +protected: + int compareItems ( Item item1, Item item2 ); +}; + +typedef QPtrListIterator<TreeMapItem> TreeMapItemListIterator; + + +/** + * Base class of items in TreeMap. + * + * This class supports an arbitrary number of text() strings + * positioned counterclock-wise starting at TopLeft. Each item + * has its own static value(), sum() and sorting(). The + * splitMode() and borderWidth() is taken from a TreeMapWidget. + * + * If you want more flexibility, reimplement TreeMapItem and + * override the corresponding methods. For dynamic creation of child + * items on demand, reimplement children(). + */ +class TreeMapItem: public StoredDrawParams +{ +public: + + /** + * Split direction for nested areas: + * AlwaysBest: Choose split direction for every subitem according to + * longest side of rectangle left for drawing + * Best: Choose split direction for all subitems of an area + * depending on longest side + * HAlternate: Horizontal at top; alternate direction on depth step + * VAlternate: Vertical at top; alternate direction on depth step + * Horizontal: Always horizontal split direction + * Vertical: Always vertical split direction + */ + enum SplitMode { Bisection, Columns, Rows, + AlwaysBest, Best, + HAlternate, VAlternate, + Horizontal, Vertical }; + + TreeMapItem(TreeMapItem* parent = 0, double value = 1.0 ); + TreeMapItem(TreeMapItem* parent, double value, + QString text1, QString text2 = QString::null, + QString text3 = QString::null, QString text4 = QString::null); + virtual ~TreeMapItem(); + + bool isChildOf(TreeMapItem*); + + TreeMapItem* commonParent(TreeMapItem* item); + + // force a redraw of this item + void redraw(); + + // delete all children + void clear(); + + // force new child generation & refresh + void refresh(); + + // call in a reimplemented items() method to check if already called + // after a clear(), this will return false + bool initialized(); + + /** + * Adds an item to a parent. + * When no sorting is used, the item is appended (drawn at bottom). + * This is only needed if the parent was not already specified in the + * construction of the item. + */ + void addItem(TreeMapItem*); + + /** + * Returns a list of text strings of specified text number, + * from root up to this item. + */ + QStringList path(int) const; + + /** + * Depth of this item. This is the distance to root. + */ + int depth() const; + + /** + * Parent Item + */ + TreeMapItem* parent() const { return _parent; } + + /** + * Temporary rectangle used for drawing this item the last time. + * This is internally used to map from a point to an item. + */ + void setItemRect(const QRect& r) { _rect = r; } + void clearItemRect(); + const QRect& itemRect() const { return _rect; } + int width() const { return _rect.width(); } + int height() const { return _rect.height(); } + + /** + * Temporary rectangle list of free space of this item. + * Used internally to enable tooltip. + */ + void clearFreeRects(); + QPtrList<QRect>* freeRects() const { return _freeRects; } + void addFreeRect(const QRect& r); + + /** + * Temporary child item index of the child that was current() recently. + */ + int index() const { return _index; } + void setIndex(int i) { _index = i; } + + + /** + * TreeMap widget this item is put in. + */ + TreeMapWidget* widget() const { return _widget; } + + void setParent(TreeMapItem* p); + void setWidget(TreeMapWidget* w) { _widget = w; } + void setSum(double s) { _sum = s; } + void setValue(double s) { _value = s; } + + virtual double sum() const; + virtual double value() const; + // replace "Default" position with setting from TreeMapWidget + virtual Position position(int) const; + virtual const QFont& font() const; + virtual bool isMarked(int) const; + + virtual int borderWidth() const; + + /** + * Returns the text number after that sorting is done or + * -1 for no sorting, -2 for value() sorting (default). + * If ascending != 0, a bool value is written at that location + * to indicate if sorting should be ascending. + */ + virtual int sorting(bool* ascending) const; + + /** + * Set the sorting for child drawing. + * + * Default is no sorting: <textNo> = -1 + * For value() sorting, use <textNo> = -2 + * + * For fast sorting, set this to -1 before child insertions and call + * again after inserting all children. + */ + void setSorting(int textNo, bool ascending = true); + + /** + * Resort according to the already set sorting. + * + * This has to be done if the sorting base changes (e.g. text or values + * change). If this is only true for the children of this item, you can + * set the recursive parameter to false. + */ + void resort(bool recursive = true); + + virtual SplitMode splitMode() const; + virtual int rtti() const; + // not const as this can create children on demand + virtual TreeMapItemList* children(); + +protected: + TreeMapItemList* _children; + double _sum, _value; + +private: + TreeMapWidget* _widget; + TreeMapItem* _parent; + + int _sortTextNo; + bool _sortAscending; + + // temporary layout + QRect _rect; + QPtrList<QRect>* _freeRects; + int _depth; + + // temporary self value (when using level skipping) + double _unused_self; + + // index of last active subitem + int _index; +}; + + +/** + * Class for visualization of a metric of hierarchically + * nested items as 2D areas. + */ +class TreeMapWidget: public QWidget +{ + Q_OBJECT + +public: + + /** + * Same as in QListBox/QListView + */ + enum SelectionMode { Single, Multi, Extended, NoSelection }; + + /* The widget becomes owner of the base item */ + TreeMapWidget(TreeMapItem* base, QWidget* parent=0, const char* name=0); + ~TreeMapWidget(); + + /** + * Returns the TreeMapItem filling out the widget space + */ + TreeMapItem* base() const { return _base; } + + /** + * Returns a reference to the current widget font. + */ + const QFont& currentFont() const; + + /** + * Returns the area item at position x/y, independent from any + * maxSelectDepth setting. + */ + TreeMapItem* item(int x, int y) const; + + /** + * Returns the nearest item with a visible area; this + * can be the given item itself. + */ + TreeMapItem* visibleItem(TreeMapItem*) const; + + /** + * Returns the item possible for selection. this returns the + * given item itself or a parent thereof, + * depending on setting of maxSelectDepth(). + */ + TreeMapItem* possibleSelection(TreeMapItem*) const; + + /** + * Selects or unselects an item. + * In multiselection mode, the constrain that a selected item + * has no selected children or parents stays true. + */ + void setSelected(TreeMapItem*, bool selected = true); + + /** + * Switches on the marking <markNo>. Marking 0 switches off marking. + * This is mutually exclusive to selection, and is automatically + * switched off when selection is changed (also by the user). + * Marking is visually the same as selection, and is based on + * TreeMapItem::isMarked(<markNo>). + * This enables to programmatically show multiple selected items + * at once even in single selection mode. + */ + void setMarked(int markNo = 1, bool redraw = true); + + /** + * Clear selection of all selected items which are children of + * parent. When parent == 0, clears whole selection + * Returns true if selection changed. + */ + bool clearSelection(TreeMapItem* parent = 0); + + /** + * Selects or unselects items in a range. + * This is needed internally for Shift-Click in Extented mode. + * Range means for a hierarchical widget: + * - select/unselect i1 and i2 according selected + * - search common parent of i1 and i2, and select/unselect the + * range of direct children between but excluding the child + * leading to i1 and the child leading to i2. + */ + void setRangeSelection(TreeMapItem* i1, + TreeMapItem* i2, bool selected); + + /** + * Sets the current item. + * The current item is mainly used for keyboard navigation. + */ + void setCurrent(TreeMapItem*, bool kbd=false); + + /** + * Set the maximal depth a selected item can have. + * If you try to select a item with higher depth, the ancestor holding + * this condition is used. + * + * See also possibleSelection(). + */ + void setMaxSelectDepth(int d) { _maxSelectDepth = d; } + + + void setSelectionMode(SelectionMode m) { _selectionMode = m; } + + /** + * for setting/getting global split direction + */ + void setSplitMode(TreeMapItem::SplitMode m); + TreeMapItem::SplitMode splitMode() const; + // returns true if string was recognized + bool setSplitMode(QString); + QString splitModeString() const; + + + /* + * Shading of rectangles enabled ? + */ + void setShadingEnabled(bool s); + bool isShadingEnabled() const { return _shading; } + + /* Setting for a whole depth level: draw 3D frame (default) or solid */ + void drawFrame(int d, bool b); + bool drawFrame(int d) const { return (d<4)?_drawFrame[d]:true; } + + /* Setting for a whole depth level: draw items (default) or transparent */ + void setTransparent(int d, bool b); + bool isTransparent(int d) const { return (d<4)?_transparent[d]:false; } + + /** + * Items usually have a size proportional to their value(). + * With <width>, you can give the minimum width + * of the resulting rectangle to still be drawn. + * For space not used because of to small items, you can specify + * with <reuseSpace> if the background should shine through or + * the space will be used to enlarge the next item to be drawn + * at this level. + */ + void setVisibleWidth(int width, bool reuseSpace = false); + + /** + * If a children value() is almost the parents sum(), + * it can happen that the border to be drawn for visibilty of + * nesting relations takes to much space, and the + * parent/child size relation can not be mapped to a correct + * area size relation. + * + * Either + * (1) Ignore the incorrect drawing, or + * (2) Skip drawing of the parent level alltogether. + */ + void setSkipIncorrectBorder(bool enable = true); + bool skipIncorrectBorder() const { return _skipIncorrectBorder; } + + /** + * Maximal nesting depth + */ + void setMaxDrawingDepth(int d); + int maxDrawingDepth() const { return _maxDrawingDepth; } + + /** + * Minimal area for rectangles to draw + */ + void setMinimalArea(int area); + int minimalArea() const { return _minimalArea; } + + /* defaults for text attributes */ + QString defaultFieldType(int) const; + QString defaultFieldStop(int) const; + bool defaultFieldVisible(int) const; + bool defaultFieldForced(int) const; + DrawParams::Position defaultFieldPosition(int) const; + + /** + * Set the type name of a field. + * This is important for the visualization menu generated + * with visualizationMenu() + */ + void setFieldType(int, QString); + QString fieldType(int) const; + + /** + * Stop drawing at item with name + */ + void setFieldStop(int, QString); + QString fieldStop(int) const; + + /** + * Should the text with number textNo be visible? + * This is only done if remaining space is enough to allow for + * proportional size constrains. + */ + void setFieldVisible(int, bool); + bool fieldVisible(int) const; + + /** + * Should the drawing of the name into the rectangle be forced? + * This enables drawing of the name before drawing subitems, and + * thus destroys proportional constrains. + */ + void setFieldForced(int, bool); + bool fieldForced(int) const; + + /** + * Set the field position in the area. See TreeMapItem::Position + */ + void setFieldPosition(int, DrawParams::Position); + DrawParams::Position fieldPosition(int) const; + void setFieldPosition(int, QString); + QString fieldPositionString(int) const; + + /** + * Do we allow the texts to be rotated by 90 degrees for better fitting? + */ + void setAllowRotation(bool); + bool allowRotation() const { return _allowRotation; } + + void setBorderWidth(int w); + int borderWidth() const { return _borderWidth; } + + /** + * Save/restore options. + */ + void saveOptions(KConfigGroup*, QString prefix = QString::null); + void restoreOptions(KConfigGroup*, QString prefix = QString::null); + + /** + * These functions populate given popup menus. + * The added items are already connected to handlers. + * + * The int is the menu id where to start for the items (100 IDs reserved). + */ + void addSplitDirectionItems(QPopupMenu*, int); + void addSelectionItems(QPopupMenu*, int, TreeMapItem*); + void addFieldStopItems(QPopupMenu*, int, TreeMapItem*); + void addAreaStopItems(QPopupMenu*, int, TreeMapItem*); + void addDepthStopItems(QPopupMenu*, int, TreeMapItem*); + void addVisualizationItems(QPopupMenu*, int); + + TreeMapWidget* widget() { return this; } + TreeMapItem* current() const { return _current; } + TreeMapItemList selection() const { return _selection; } + bool isSelected(TreeMapItem* i) const; + int maxSelectDepth() const { return _maxSelectDepth; } + SelectionMode selectionMode() const { return _selectionMode; } + + /** + * Return tooltip string to show for a item (can be rich text) + * Default implementation gives lines with "text0 (text1)" going to root. + */ + virtual QString tipString(TreeMapItem* i) const; + + /** + * Redraws an item with all children. + * This takes changed values(), sums(), colors() and text() into account. + */ + void redraw(TreeMapItem*); + void redraw() { redraw(_base); } + + /** + * Resort all TreeMapItems. See TreeMapItem::resort(). + */ + void resort() { _base->resort(true); } + + // internal + void drawTreeMap(); + + // used internally when items are destroyed + void deletingItem(TreeMapItem*); + +protected slots: + void splitActivated(int); + void selectionActivated(int); + void fieldStopActivated(int); + void areaStopActivated(int); + void depthStopActivated(int); + void visualizationActivated(int); + +signals: + void selectionChanged(); + void selectionChanged(TreeMapItem*); + + /** + * This signal is emitted if the current item changes. + * If the change is done because of keyboard navigation, + * the <kbd> is set to true + */ + void currentChanged(TreeMapItem*, bool keyboard); + void clicked(TreeMapItem*); + void returnPressed(TreeMapItem*); + void doubleClicked(TreeMapItem*); + void rightButtonPressed(TreeMapItem*, const QPoint &); + void contextMenuRequested(TreeMapItem*, const QPoint &); + +protected: + void mousePressEvent( QMouseEvent * ); + void contextMenuEvent( QContextMenuEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void mouseDoubleClickEvent( QMouseEvent * ); + void keyPressEvent( QKeyEvent* ); + void paintEvent( QPaintEvent * ); + void resizeEvent( QResizeEvent * ); + void showEvent( QShowEvent * ); + void fontChange( const QFont& ); + +private: + TreeMapItemList diff(TreeMapItemList&, TreeMapItemList&); + // returns true if selection changed + TreeMapItem* setTmpSelected(TreeMapItem*, bool selected = true); + TreeMapItem* setTmpRangeSelection(TreeMapItem* i1, + TreeMapItem* i2, bool selected); + bool isTmpSelected(TreeMapItem* i); + + void drawItem(QPainter* p, TreeMapItem*); + void drawItems(QPainter* p, TreeMapItem*); + bool horizontal(TreeMapItem* i, const QRect& r); + void drawFill(TreeMapItem*,QPainter* p, QRect& r); + void drawFill(TreeMapItem*,QPainter* p, QRect& r, + TreeMapItemListIterator it, int len, bool goBack); + bool drawItemArray(QPainter* p, TreeMapItem*, QRect& r, double, + TreeMapItemListIterator it, int len, bool); + bool resizeAttr(int); + + TreeMapItem* _base; + TreeMapItem *_current, *_pressed, *_lastOver, *_oldCurrent; + TreeMapTip* _tip; + int _maxSelectDepth, _maxDrawingDepth; + + // attributes for field, per textNo + struct FieldAttr { + QString type, stop; + bool visible, forced; + DrawParams::Position pos; + }; + QValueVector<FieldAttr> _attr; + + SelectionMode _selectionMode; + TreeMapItem::SplitMode _splitMode; + int _visibleWidth, _stopArea, _minimalArea, _borderWidth; + bool _reuseSpace, _skipIncorrectBorder, _drawSeparators, _shading; + bool _allowRotation; + bool _transparent[4], _drawFrame[4]; + TreeMapItem * _needsRefresh; + TreeMapItemList _selection; + int _markNo; + + // for the context menus: start IDs + int _splitID, _selectionID, _visID; + int _fieldStopID, _areaStopID, _depthStopID; + TreeMapItem* _menuItem; + + // temporary selection while dragging, used for drawing + // most of the time, _selection == _tmpSelection + TreeMapItemList _tmpSelection; + bool _inShiftDrag, _inControlDrag; + + // temporary widget font metrics while drawing + QFont _font; + int _fontHeight; + + // back buffer pixmap + QPixmap _pixmap; +}; + +#endif diff --git a/kcachegrind/kcachegrind/utils.cpp b/kcachegrind/kcachegrind/utils.cpp new file mode 100644 index 00000000..618fce7b --- /dev/null +++ b/kcachegrind/kcachegrind/utils.cpp @@ -0,0 +1,483 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Utility classes for KCachegrind + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_MMAP +#include <unistd.h> +#include <sys/mman.h> +#endif + +#include <qfile.h> +#include <errno.h> + +#include "utils.h" + + +// class FixString + +FixString::FixString(const char* str, int len) +{ + _str = str; + _len = len; +} + +bool FixString::stripFirst(char& c) +{ + if (!_len) { + c = 0; + return false; + } + + c = *_str; + _str++; + _len--; + return true; + } + +bool FixString::stripPrefix(const char* p) +{ + if (_len == 0) return false; + if (!p || (*p != *_str)) return false; + + const char* s = _str+1; + int l = _len-1; + p++; + while(*p) { + if (l==0) return false; + if (*s != *p) return false; + p++; + s++; + l--; + } + _str = s; + _len = l; + return true; +} + + +// this parses hexadecimal (with prefix '0x' too) +bool FixString::stripUInt(unsigned int& v, bool stripSpaces) +{ + if (_len==0) { + v = 0; + return false; + } + + char c = *_str; + if (c<'0' || c>'9') { + v = 0; + return false; + } + + v = c-'0'; + const char* s = _str+1; + int l = _len-1; + c = *s; + + if ((l>0) && (c == 'x') && (v==0)) { + // hexadecimal + s++; + c = *s; + l--; + + while(l>0) { + if (c>='0' && c<='9') + v = 16*v + (c-'0'); + else if (c>='a' && c<='f') + v = 16*v + 10 + (c-'a'); + else if (c>='A' && c<='F') + v = 16*v + 10 + (c-'A'); + else + break; + s++; + c = *s; + l--; + } + } + else { + // decimal + + while(l>0) { + if (c<'0' || c>'9') break; + v = 10*v + (c-'0'); + s++; + c = *s; + l--; + } + } + + if (stripSpaces) + while(l>0) { + if (c != ' ') break; + s++; + c = *s; + l--; + } + + _str = s; + _len = l; + return true; +} + + +void FixString::stripSurroundingSpaces() +{ + if (_len==0) return; + + // leading spaces + while((_len>0) && (*_str==' ')) { + _len--; + _str++; + } + + // trailing spaces + while((_len>0) && (_str[_len-1]==' ')) { + _len--; + } +} + +void FixString::stripSpaces() +{ + while((_len>0) && (*_str==' ')) { + _len--; + _str++; + } +} + +bool FixString::stripName(FixString& s) +{ + if (_len==0) return false; + + // first char has to be a letter or "_" + if (!QChar(*_str).isLetter() && (*_str != '_')) return false; + + int newLen = 1; + const char* newStr = _str; + + _str++; + _len--; + + while(_len>0) { + if (!QChar(*_str).isLetterOrNumber() + && (*_str != '_')) break; + + newLen++; + _str++; + _len--; + } + + s.set(newStr, newLen); + return true; +} + +FixString FixString::stripUntil(char c) +{ + if (_len == 0) return FixString(); + + const char* newStr = _str; + int newLen = 0; + + while(_len>0) { + if (*_str == c) { + _str++; + _len--; + break; + } + + _str++; + _len--; + newLen++; + } + return FixString(newStr, newLen); +} + +bool FixString::stripUInt64(uint64& v, bool stripSpaces) +{ + if (_len==0) { + v = 0; + return false; + } + + char c = *_str; + if (c<'0' || c>'9') { + v = 0; + return false; + } + + v = c-'0'; + const char* s = _str+1; + int l = _len-1; + c = *s; + + if ((l>0) && (c == 'x') && (v==0)) { + // hexadecimal + s++; + c = *s; + l--; + + while(l>0) { + if (c>='0' && c<='9') + v = 16*v + (c-'0'); + else if (c>='a' && c<='f') + v = 16*v + 10 + (c-'a'); + else if (c>='A' && c<='F') + v = 16*v + 10 + (c-'A'); + else + break; + s++; + c = *s; + l--; + } + } + else { + // decimal + while(l>0) { + if (c<'0' || c>'9') break; + v = 10*v + (c-'0'); + s++; + c = *s; + l--; + } + } + + if (stripSpaces) + while(l>0) { + if (c != ' ') break; + s++; + c = *s; + l--; + } + + _str = s; + _len = l; + return true; +} + + +bool FixString::stripInt64(int64& v, bool stripSpaces) +{ + if (_len==0) { + v = 0; + return false; + } + + char c = *_str; + if (c<'0' || c>'9') { + v = 0; + return false; + } + + v = c-'0'; + const char* s = _str+1; + int l = _len-1; + c = *s; + + if ((l>0) && (c == 'x') && (v==0)) { + // hexadecimal + s++; + c = *s; + l--; + + while(l>0) { + if (c>='0' && c<='9') + v = 16*v + (c-'0'); + else if (c>='a' && c<='f') + v = 16*v + 10 + (c-'a'); + else if (c>='A' && c<='F') + v = 16*v + 10 + (c-'A'); + else + break; + s++; + c = *s; + l--; + } + } + else { + // decimal + + while(l>0) { + if (c<'0' || c>'9') break; + v = 10*v + (c-'0'); + s++; + c = *s; + l--; + } + } + + if (stripSpaces) + while(l>0) { + if (c != ' ') break; + s++; + c = *s; + l--; + } + + _str = s; + _len = l; + return true; +} + + + +// class FixFile + +FixFile::FixFile(QFile* file) +{ + if (!file) { + _len = 0; + _currentLeft = 0; + _openError = true; + return; + } + + _filename = file->name(); + if (!file->isOpen() && !file->open( IO_ReadOnly ) ) { + qWarning( "%s: %s", (const char*) QFile::encodeName(_filename), + strerror( errno ) ); + _len = 0; + _currentLeft = 0; + _openError = true; + return; + } + + _openError = false; + _used_mmap = false; + +#ifdef HAVE_MMAP + char *addr = 0; + size_t len = file->size(); + if (len>0) addr = (char *) mmap( addr, len, + PROT_READ, MAP_PRIVATE, + file->handle(), 0 ); + if (addr && (addr != MAP_FAILED)) { + // mmap succeeded + _base = addr; + _len = len; + _used_mmap = true; + + if (0) qDebug("Mapped '%s'", _filename.ascii()); + } else { +#endif // HAVE_MMAP + // try reading the data into memory instead + _data = file->readAll(); + _base = _data.data(); + _len = _data.size(); +#ifdef HAVE_MMAP + } +#endif // HAVE_MMAP + + _current = _base; + _currentLeft = _len; +} + +FixFile::~FixFile() +{ + // if the file was read into _data, it will be deleted automatically + +#ifdef HAVE_MMAP + if (_used_mmap) { + if (0) qDebug("Unmapping '%s'", _filename.ascii()); + if (munmap(_base, _len) != 0) + qWarning( "munmap: %s", strerror( errno ) ); + } +#endif // HAVE_MMAP +} + +bool FixFile::nextLine(FixString& str) +{ + if (_currentLeft == 0) return false; + + unsigned left = _currentLeft; + char* current = _current; + + while(left>0) { + if (*current == 0 || *current == '\n') break; + current++; + left--; + } + + if (0) { + char tmp[200]; + int l = _currentLeft-left; + if (l>199) l = 199; + strncpy(tmp, _current, l); + tmp[l] = 0; + qDebug("[FixFile::nextLine] At %d, len %d: '%s'", + _current - _base, _currentLeft-left, tmp); + } + + str.set(_current, _currentLeft-left); + + if (*current == '\n') { + current++; + left--; + } + _current = current; + _currentLeft = left; + + return true; +} + +bool FixFile::setCurrent(unsigned pos) +{ + if (pos > _len) return false; + + _current = _base + pos; + _currentLeft = _len - pos; + return true; +} + + +#if 0 + +// class AppendList + + +AppendList::AppendList() +{ + _next = 0; + _current = 0; + _last = 0; + + _count = 0; + _currentIndex = 0; + _lastIndex = 0; + _autoDelete = false; +} + + +void AppendList::clear() +{ + int count = _count; + int i; + + if (count <= firstLen) { + if (_autoDelete) + for (i=0;i<count;i++) + delete _first[i]; + } +} + +#endif diff --git a/kcachegrind/kcachegrind/utils.h b/kcachegrind/kcachegrind/utils.h new file mode 100644 index 00000000..2831c1ce --- /dev/null +++ b/kcachegrind/kcachegrind/utils.h @@ -0,0 +1,164 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Utility classes for KCachegrind + */ + +#ifndef UTILS_H +#define UTILS_H + +#include <qstring.h> + +class QFile; + +typedef unsigned long long uint64; +typedef long long int64; + +/** + * A simple, constant string class + * + * For use with zero-copy strings from mapped files. + */ +class FixString { + + public: + // constructor for an invalid string + FixString() { _len = 0; _str = 0; } + + /** + * FixString never does a deep copy! You have to make sure that + * the string starting at the char pointer is valid trough the + * lifetime of FixString. + */ + FixString(const char*, int len); + + int len() { return _len; } + const char* ascii() { return _str; } + bool isEmpty() { return _len == 0; } + bool isValid() { return _str != 0; } + + // sets <c> to first character and returns true if length >0 + bool first(char& c) + { if (_len==0) return false; c=_str[0]; return true; } + + void set(const char* s, int l) { _str=s; _len=l; } + bool stripFirst(char&); + bool stripPrefix(const char*); + + /** + * Strip leading and trailing spaces + */ + void stripSurroundingSpaces(); + + /** + * Strip leading spaces + */ + void stripSpaces(); + + /** + * Strip name: [A-Za-z_][0-9A_Za-z_]* + */ + bool stripName(FixString&); + + /** + * Strip string until char appears or end. Strips char, too. + */ + FixString stripUntil(char); + + bool stripUInt(uint&, bool stripSpaces = true); + bool stripUInt64(uint64&, bool stripSpaces = true); + bool stripInt64(int64&, bool stripSpaces = true); + + operator QString() const + { return QString::fromLatin1(_str,_len); } + + private: + const char* _str; + int _len; +}; + + +/** + * A class for fast line by line reading of a read-only ASCII file + */ +class FixFile { + + public: + FixFile(QFile*); + ~FixFile(); + + /** + * Read next line into <str>. Returns false on error or EOF. + */ + bool nextLine(FixString& str); + bool exists() { return !_openError; } + unsigned len() { return _len; } + unsigned current() { return _current - _base; } + bool setCurrent(unsigned pos); + void rewind() { setCurrent(0); } + + private: + char *_base, *_current; + QByteArray _data; + unsigned _len, _currentLeft; + bool _used_mmap, _openError; + QString _filename; +}; + + +/** + * A list of pointers, only able to append items. + * Optimized for speed, not space. + */ +template<class type> +class AppendList { + + public: + AppendList(); + ~AppendList() { clear(); } + + void setAutoDelete(bool); + void clear(); + void append(const type*); + + unsigned count() const { return _count; } + unsigned containsRef(const type*) const; + + type* current(); + type* first(); + type* next(); + + private: + static const int firstLen = 8; + static const int maxLen = 256; + + struct AppendListChunk { + int size; + struct AppendListChunk* next; + type* data[1]; + }; + + struct AppendListChunk *_next, *_current, *_last; + int _count, _currentIndex, _lastIndex; + bool _autoDelete; + type* _first[firstLen]; +}; + + +#endif diff --git a/kcachegrind/kcachegrind/x-kcachegrind.desktop b/kcachegrind/kcachegrind/x-kcachegrind.desktop new file mode 100644 index 00000000..671f89ac --- /dev/null +++ b/kcachegrind/kcachegrind/x-kcachegrind.desktop @@ -0,0 +1,44 @@ +[Desktop Entry] +Comment=Cachegrind/Callgrind Profile Dump +Comment[ca]=Resultat del anàlisis de Cachegrind/Callgring +Comment[cs]=Data profilace Cachegrind/Callgrind +Comment[cy]=Tomen Proffil Cachegrind/Callgrind +Comment[da]=Cachegrind/Callgrind profile-dump +Comment[de]=Cachegrind/Callgrind Profil-Ausgabe +Comment[el]=Αποτύπωση προφίλ Cachegrind/Callgrind +Comment[es]=Resultado de análisis de Cachegrind/Callgring +Comment[et]=Cachegrind/Callgrind profileerimistõmmis +Comment[eu]=Cachegrind/Callgrind profil iraulketa +Comment[fa]=تخلیۀ Profile Cachegrind/Callgrind +Comment[fi]=Cachegrind/Callgrind-profiilivedos +Comment[fr]=Dépôt de profil Cachegrind / Callgrind +Comment[gl]=Resultado da análise de Cachegrind/Callgrind +Comment[hi]=केश-ग्रिंड/काल-ग्रिंड प्रोफ़ाइल डम्प +Comment[hu]=Cachegrind/Callgrind teljesítményprofil-fájl +Comment[is]=Niðurstaða afkastakönnunar á Cachegrind/Callgrind +Comment[it]=Dump del profilo di Cachegrind/Callgrind +Comment[ja]=Callgrind/Callgrind プロファイルダンプ +Comment[ka]=Cachegrind/Callgrind პროფილის დამპი +Comment[kk]=Cachegrind/Callgrind профилінің дампы +Comment[nds]=Cachegrind/Callgrind-Profilutgaav +Comment[ne]=Cachegrind/Callgrind प्रोफाइल डम्प +Comment[nl]=Cachegrind/Callgrind Profieldump +Comment[nn]=Cachegrind/Callgrind-profildump +Comment[pl]=Zrzut profilowania Cachegrind/Callgrind +Comment[pt]=Resultado da Análise do Cachegrind/Callgrind +Comment[pt_BR]=Depósito de Perfil Cachegrind/Callgrind +Comment[ru]=Дамп профилирования Cachegrind/Callgrind +Comment[sk]=Výpis volaní Cachegrind/Callgrind +Comment[sr]=Cachegrind-ов/Callgrind-ов избачај профила +Comment[sr@Latn]=Cachegrind-ov/Callgrind-ov izbačaj profila +Comment[sv]=Profileringsdump från Cachegrind/Callgrind +Comment[ta]=இடைமாற்றகட்டம்/ அழைப்பு கட்டம் விவரக்குறி திணிப்பு +Comment[tg]=Дампи профилкунии Cachegrind/Callgrind +Comment[uk]=Звалювання профілювання Cachegrind/Callgrind +Comment[zh_CN]=Cachegrind/Callgrind 配置文件转存 +Comment[zh_TW]=Cachegrind/Callgrind 分析資料傾印 +DefaultApp=kcachegrind +Icon=kcachegrind +Type=MimeType +MimeType=application/x-kcachegrind +Patterns=cachegrind.out*;callgrind.out* diff --git a/kcachegrind/pics/Makefile.am b/kcachegrind/pics/Makefile.am new file mode 100644 index 00000000..a899c7b2 --- /dev/null +++ b/kcachegrind/pics/Makefile.am @@ -0,0 +1,3 @@ +kcachegrindicondir = $(kde_datadir)/kcachegrind/icons +kcachegrindicon_ICON = AUTO +SUBDIRS = hicolor diff --git a/kcachegrind/pics/hicolor/Makefile.am b/kcachegrind/pics/hicolor/Makefile.am new file mode 100644 index 00000000..be8b0c26 --- /dev/null +++ b/kcachegrind/pics/hicolor/Makefile.am @@ -0,0 +1,2 @@ +kcachegrindicondir = $(kde_datadir)/kcachegrind/icons +kcachegrindicon_ICON = AUTO diff --git a/kcachegrind/pics/hicolor/hi16-action-fromrec.png b/kcachegrind/pics/hicolor/hi16-action-fromrec.png Binary files differnew file mode 100644 index 00000000..a5cb430d --- /dev/null +++ b/kcachegrind/pics/hicolor/hi16-action-fromrec.png diff --git a/kcachegrind/pics/hicolor/hi16-action-percent.png b/kcachegrind/pics/hicolor/hi16-action-percent.png Binary files differnew file mode 100644 index 00000000..7a4ba47e --- /dev/null +++ b/kcachegrind/pics/hicolor/hi16-action-percent.png diff --git a/kcachegrind/pics/hicolor/hi16-action-recrec.png b/kcachegrind/pics/hicolor/hi16-action-recrec.png Binary files differnew file mode 100644 index 00000000..ec11bfa4 --- /dev/null +++ b/kcachegrind/pics/hicolor/hi16-action-recrec.png diff --git a/kcachegrind/pics/hicolor/hi16-action-torec.png b/kcachegrind/pics/hicolor/hi16-action-torec.png Binary files differnew file mode 100644 index 00000000..c092c018 --- /dev/null +++ b/kcachegrind/pics/hicolor/hi16-action-torec.png diff --git a/kcachegrind/pics/hicolor/hi22-action-percent.png b/kcachegrind/pics/hicolor/hi22-action-percent.png Binary files differnew file mode 100644 index 00000000..c64a3785 --- /dev/null +++ b/kcachegrind/pics/hicolor/hi22-action-percent.png diff --git a/kcachegrind/pics/hicolor/hi32-action-percent.png b/kcachegrind/pics/hicolor/hi32-action-percent.png Binary files differnew file mode 100644 index 00000000..e876c30d --- /dev/null +++ b/kcachegrind/pics/hicolor/hi32-action-percent.png diff --git a/kcachegrind/tests/cg-badcompression1 b/kcachegrind/tests/cg-badcompression1 new file mode 100644 index 00000000..6076bf92 --- /dev/null +++ b/kcachegrind/tests/cg-badcompression1 @@ -0,0 +1,17 @@ +# Test with bad callgrind format +# Expected: +# :13 - Redefinition of compressed file index 2 (was 'file1.c') to '' +# :14 - Redefinition of compressed function index 1 (was 'main') to 'main2' +# :16 - Undefined compressed function index 2 +# :16 - Invalid function, setting to unknown + +events: Ir + +fl=(2) file1.c +fn=(1) main +10 9 +fl=(2 ) +fn=(1) main2 +11 1 +fn=(2) +12 1 diff --git a/kcachegrind/tests/cg-badcostline1 b/kcachegrind/tests/cg-badcostline1 new file mode 100644 index 00000000..224ff670 --- /dev/null +++ b/kcachegrind/tests/cg-badcostline1 @@ -0,0 +1,11 @@ +# Test with bad callgrind format +# Expected: +# :10 - ignored garbage at end of cost line ('30') +# :11 - ignored garbage at end of cost line ('hello') + +events: Ir + +fn=main +10 20 30 +11 hello +12 10 diff --git a/kcachegrind/tests/cg-badposition b/kcachegrind/tests/cg-badposition new file mode 100644 index 00000000..1be582c7 --- /dev/null +++ b/kcachegrind/tests/cg-badposition @@ -0,0 +1,15 @@ +# Test with bad callgrind format +# Expected: +# :11 - Negative line number -20 +# :12 - Garbage at end of cost line ('a 21') +# :13 - Negative line number -91 +# :15 - Invalid line 'aa 40' + +events: Ir + +fn=main +-20 1 +9a 21 +-100 20 +0x9a 30 +aa 40 diff --git a/kcachegrind/version.h.in b/kcachegrind/version.h.in new file mode 100644 index 00000000..21f758c7 --- /dev/null +++ b/kcachegrind/version.h.in @@ -0,0 +1 @@ +#define KCACHEGRIND_VERSION "@KCACHEGRIND_VERSION@" |