diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e9f8ce4..a407f954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,4 +27,5 @@ macro_optional_add_subdirectory (kfilereplace) # NOTE: you will need kdoctools from kdelibs ported for the help KIO slave macro_optional_add_subdirectory (khelpcenter) macro_optional_add_subdirectory (kaffeine) -macro_optional_add_subdirectory (kamoso) \ No newline at end of file +macro_optional_add_subdirectory (kamoso) +macro_optional_add_subdirectory (kcachegrind) \ No newline at end of file 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 diff --git a/kcachegrind/CMakeLists.txt b/kcachegrind/CMakeLists.txt new file mode 100644 index 00000000..a7b1a1e1 --- /dev/null +++ b/kcachegrind/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.6) + +project(kcachegrind) + +find_package(KDE4 4.14.3 REQUIRED) +include(KDE4Defaults) +include(MacroLibrary) + +set( CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS} -DQT_STRICT_ITERATORS ) +add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES} ) + +set( KCACHEGRIND_VERSION "0.7.4kde") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h ) + +IF(NOT WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kcachegrind.spec.cmake ${CMAKE_CURRENT_BINARY_DIR}/kcachegrind.spec ) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kcachegrind.lsm.cmake ${CMAKE_CURRENT_BINARY_DIR}/kcachegrind.lsm ) +ENDIF(NOT WIN32) + +macro_additional_clean_files( + ${CMAKE_CURRENT_BINARY_DIR}/version.h + ) +IF(NOT WIN32) +macro_additional_clean_files( + ${CMAKE_CURRENT_BINARY_DIR}/kcachegrind.lsm + ${CMAKE_CURRENT_BINARY_DIR}/kcachegrind.spec + ) +ENDIF(NOT WIN32) + +add_subdirectory( libcore ) +add_subdirectory( cgview ) +add_subdirectory( libviews ) +add_subdirectory( kcachegrind ) +add_subdirectory( qcachegrind ) +add_subdirectory( pics ) +add_subdirectory( converters ) diff --git a/kcachegrind/COPYING b/kcachegrind/COPYING new file mode 100644 index 00000000..c13faf0d --- /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. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + + Copyright (C) + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + , 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/COPYING.DOC b/kcachegrind/COPYING.DOC new file mode 100644 index 00000000..a988da5a --- /dev/null +++ b/kcachegrind/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document 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. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation 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. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/kcachegrind/ChangeLog b/kcachegrind/ChangeLog new file mode 100644 index 00000000..d443b016 --- /dev/null +++ b/kcachegrind/ChangeLog @@ -0,0 +1,6 @@ +2011/11/14 + * KCachegrind: use callgrind_control to request a dump. + The old way will not work with Callgrind in VG > 3.7 + +2011/9/23 + * Compiles without Qt3 support diff --git a/kcachegrind/INSTALL b/kcachegrind/INSTALL new file mode 100644 index 00000000..d618de38 --- /dev/null +++ b/kcachegrind/INSTALL @@ -0,0 +1,5 @@ +For build instructions see README. + +More details for KDE build: +http://techbase.kde.org/Getting_Started/Build/KDE4 + diff --git a/kcachegrind/KnownBugs b/kcachegrind/KnownBugs new file mode 100644 index 00000000..9c397080 --- /dev/null +++ b/kcachegrind/KnownBugs @@ -0,0 +1,23 @@ +Known Bugs + +* Cost values do not update after the formula of a derived event type + was changed. One has to change to another event type and back (9/2011) + + Reason: For derived event types, class ProfileCostArray caches the cost, + calculated from the formula, as this is used multiple times. The cache + is only invalidated when the cost of another event type is requested. + + Possible fix: Invalidate the cache of every cost item after a formula + change. Tricky and takes time. Better allocate a new event type and + remove old? + +* KCachegrind allows two event types to be shown. If they are set to the + same, and this event type is removed, KCachegrind crashes. + + Reason: By design, only one of the event types can be changed at a time. + Still, on a change, the cost for the other event type is updated. As + the event type is already deleted, the crash happens. + + Possible fix: delete event type after selecting another event type before. + Tricky, as change actions are delayed by a timer. + Another fix: do not allow the two event types to be the same. diff --git a/kcachegrind/Mainpage.dox b/kcachegrind/Mainpage.dox new file mode 100644 index 00000000..d7e9234b --- /dev/null +++ b/kcachegrind/Mainpage.dox @@ -0,0 +1,16 @@ +/** @mainpage KCachegrind +* +* KCachegrind is a browser for data produced by profiling tools. +* +* KCachegrind features various visualization views for a selected function, namely +* - a call-graph view, which shows a section of the call graph around the +* selected function, +* - a tree-map view, which allows nested-call relations to be visualized, +* together with inclusive cost metric for fast visual detection of +* problematic functions, +* - source code and disassembler annotation views, allowing to see details of +* cost related to source lines and assembler instructions. +* +* KCachegrind is part of the KDE SDK package, and it is released at the same time as +* the rest of KDE SDK and KDE. +*/ diff --git a/kcachegrind/Messages.sh b/kcachegrind/Messages.sh new file mode 100644 index 00000000..8241656a --- /dev/null +++ b/kcachegrind/Messages.sh @@ -0,0 +1,6 @@ +#! /bin/sh +(cd kcachegrind;$PREPARETIPS) > tips.cpp +$EXTRACTRC `find kcachegrind -name \*.ui -o -name \*.rc -o -name \*.kcfg` >> rc.cpp +$XGETTEXT rc.cpp `find kcachegrind -name '*.cpp'` tips.cpp -o $podir/kcachegrind.pot +$XGETTEXT_QT `find libcore -name '*.cpp'` `find libviews -name '*.cpp'` -o $podir/kcachegrind_qt.pot +rm -f tips.cpp rc.cpp diff --git a/kcachegrind/NEWS b/kcachegrind/NEWS new file mode 100644 index 00000000..e69de29b diff --git a/kcachegrind/README b/kcachegrind/README new file mode 100644 index 00000000..9883ea12 --- /dev/null +++ b/kcachegrind/README @@ -0,0 +1,108 @@ +KCachegrind / QCachegrind +-======================== + +{K,Q}Cachegrind is a KDE/Qt GUI to visualize profiling data. +It's mainly used as visualization frontend for data measured +by Cachegrind/Callgrind tools from the Valgrind package, but +there are converters for other measurement tools available. + +Features + +* direct support for profiles generated by Cachegrind/Callgrind +* support for arbitrary event types and derived event types +* sorted function list, with grouping according to ELF object/source + file/symbol namespace (such as C++ classes) +* correct handling of recursive cycles (similar to GProf) +* various visualization views for a selected function, such as + - treemap in caller/callee direction + - call graph around function + - source & assembly annotation + + +Hmm. What is stuff good for? +---------------------------- + +Any work in improving the performance of a program should be +started with measuring the performance characteristics of the +optimized binary, using representative input data. This process +is called "Profiling". Any other way for performance optimization +usually just wastes developer time. +Profile measurements show whether optimization is needed at all, +and what improvement can be expected. Further, it pinpoint at +functions and source lines where most time is spent, i.e. where an +improvement has most influence on allover runtime. + +{K,Q}Cachegrind visualizes profile measurement data. Example of an +easy to use profile measurement tool (no source modifications and +recompilation is required, as well as no root access) are the +cache simulators Cachegrind and Callgrind from the Valgrind toolset. +While {K,Q}Cachegrind directly supports the formats of these +profiling tools, converters are available to allow to import data +from other profiling tools, too. + + + +Compilation and Installation +-=========================== + + +QCachegrind +----------- + +Requirements: +* Qt5.x (x >=0) or Qt4.y (y >=5) (earlier versions not tested) +* Any platform supported by Qt (Linux, Windows, Mac OS X, ...) + +Compilation (from base directory): + + qmake; make + +To not build in the source directories, do: + + mkdir build; cd build; qmake ../qcg.pro; make + +The build includes the command line tool "cgview". + +Copy the resulting "qcachegrind" binary from the build directory into +your $PATH. For better desktop integration, it should be enough to +copy the .desktop file and icons into standard places, such as: + + sudo install -m 755 qcachegrind/qcachegrind /usr/local/bin + sudo install -m 644 qcachegrind/qcachegrind.desktop \ + /usr/local/share/applications/ + sudo install -m 644 kcachegrind/hi32-app-kcachegrind.png \ + /usr/local/share/icons/hicolor/32x32/apps/kcachegrind.png + sudo install -m 644 kcachegrind/hi48-app-kcachegrind.png \ + /usr/local/share/icons/hicolor/48x48/apps/kcachegrind.png + + +KCachegrind +------------ + +Requirements: +* KDE 4.3 or higher: kdelibs development packages (libs & headers) +* CMake + +Commands (from base directory): + + cmake .; make; make install + +To not build in the source directories, do: + + mkdir build; cd build; cmake ..; make; make install + +The build also compiles the command line tool "cgview" and "qcachegrind", +the Qt-only version of KCachegrind. However, these are not installed. +If you want to also install qcachegrind, see instructions above. + + +Usage & Help +-=========== + +{K,Q}Cachegrind has detailed "What's this?" help for +each GUI part. For further help, see quick start pages +on kcachegrind.sf.net + + + + Josef Weidendorfer diff --git a/kcachegrind/TODO b/kcachegrind/TODO new file mode 100644 index 00000000..f9246168 --- /dev/null +++ b/kcachegrind/TODO @@ -0,0 +1,94 @@ +TODO/Wishlist Items +=================== + +Support for KCachegrind in Calltree +----------------------------------- + +<<<<<<< TODO +BUGS: +- discarded separated by objname + +======= +>>>>>>> 1.1.2.1 +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 + + +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 + +Assembler view: +* Option for context lines + +Source view: +* Option for context lines +* Implicit jumps (green) + +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 + + diff --git a/kcachegrind/cgview/CMakeLists.txt b/kcachegrind/cgview/CMakeLists.txt new file mode 100644 index 00000000..fda80c17 --- /dev/null +++ b/kcachegrind/cgview/CMakeLists.txt @@ -0,0 +1,7 @@ +include_directories(../libcore) +add_executable(cgview main.cpp) + +target_link_libraries(cgview core ${QT_QTCORE_LIBRARY}) + +# do not install example code... +# install(TARGETS cgview ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/kcachegrind/cgview/README b/kcachegrind/cgview/README new file mode 100644 index 00000000..d541016c --- /dev/null +++ b/kcachegrind/cgview/README @@ -0,0 +1,9 @@ +Currently, this directory just contains _example_ code for using +KCachegrind's libcore for command line tools. It is not expected +to be packaged with KCachegrind (therefore, nothing is installed). + +Eventually, it will grow into a convertion/merge/filter tool for +profile data, and a replacement for the callgrind_annotate +script from the Valgrind package (which is broken as it does not +handle profile data with instruction granularity, as well as +cycle detection). diff --git a/kcachegrind/cgview/cgview.pro b/kcachegrind/cgview/cgview.pro new file mode 100644 index 00000000..1259d9b4 --- /dev/null +++ b/kcachegrind/cgview/cgview.pro @@ -0,0 +1,16 @@ +TEMPLATE = app +QT -= gui + +include(../libcore/libcore.pri) + +# This generate *.moc files from NHEADERS, which get included from *.cpp +new_moc.CONFIG = no_link moc_verify +new_moc.output = ${QMAKE_FILE_BASE}.moc +new_moc.commands = $$moc_header.commands +new_moc.input = NHEADERS +QMAKE_EXTRA_COMPILERS = new_moc + +SOURCES += main.cpp + +# makes headers visible in qt-creator +HEADERS += $$NHEADERS diff --git a/kcachegrind/cgview/main.cpp b/kcachegrind/cgview/main.cpp new file mode 100644 index 00000000..3cf9382b --- /dev/null +++ b/kcachegrind/cgview/main.cpp @@ -0,0 +1,193 @@ +/* This file is part of KCachegrind. + Copyright (C) 2008 Josef Weidendorfer + + 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. +*/ + +#include +#include + +#include "tracedata.h" +#include "loader.h" +#include "config.h" +#include "globalconfig.h" +#include "logger.h" + +/* + * Just a simple command line tool using libcore + */ + +void showHelp(QTextStream& out, bool fullHelp = true) +{ + out << "Show profiles from callgrind files. (C) 2010 J. Weidendorfer\n"; + + if (!fullHelp) + out << "Type 'cgview -h' for help." << endl; + else + out << "Usage: cgview [options] ...\n\n" + "Options:\n" + " -h Show this help text\n" + " -e Sort list according to exclusive cost\n" + " -s Sort and show counters for event \n" + " -c Sort by call count\n" + " -b Show butterfly (callers and callees)\n" + " -n Do not detect recursive cycles" << endl; + + exit(1); +} + + +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + QTextStream out(stdout); + + Loader::initLoaders(); + ConfigStorage::setStorage(new ConfigStorage); + GlobalConfig::config()->addDefaultTypes(); + + QStringList list = app.arguments(); + list.pop_front(); + if (list.isEmpty()) showHelp(out, false); + + bool sortByExcl = false; + bool sortByCount = false; + bool showCalls = false; + QString showEvent; + QStringList files; + + for(int arg = 0; argload(files); + + EventTypeSet* m = d->eventTypes(); + if (m->realCount() == 0) { + out << "Error: No event types found." << endl; + return 1; + } + + out << "\nTotals for event types:\n"; + + QString p; + EventType* et; + for (int i=0;irealCount();i++) { + et = m->realType(i); + out.setFieldWidth(14); + out.setFieldAlignment(QTextStream::AlignRight); + out << d->subCost(et).pretty(); + out.setFieldWidth(0); + out << " " << et->longName() << " (" << et->name() << ")\n"; + } + for (int i=0;iderivedCount();i++) { + et = m->derivedType(i); + out.setFieldWidth(14); + out.setFieldAlignment(QTextStream::AlignRight); + out << d->subCost(et).pretty(); + out.setFieldWidth(0); + out << " " << et->longName() << + " (" << et->name() << " = " << et->formula() << ")\n"; + } + out << endl; + + if (showEvent.isEmpty()) + et = m->realType(0); + else { + et = m->type(showEvent); + if (!et) { + out << "Error: event '" << showEvent << "' not found." << endl; + return 1; + } + } + Q_ASSERT( et!=0 ); + out << "Sorted by: " << (sortByExcl ? "Exclusive ":"Inclusive ") + << et->longName() << " (" << et->name() << ")" << endl; + + QList flist; + HighestCostList hc; + hc.clear(50); + TraceFunctionMap::Iterator it; + for ( it = d->functionMap().begin(); it != d->functionMap().end(); ++it ) + flist.append(&(*it)); + + TraceFunction *f; + foreach(f, d->functionCycles()) + flist.append(f); + + foreach(f, flist) { + if (sortByCount) + hc.addCost(f, f->calledCount()); + else if (sortByExcl) + hc.addCost(f, f->subCost(et)); + else + hc.addCost(f, f->inclusive()->subCost(et)); + } + + + out << "\n Inclusive Exclusive Called Function name (DSO)\n"; + out << " ==================================================================\n"; + + out.setFieldAlignment(QTextStream::AlignRight); + for(int i=0; i0) out << endl; + foreach(TraceCall* c, f->callers()) { + out << " "; + out.setFieldWidth(14); + out << c->subCost(et).pretty(); + out.setFieldWidth(0); + out << " "; + out.setFieldWidth(13); + out << c->prettyCallCount(); + out.setFieldWidth(0); + out << " < " << c->caller()->prettyName() << endl; + } + } + + out.setFieldWidth(14); + out << f->inclusive()->subCost(et).pretty(); + out << f->subCost(et).pretty(); + out.setFieldWidth(13); + out << f->prettyCalledCount(); + out.setFieldWidth(0); + out << " " << f->name() << " (" << f->object()->name() << ")" << endl; + + if (showCalls) { + foreach(TraceCall* c, f->callings()) { + out << " "; + out.setFieldWidth(14); + out << c->subCost(et).pretty(); + out.setFieldWidth(0); + out << " "; + out.setFieldWidth(13); + out << c->prettyCallCount(); + out.setFieldWidth(0); + out << " > " << c->called()->prettyName() << endl; + } + } + + } +} + diff --git a/kcachegrind/converters/CMakeLists.txt b/kcachegrind/converters/CMakeLists.txt new file mode 100644 index 00000000..2589d7cb --- /dev/null +++ b/kcachegrind/converters/CMakeLists.txt @@ -0,0 +1,11 @@ +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/hotshot2calltree.cmake + ${CMAKE_CURRENT_BINARY_DIR}/hotshot2calltree + ) +macro_additional_clean_files( + ${CMAKE_CURRENT_BINARY_DIR}/hotshot2calltree + ) + +install( PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/hotshot2calltree + op2calltree pprof2calltree dprof2calltree memprof2calltree + DESTINATION ${BIN_INSTALL_DIR} ) diff --git a/kcachegrind/converters/README b/kcachegrind/converters/README new file mode 100644 index 00000000..281ff003 --- /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 for + dprof2calltree and pprof2calltree, +* Jörg Beyer 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 +# 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 [-o outfile]\n"; + exit -1; +} + + +# vim: set sts=2 ts=2 bs ai expandtab : diff --git a/kcachegrind/converters/hotshot2calltree.cmake b/kcachegrind/converters/hotshot2calltree.cmake new file mode 100644 index 00000000..c2dcc170 --- /dev/null +++ b/kcachegrind/converters/hotshot2calltree.cmake @@ -0,0 +1,394 @@ +#!/usr/bin/env python +# _*_ coding: latin1 _*_ + +# +# Copyright (c) 2003 by WEB.DE, Karlsruhe +# Autor: Jörg Beyer +# +# 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, 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 +# 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 berücksichtigt, +# zZ sind alle __init__'s und alle run's schwer unterscheidbar :-( +# +version = "Version ${KCACHEGRIND_VERSION}" +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 the 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..ca121a2a --- /dev/null +++ b/kcachegrind/converters/op2calltree @@ -0,0 +1,238 @@ +#!/usr/bin/perl +# +# Copyright (c) 2004 +# Author: Josef Weidendorfer +# +# 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, 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 +# 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 + +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; + exit(1); +} +?> diff --git a/kcachegrind/kcachegrind.lsm.cmake b/kcachegrind/kcachegrind.lsm.cmake new file mode 100644 index 00000000..ab1d0729 --- /dev/null +++ b/kcachegrind/kcachegrind.lsm.cmake @@ -0,0 +1,11 @@ +Begin3 +Title: kcachegrind +Version: ${KCACHEGRIND_VERSION} +Description: KDE Profiling Visualisation Tool +Keywords: Profiling, Performance Analysis, Visualisation, Development +Author: Josef Weidendorfer +Maintained-by: Josef Weidendorfer +Home-page: http://kcachegrind.sourceforge.net +Platforms: Linux and other Unices +Copying-policy: GNU Public License +End diff --git a/kcachegrind/kcachegrind.spec.cmake b/kcachegrind/kcachegrind.spec.cmake new file mode 100644 index 00000000..5f06983e --- /dev/null +++ b/kcachegrind/kcachegrind.spec.cmake @@ -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 +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/CMakeLists.txt b/kcachegrind/kcachegrind/CMakeLists.txt new file mode 100644 index 00000000..b94fa38a --- /dev/null +++ b/kcachegrind/kcachegrind/CMakeLists.txt @@ -0,0 +1,33 @@ +include_directories( ../libcore ../libviews ) + +########### next target ############### + +set(kcachegrind_SRCS + main.cpp + kdeconfig.cpp + toplevel.cpp + configdlg.cpp + ) + +kde4_add_ui_files(kcachegrind_SRCS + configdlgbase.ui + ) + +kde4_add_app_icon(kcachegrind_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/hi*-app-kcachegrind.png") + +kde4_add_executable(kcachegrind ${kcachegrind_SRCS}) + +target_link_libraries(kcachegrind core views ${KDE4_KIO_LIBS}) + +install(TARGETS kcachegrind ${INSTALL_TARGETS_DEFAULT_ARGS} ) + + +########### install files ############### + +install( PROGRAMS kcachegrind.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +install( FILES tips DESTINATION ${DATA_INSTALL_DIR}/kcachegrind ) +install( FILES kcachegrindui.rc DESTINATION ${DATA_INSTALL_DIR}/kcachegrind ) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) + + diff --git a/kcachegrind/kcachegrind/Doxyfile b/kcachegrind/kcachegrind/Doxyfile new file mode 100644 index 00000000..3a3e9650 --- /dev/null +++ b/kcachegrind/kcachegrind/Doxyfile @@ -0,0 +1,155 @@ +# 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 = +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/configdlg.cpp b/kcachegrind/kcachegrind/configdlg.cpp new file mode 100644 index 00000000..1a8184cf --- /dev/null +++ b/kcachegrind/kcachegrind/configdlg.cpp @@ -0,0 +1,375 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration Dialog for KCachegrind + */ + +#include "configdlg.h" + +#include +#include + +#include +#include +#include +#include + +#include "tracedata.h" +#include "globalguiconfig.h" + + +ConfigDlg::ConfigDlg(GlobalGUIConfig* c, TraceData* data, + QWidget* parent) + :ConfigDlgBase(parent) +{ + _config = c; + _data = data; + _objectCS = 0; + _classCS = 0; + _fileCS = 0; + + 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 &))); + + connect(buttonBox, SIGNAL(accepted()),SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()),SLOT(reject())); + QString objectPrefix = ProfileContext::typeName(ProfileContext::Object); + QString classPrefix = ProfileContext::typeName(ProfileContext::Class); + QString filePrefix = ProfileContext::typeName(ProfileContext::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; + if (data) { + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) + oList.append((*oit).prettyName()); + } + + TraceClassMap::Iterator cit; + QStringList cList; + if (data) { + for ( cit = data->classMap().begin(); + cit != data->classMap().end(); ++cit ) + cList.append((*cit).prettyName()); + } + + TraceFileMap::Iterator fit; + QStringList fList; + if (data) { + for ( fit = data->fileMap().begin(); + fit != data->fileMap().end(); ++fit ) + fList.append((*fit).prettyName()); + } + + // then already defined colors (have to check for duplicates!) + foreach(ConfigColorSetting* cs, c->_colors) { + if (cs->_automatic) continue; + + QString n = cs->_name; + if (n.startsWith(objectPrefix)) { + n = n.remove(0, objectPrefix.length()+1); + if (oList.indexOf(n) == -1) oList.append(n); + } + else if (n.startsWith(classPrefix)) { + n = n.remove(0, classPrefix.length()+1); + if (cList.indexOf(n) == -1) cList.append(n); + } + else if (n.startsWith(filePrefix)) { + n = n.remove(0, filePrefix.length()+1); + if (fList.indexOf(n) == -1) fList.append(n); + } + } + + oList.sort(); + cList.sort(); + fList.sort(); + objectCombo->addItems(oList); + classCombo->addItems(cList); + fileCombo->addItems(fList); + + objectActivated(objectCombo->currentText()); + classActivated(classCombo->currentText()); + fileActivated(fileCombo->currentText()); + + maxListEdit->setValue(c->_maxListCount); + + QTreeWidgetItem* i = new QTreeWidgetItem(dirList); + i->setText(0, i18n("(always)")); + i->setExpanded(true); + QTreeWidgetItem* root = i; + QStringList::const_iterator sit = c->_generalSourceDirs.constBegin(); + for(; sit != c->_generalSourceDirs.constEnd(); ++sit ) { + QTreeWidgetItem *item = new QTreeWidgetItem(i); + item->setText(0, sit->isEmpty() ? QDir::rootPath() : *sit); + } + if (data) { + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) { + const QString n = (*oit).name(); + if (n.isEmpty()) continue; + i = new QTreeWidgetItem(dirList); + i->setText(0, n); + i->setExpanded(true); + const QStringList dirs = c->_objectSourceDirs[n]; + + sit = dirs.constBegin(); + for(; sit != dirs.constEnd(); ++sit ) { + QTreeWidgetItem *item = new QTreeWidgetItem(i); + item->setText(0, sit->isEmpty() ? QDir::rootPath() : *sit); + } + } + } + + connect(dirList, SIGNAL(itemSelectionChanged()), + this, SLOT(dirsItemChanged())); + connect(addDirButton, SIGNAL(clicked()), + this, SLOT(dirsAddPressed())); + connect(deleteDirButton, SIGNAL(clicked()), + this, SLOT(dirsDeletePressed())); + dirList->setCurrentItem(root); + + symbolCount->setValue(c->_maxSymbolCount); + symbolLength->setValue(c->_maxSymbolLength); + precisionEdit->setValue(c->_percentPrecision); + contextEdit->setValue(c->_context); +} + +ConfigDlg::~ConfigDlg() +{ +} + +bool ConfigDlg::configure(GlobalGUIConfig* c, TraceData* d, QWidget* p) +{ + ConfigDlg dlg(c, d, p); + + if (dlg.exec()) { + + // max 499 per definition + c->_maxListCount = dlg.maxListEdit->value(); + c->_maxSymbolCount = dlg.symbolCount->value(); + c->_maxSymbolLength = dlg.symbolLength->value(); + c->_percentPrecision = dlg.precisionEdit->value(); + c->_context = dlg.contextEdit->value(); + return true; + } + return false; +} + +void ConfigDlg::objectActivated(const QString & s) +{ +// qDebug("objectActivated: %s", s.ascii()); + + if (s.isEmpty()) { _objectCS=0; return; } + + QString n = ProfileContext::typeName(ProfileContext::Object) + '-' + s; + + ConfigColorSetting* cs = _config->_colors[n]; + if (!cs) + cs = GlobalGUIConfig::colorSetting(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 = ProfileContext::typeName(ProfileContext::Class) + '-' + s; + + ConfigColorSetting* cs = _config->_colors[n]; + if (!cs) + cs = GlobalGUIConfig::colorSetting(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 = ProfileContext::typeName(ProfileContext::File) + '-' + s; + + ConfigColorSetting* cs = _config->_colors[n]; + if (!cs) + cs = GlobalGUIConfig::colorSetting(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; +} + +QTreeWidgetItem *ConfigDlg::getSelectedDirItem() +{ + const QList selectedItems = dirList->selectedItems(); + return selectedItems.count() ? selectedItems[0] : NULL; +} + +void ConfigDlg::dirsItemChanged() +{ + QTreeWidgetItem *dirItem = getSelectedDirItem(); + deleteDirButton->setEnabled(dirItem && dirItem->parent() != NULL); + addDirButton->setEnabled(dirItem && dirItem->parent() == NULL); +} + +void ConfigDlg::dirsDeletePressed() +{ + QTreeWidgetItem *dirItem = getSelectedDirItem(); + if (!dirItem || (dirItem->parent() == 0)) return; + QTreeWidgetItem* p = dirItem->parent(); + if (!p) return; + + QString objName = p->text(0); + + QStringList* dirs; + if (objName == i18n("(always)")) + dirs = &(_config->_generalSourceDirs); + else + dirs = &(_config->_objectSourceDirs[objName]); + + dirs->removeAll(dirItem->text(0)); + delete dirItem; + + deleteDirButton->setEnabled(false); +} + +void ConfigDlg::dirsAddPressed() +{ + QTreeWidgetItem *dirItem = getSelectedDirItem(); + if (!dirItem || (dirItem->parent() != 0)) return; + + QString objName = dirItem->text(0); + + QStringList* dirs; + if (objName == i18n("(always)")) + dirs = &(_config->_generalSourceDirs); + else + dirs = &(_config->_objectSourceDirs[objName]); + + QString newDir; + newDir = KFileDialog::getExistingDirectory(KUrl(), + this, + i18n("Choose Source Folder")); + if (newDir.isEmpty()) return; + + // even for '/', we strip the tailing slash + if (newDir.endsWith(QLatin1Char('/'))) + newDir = newDir.left(newDir.length()-1); + + if (dirs->indexOf(newDir)>=0) return; + + dirs->append(newDir); + if (newDir.isEmpty()) newDir = QDir::rootPath(); + QTreeWidgetItem *item = new QTreeWidgetItem(dirItem); + item->setText(0, newDir); +} + +#include "moc_configdlg.cpp" diff --git a/kcachegrind/kcachegrind/configdlg.h b/kcachegrind/kcachegrind/configdlg.h new file mode 100644 index 00000000..416249c4 --- /dev/null +++ b/kcachegrind/kcachegrind/configdlg.h @@ -0,0 +1,73 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration Dialog for KCachegrind + */ + +#ifndef CONFIGDLG_H +#define CONFIGDLG_H + +#include "ui_configdlgbase.h" + +class TraceData; +class GlobalGUIConfig; +class ConfigColorSetting; + +class ConfigDlgBase : public QDialog, public Ui::ConfigDlgBase +{ +public: + ConfigDlgBase( QWidget *parent ) : QDialog( parent ) { + setupUi( this ); + } +}; + +class ConfigDlg : public ConfigDlgBase +{ + Q_OBJECT + +public: + ConfigDlg(GlobalGUIConfig*, TraceData*, + QWidget* parent = 0); + ~ConfigDlg(); + + static bool configure(GlobalGUIConfig*, 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(); + void dirsDeletePressed(); + void dirsAddPressed(); + +private: + QTreeWidgetItem *getSelectedDirItem(); + GlobalGUIConfig* _config; + TraceData* _data; + + ConfigColorSetting *_objectCS, *_classCS, *_fileCS; +}; + +#endif diff --git a/kcachegrind/kcachegrind/configdlgbase.ui b/kcachegrind/kcachegrind/configdlgbase.ui new file mode 100644 index 00000000..b70cd64c --- /dev/null +++ b/kcachegrind/kcachegrind/configdlgbase.ui @@ -0,0 +1,519 @@ + + + ConfigDlgBase + + + + 0 + 0 + 394 + 328 + + + + Configuration + + + + + + 0 + + + + General + + + + + + + + + + Maximum number of items in lists: + + + false + + + + + + + Truncate symbols in tooltips and context menus + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + when more than: + + + true + + + + + + + when longer than: + + + true + + + + + + + Precision of percentage values: + + + false + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + 10 + + + + + + + 999 + + + + + + + 999 + + + + + + + The Maximum Number of List Items should be below 500. + + + 499 + + + + + + + + + + 75 + true + + + + QFrame::NoFrame + + + QFrame::Plain + + + Cost Item Colors + + + false + + + + + + + 0 + + + 6 + + + + + Automatic + + + + + + + Object: + + + false + + + + + + + Class: + + + false + + + + + + + + + + + + + + Automatic + + + + + + + + + + + + + + Automatic + + + + + + + File: + + + false + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 20 + + + + + + + + + 150 + 0 + + + + true + + + + + + + true + + + + + + + true + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 0 + + + + + + + + + Annotations + + + + + + + + Context lines in annotations: + + + false + + + + + + + + + + + + + 75 + true + + + + Source Folders + + + false + + + + + + + + + + + Add + + + + + + + Delete + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 16 + 49 + + + + + + + + + + + 300 + 0 + + + + QAbstractItemView::NoEditTriggers + + + false + + + + Object / Related Source Base + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + KIntSpinBox + QSpinBox +
knuminput.h
+
+ + KDialogButtonBox + QDialogButtonBox +
kdialogbuttonbox.h
+
+ + KPushButton + QPushButton +
kpushbutton.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + KColorButton + QPushButton +
kcolorbutton.h
+
+
+ + tabWidget2 + objectCombo + objectCheck + objectColor + classCombo + classCheck + classColor + fileCombo + fileCheck + fileColor + buttonBox + dirList + + + + + classCheck + toggled(bool) + classColor + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + fileCheck + toggled(bool) + fileColor + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + objectCheck + toggled(bool) + objectColor + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + +
diff --git a/kcachegrind/kcachegrind/dumpmanager.cpp b/kcachegrind/kcachegrind/dumpmanager.cpp new file mode 100644 index 00000000..677e5001 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpmanager.cpp @@ -0,0 +1,66 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * DumpManager + */ + +#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..291de507 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpmanager.h @@ -0,0 +1,75 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * DumpManager + * + * 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 +#include + +class Dump; +class TraceData; + +typedef QList DumpList; + + +/** + * A loadable profile Dump + */ +class Dump +{ +public: + Dump(QString); + + QString filename() const { 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..b8a1d053 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselection.cpp @@ -0,0 +1,49 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * DumpSelection Dockable + * + * - 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 "moc_dumpselection.cpp" + diff --git a/kcachegrind/kcachegrind/dumpselection.h b/kcachegrind/kcachegrind/dumpselection.h new file mode 100644 index 00000000..8fb61e46 --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselection.h @@ -0,0 +1,45 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * DumpSelection Dockable + * + * - 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: + explicit 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..b12bacbf --- /dev/null +++ b/kcachegrind/kcachegrind/dumpselectionbase.ui @@ -0,0 +1,1021 @@ + + DumpSelectionBase + + + + 0 + 0 + 349 + 832 + + + + Profile Dumps + + + + + + Qt::Vertical + + + + + Target + + + true + + + true + + + + + + + + true + + + true + + + + + Time + + + true + + + true + + + + + Path + + + true + + + true + + + + + + + Options + + + + 0 + + + + + + 5 + 5 + 1 + 0 + + + + Target command: + + + false + + + + + + + + + + Profiler options: + + + false + + + + + + + + Option + + + true + + + true + + + + + Value + + + true + + + true + + + + + Trace + + + + + + + + + + + + + Jumps + + + + + + + + + + + + + + Instructions + + + + + + + + + + + + + + + Events + + + + + + + + + + + + + Full Cache + + + + + + + + + + + + + + Custom + + + + + + + + + + + + + + + Collect + + + + + + + + + + + + + At Startup + + + + + + + + + + + + + + While In + + + + + + + + + + + + + + + Skip + + + + + + + + + + + + + PLT + + + + + + + + + + + + + + Function + + + + + + + + + + + + + + + Dump Profile + + + + + + + + + + + + + Every BBs + + + + + + + + + + + + + + On Entering + + + + + + + + + + + + + + On Leaving + + + + + + + + + + + + + + + Zero Events + + + + + + + + + + + + + On Entering + + + + + + + + + + + + + + + Separate + + + + + + + + + + + + + Threads + + + + + + + + + + + + + + Recursions + + + + + + + + + + + + + + Call Chain + + + + + + + + + + + + + + + + + + + 5 + 5 + 1 + 0 + + + + Custom profiler options: + + + false + + + + + + + + + + 0 + + + + + + 21 + 20 + + + + Expanding + + + Horizontal + + + + + + + Run New Profile + + + + + + + + + + Info + + + + 0 + + + + + Dump reason: + + + false + + + + + + + + + + Event summary: + + + false + + + + + + + + Name + + + true + + + true + + + + + Sum + + + true + + + true + + + + + + + + Miscellaneous: + + + false + + + + + + + + + + 0 + + + + + + 50 + 20 + + + + Expanding + + + Horizontal + + + + + + + Show + + + + + + + Compare + + + + + + + + + + State + + + + 0 + + + + + 0 + + + + + Update + + + + + + + Every [s]: + + + + + + + + + + + + + Counter + + + true + + + true + + + + + Value + + + true + + + true + + + + + Dumps Done + + + + + + + + + + + + + + Is Collecting + + + + + + + + + + + + + + Executed + + + + + + + + + + + + + Basic Blocks + + + + + + + + + + + + + + Calls + + + + + + + + + + + + + + Jumps + + + + + + + + + + + + + + + Events + + + + + + + + + + + + + Ir + + + + + + + + + + + + + + + Distinct + + + + + + + + + + + + + ELF Objects + + + + + + + + + + + + + + Functions + + + + + + + + + + + + + + Contexts + + + + + + + + + + + + + + + + + + 0 + + + + + + 5 + 5 + 1 + 0 + + + + Stack trace: + + + false + + + + + + + Sync. + + + + + + + + + + # + + + true + + + true + + + + + Incl. + + + true + + + true + + + + + Called + + + true + + + true + + + + + Function + + + true + + + true + + + + + Location + + + true + + + true + + + + + + + + 0 + + + + + Start + + + + + + + + 20 + 20 + + + + Expanding + + + Horizontal + + + + + + + Zero + + + + + + + Dump + + + + + + + + + + Messages + + + + 0 + + + + + + + + 0 + + + + + Kill Run + + + + + + + + 21 + 20 + + + + Expanding + + + Horizontal + + + + + + + Clear + + + + + + + + + + + + + diff --git a/kcachegrind/kcachegrind/hi32-app-kcachegrind.png b/kcachegrind/kcachegrind/hi32-app-kcachegrind.png new file mode 100644 index 00000000..632591e4 Binary files /dev/null and b/kcachegrind/kcachegrind/hi32-app-kcachegrind.png differ diff --git a/kcachegrind/kcachegrind/hi48-app-kcachegrind.png b/kcachegrind/kcachegrind/hi48-app-kcachegrind.png new file mode 100644 index 00000000..b739663f Binary files /dev/null and b/kcachegrind/kcachegrind/hi48-app-kcachegrind.png differ diff --git a/kcachegrind/kcachegrind/kcachegrind.desktop b/kcachegrind/kcachegrind/kcachegrind.desktop new file mode 100755 index 00000000..08d8277b --- /dev/null +++ b/kcachegrind/kcachegrind/kcachegrind.desktop @@ -0,0 +1,178 @@ +# KDE Config File +[Desktop Entry] +Type=Application +Exec=kcachegrind -caption %c %u +MimeType=application/x-kcachegrind; +Icon=kcachegrind +X-DocPath=kcachegrind/index.html +Terminal=false +Name=KCachegrind +Name[ast]=KCachegrind +Name[bg]=KCachegrind +Name[br]=KCachegrind +Name[bs]=KCachegrind +Name[ca]=KCachegrind +Name[ca@valencia]=KCachegrind +Name[cs]=KCachegrind +Name[cy]=KCachegrind +Name[da]=KCachegrind +Name[de]=KCachegrind +Name[el]=KCachegrind +Name[en_GB]=KCachegrind +Name[eo]=KCachegrind +Name[es]=KCachegrind +Name[et]=KCachegrind +Name[eu]=KCachegrind +Name[fi]=KCachegrind +Name[fr]=KCachegrind +Name[ga]=KCachegrind +Name[gl]=KCachegrind +Name[he]=KCachegrind +Name[hu]=KCachegrind +Name[is]=KCachegrind +Name[it]=KCachegrind +Name[ja]=KCachegrind +Name[kk]=KCachegrind +Name[km]=KCachegrind +Name[ko]=KCachegrind +Name[lt]=KCachegrind +Name[lv]=KCachegrind +Name[mr]=के-कॅशेग्रिन्ड +Name[nb]=KCachegrind +Name[nds]=KCachegrind +Name[ne]=केडीई क्यास ग्रिन्ड +Name[nl]=KCachegrind +Name[nn]=KCachegrind +Name[pa]=KCachegrind +Name[pl]=KCachegrind +Name[pt]=KCachegrind +Name[pt_BR]=KCachegrind +Name[ro]=KCachegrind +Name[ru]=KCachegrind +Name[sk]=KCachegrind +Name[sl]=KCachegrind +Name[sr]=К‑кешгринд +Name[sr@ijekavian]=К‑кешгринд +Name[sr@ijekavianlatin]=KCacheGrind +Name[sr@latin]=KCacheGrind +Name[sv]=Kcachegrind +Name[ta]= இடைமாற்றகட்டம் +Name[tg]=KCachegrind +Name[tr]=KCachegrind +Name[ug]=KCachegrind +Name[uk]=KCachegrind +Name[x-test]=xxKCachegrindxx +Name[zh_CN]=KCachegrind +Name[zh_TW]=KCachegrind +GenericName=Profiler Frontend +GenericName[ast]=Interface del analizador de rendimientu +GenericName[bs]=Profajlerski program +GenericName[ca]=Frontal del Profiler +GenericName[ca@valencia]=Frontal del 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[en_GB]=Profiler Frontend +GenericName[eo]=Fasado de Profililo +GenericName[es]=Interfaz del analizador de rendimiento +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[hu]=Ábrázoló előtétprogram +GenericName[is]=Myndrænt viðmót á afkastakönnuð +GenericName[it]=Interfaccia a profiler +GenericName[ja]=プロファイラフロントエンド +GenericName[kk]=Профильдеткіштің интерфейсі +GenericName[km]=កម្មវិធី​ផ្នែក​ខាង​មុខ​របស់​ទម្រង់ +GenericName[ko]=프로파일러 프론트엔드 +GenericName[lt]=Profiliuoklio naudotojo sąsaja +GenericName[lv]=Profilēšanas priekšpuse +GenericName[mr]=प्रोफाइलर फ्रंटएन्ड +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 Análise de Performance +GenericName[pt_BR]=Interface de análise de performance +GenericName[ro]=Interfață grafică pentru Profiler +GenericName[ru]=Интерфейс к профилировщику +GenericName[sk]=Rozhranie pre profiler +GenericName[sl]=Vmesnik profilnika +GenericName[sr]=Прочеље профилизатора +GenericName[sr@ijekavian]=Прочеље профилизатора +GenericName[sr@ijekavianlatin]=Pročelje profilizatora +GenericName[sr@latin]=Pročelje profilizatora +GenericName[sv]=Profileringsgränssnitt +GenericName[ta]= விவரக்குறிப்பு முன்பகுதி +GenericName[tg]=Интерфейс ба профилкунанда +GenericName[tr]=Profil Önyüzü +GenericName[uk]=Інтерфейс до Profiler +GenericName[wa]=Eterface grafike po Profiler +GenericName[x-test]=xxProfiler Frontendxx +GenericName[zh_CN]=性能测试数据前端 +GenericName[zh_TW]=分析器前端 +Comment=Visualization of Performance Profiling Data +Comment[ast]=Visualización de datos d'analís de rendimientu +Comment[bg]=Визуализация на данните за производителност +Comment[bs]=Predočenje podataka profiliranja performansi +Comment[ca]=Visualització de dades de perfilat de rendiment +Comment[ca@valencia]=Visualització 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]=Visualise les données de profilage de performances +Comment[ga]=Amharcléiriú ar Shonraí Próifílithe Feidhmíochta +Comment[gl]=Visualización dos datos da análise de rendemento +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 profilo delle prestazioni +Comment[ja]=パフォーマンスプロファイルデータを視覚化 +Comment[kk]=Деректерді профильдеудің визуализациясы +Comment[km]=កា​រ​មើល​ឃើញ​ការ​អនុវត្ត​របស់​ទិន្នន័យ​ទម្រង់ +Comment[ko]=성능 프로파일링 데이터 시각화 +Comment[lt]=Veikimo profiliavimo duomenų vizualizacija +Comment[lv]=Veiktspējas profilēšanas datu vizualizācija +Comment[mr]=कार्यक्षमता प्रोफाइलिंग डेटाचे व्हिज्युअलायझेशन +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 dos dados de análise de desempenho +Comment[ru]=Утилита для визуального профилирования приложений +Comment[sk]=Vizualizácia dát o výkone +Comment[sl]=Vizualizacija podatkov profilnih zmogljivosti +Comment[sr]=Визуелизација профилисања перформанси +Comment[sr@ijekavian]=Визуелизација профилисања перформанси +Comment[sr@ijekavianlatin]=Vizuelizacija profilisanja performansi +Comment[sr@latin]=Vizuelizacija profilisanja performansi +Comment[sv]=Åskådliggörande av profileringsdata för prestanda +Comment[ta]= விவர தகவலை செயல்பாட்டு காட்சியாளிப்பு +Comment[tg]=Утилита барои гузориши профили визуалӣ +Comment[tr]=Performans Profilleme Verisinin Görünür Hali +Comment[ug]=سانلىق-مەلۇماتلارنىڭ سەپلىنىشىنى سۈرەتلەشتۈرۈش +Comment[uk]=Візуалізація даних профілювання швидкодії +Comment[x-test]=xxVisualization of Performance Profiling Dataxx +Comment[zh_CN]=性能测试数据的可视化表现 +Comment[zh_TW]=效能分析資料視覺化 +X-DBUS-StartupType=Multi +X-DBUS-ServiceName=net.sf.kcachegrind +Categories=Qt;KDE;Development; diff --git a/kcachegrind/kcachegrind/kcachegrindui.rc b/kcachegrind/kcachegrind/kcachegrindui.rc new file mode 100644 index 00000000..8efed1ab --- /dev/null +++ b/kcachegrind/kcachegrind/kcachegrindui.rc @@ -0,0 +1,57 @@ + + + + &File + + + + + + &View + + + + + &Layout + + + + + + + + + + + + + + + + + + Sidebars + + + + + + + + + Main Toolbar + + + + + + + + + + + + State Toolbar + + + diff --git a/kcachegrind/kcachegrind/kdeconfig.cpp b/kcachegrind/kcachegrind/kdeconfig.cpp new file mode 100644 index 00000000..d44cdf2c --- /dev/null +++ b/kcachegrind/kcachegrind/kdeconfig.cpp @@ -0,0 +1,148 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration for KCachegrind + */ + +#include "kdeconfig.h" + +#include +#include +#include +#include + +#include "tracedata.h" +#include "traceitemview.h" +#include "ui_configdlgbase.h" + +// +// KDEConfigGroup +// + +KDEConfigGroup::KDEConfigGroup(KConfigGroup* group, bool readOnly) +{ + _kgroup = group; + _readOnly = readOnly; +} + +KDEConfigGroup::~KDEConfigGroup() +{ + delete _kgroup; +} + +void KDEConfigGroup::setValue(const QString& key, const QVariant& value, + const QVariant& defaultValue) +{ + if ((_kgroup == 0) || _readOnly) return; + + if (value == defaultValue) { + _kgroup->deleteEntry(key); + return; + } + + switch(value.type()) { + case QVariant::Bool: + _kgroup->writeEntry(key, value.toBool()); + break; + case QVariant::Int: + _kgroup->writeEntry(key, value.toInt()); + break; + case QVariant::Double: + _kgroup->writeEntry(key, value.toDouble()); + break; + case QVariant::String: + _kgroup->writeEntry(key, value.toString()); + break; + case QVariant::StringList: + _kgroup->writeEntry(key, value.toStringList()); + break; + case QVariant::Color: + _kgroup->writeEntry(key, value.value()); + break; + default: + qFatal("KDEConfigGroup::setValue - QVariant type %s not supported", + value.typeName()); + } +} + +QVariant KDEConfigGroup::value(const QString& key, + const QVariant& defaultValue) const +{ + if (_kgroup == 0) return defaultValue; + + switch(defaultValue.type()) { + case QVariant::Bool: + return QVariant(_kgroup->readEntry(key, + defaultValue.toBool())); + case QVariant::Int: + return QVariant(_kgroup->readEntry(key, + defaultValue.toInt())); + case QVariant::Double: + return QVariant(_kgroup->readEntry(key, + defaultValue.toDouble())); + case QVariant::String: + return QVariant(_kgroup->readEntry(key, + defaultValue.toString())); + case QVariant::StringList: + return QVariant(_kgroup->readEntry(key, + defaultValue.toStringList())); + case QVariant::Color: + return QVariant(_kgroup->readEntry(key, + defaultValue.value())); + default: + qFatal("KDEConfigGroup::value - QVariant type %s not supported", + defaultValue.typeName()); + } + return defaultValue; +} + + + +// +// KDEConfigStorage +// + +KDEConfigStorage::KDEConfigStorage(KConfig* kconfig) +{ + _kconfig = kconfig; +} + +ConfigGroup* KDEConfigStorage::getGroup(const QString& group, + const QString& optSuffix) +{ + KConfigGroup* g; + bool readOnly; + + if (!optSuffix.isEmpty()) { + readOnly = true; + QStringList gList = _kconfig->groupList(); + if (gList.contains(group+optSuffix)) + g = new KConfigGroup(_kconfig, group+optSuffix); + else if (gList.contains(group)) + g = new KConfigGroup(_kconfig, group); + else + g = 0; + } + else { + readOnly = false; + g = new KConfigGroup(_kconfig, group); + } + + return new KDEConfigGroup(g, readOnly); +} diff --git a/kcachegrind/kcachegrind/kdeconfig.h b/kcachegrind/kcachegrind/kdeconfig.h new file mode 100644 index 00000000..51b0d792 --- /dev/null +++ b/kcachegrind/kcachegrind/kdeconfig.h @@ -0,0 +1,63 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration for KCachegrind + */ + +#ifndef KDECONFIG_H +#define KDECONFIG_H + +#include "config.h" + +class KConfig; +class KConfigGroup; +class KDEConfigStorage; + +class KDEConfigGroup: public ConfigGroup +{ + friend class KDEConfigStorage; + +public: + ~KDEConfigGroup(); + + void setValue(const QString& key, const QVariant& value, + const QVariant& defaultValue = QVariant()); + QVariant value(const QString& key, const QVariant& defaultValue) const; + +private: + KDEConfigGroup(KConfigGroup*, bool); + + KConfigGroup* _kgroup; + bool _readOnly; +}; + + +class KDEConfigStorage : public ConfigStorage +{ +public: + KDEConfigStorage(KConfig*); + +private: + ConfigGroup* getGroup(const QString& group, + const QString& optSuffix); + + KConfig* _kconfig; +}; + +#endif diff --git a/kcachegrind/kcachegrind/main.cpp b/kcachegrind/kcachegrind/main.cpp new file mode 100644 index 00000000..e7eafe5d --- /dev/null +++ b/kcachegrind/kcachegrind/main.cpp @@ -0,0 +1,99 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * KCachegrind startup + */ + +// for KCACHEGRIND_VERSION +#include "../version.h" + + +#include +#include +#include +#include + +#include "kdeconfig.h" +#include "toplevel.h" +#include "tracedata.h" +#include "loader.h" + +int main( int argc, char ** argv ) +{ + KAboutData aboutData("kcachegrind", 0, + ki18n("KCachegrind"), + KCACHEGRIND_VERSION, + ki18n("KDE Frontend for Callgrind/Cachegrind"), + KAboutData::License_GPL, + ki18n("(C) 2002 - 2011"), KLocalizedString(), + "http://kcachegrind.sf.net"); + aboutData.addAuthor(ki18n("Josef Weidendorfer"), + ki18n("Author/Maintainer"), + "Josef.Weidendorfer@gmx.de"); + + KCmdLineArgs::init(argc, argv, &aboutData); + + KCmdLineOptions options; + //options.add("r ", ki18n("Run under cachegrind")); + options.add("+[trace]", ki18n("Show information of this trace")); + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication a; + KGlobal::locale()->insertCatalog("kcachegrind_qt"); + TopLevel* t; + Loader::initLoaders(); + + KConfig* kc = KGlobal::config().data(); + ConfigStorage::setStorage(new KDEConfigStorage(kc)); + + if (a.isSessionRestored()){ + int n = 1; + while (KMainWindow::canBeRestored(n)){ + (new TopLevel())->restore(n); + n++; + } + } + else { + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + int nbArgs = args->count(); + if (nbArgs>0) { + t = new TopLevel(); + t->show(); + for(int i = 0; i < nbArgs; i++) { + t->loadDelayed(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(); + ProfileContext::cleanup(); + ConfigStorage::cleanup(); + + return res; +} diff --git a/kcachegrind/kcachegrind/tips b/kcachegrind/kcachegrind/tips new file mode 100644 index 00000000..4e64d1f6 --- /dev/null +++ b/kcachegrind/kcachegrind/tips @@ -0,0 +1,141 @@ + + +

...that the What's this... help for every GUI widget +in KCachegrind contains detailed usage information for this widget? +It is highly recommend to read at least these help texts on first +use. Request What's this... help by pressing +Shift-F1 and clicking on the widget.

+ +
+ + + +

...that you can get profile information at instruction level +with Calltree when you provide the option --dump-instr=yes? +Use the Assembler View for the instruction annotations. +

+ +
+ + + +

...that you can use Alt-Left/Right keys of your keyboard to go +back/forward in the active object history ?

+ +
+ + + +

...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. +

+ +
+ + + +

...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. +

+ +
+ + + +

...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?

+ +
+ + + +

...that you can assign custom colors to +ELF objects/C++ Classes/Source Files for graph coloring +in Settings->Configure KCachegrind...?

+ +
+ + + +

...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?

+

There must be the name of the source file (with extension). +If KCachegrind still does not show the source, make sure that you +have added the directory of the source file to the +Source Directories list in the configuration. + + + + + +

...that you can configure whether KCachgrind should +show absolute event counts or relative ones (percentage display)?

+ +
+ + + +

...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.

+

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.

+ +
+ + + +

...that the Coverage tab - in contrast to the Call Lists tab - +shows all 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?

+

Examples:

+

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().

+

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().

+ +
+ + + +

...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?

+

Items from this list can be selected by pressing the right +mouse button.

+ +
+ + + +

...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?

+

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).

+ +
+ + +

...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?

+ +
+ diff --git a/kcachegrind/kcachegrind/toplevel.cpp b/kcachegrind/kcachegrind/toplevel.cpp new file mode 100644 index 00000000..a11e90af --- /dev/null +++ b/kcachegrind/kcachegrind/toplevel.cpp @@ -0,0 +1,2381 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * KCachegrind top level window + */ + +#define TRACE_UPDATES 0 +#define ENABLE_DUMPDOCK 0 + +#include "toplevel.h" + +#include // for system() + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if ENABLE_DUMPDOCK +#include "dumpselection.h" +#endif + +#include "partselection.h" +#include "functionselection.h" +#include "stackselection.h" +#include "stackbrowser.h" +#include "tracedata.h" +#include "globalguiconfig.h" +#include "config.h" +#include "configdlg.h" +#include "multiview.h" +#include "callgraphview.h" + +TopLevel::TopLevel() + : KXmlGuiWindow(0) +{ + QDBusConnection::sessionBus().registerObject("/KCachegrind", this, QDBusConnection::ExportScriptableSlots); + + _progressBar = 0; + _statusbar = statusBar(); + _statusLabel = new QLabel(_statusbar); + _statusbar->addWidget(_statusLabel, 1); + _ccProcess = 0; + + _layoutCount = 1; + _layoutCurrent = 0; + + resetState(); + + KConfig *kconfig = KGlobal::config().data(); + GlobalGUIConfig::config()->readOptions(); + + createDocks(); + + _multiView = new MultiView(this, this ); + _multiView->setObjectName("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 + + + // set toggle after reading configuration + _showPercentage = GlobalConfig::showPercentage(); + _showExpanded = GlobalConfig::showExpanded(); + _showCycles = GlobalConfig::showCycles(); + _hideTemplates = GlobalConfig::hideTemplates(); + _taPercentage->setChecked(_showPercentage); + _taExpanded->setChecked(_showExpanded); + _taCycles->setChecked(_showCycles); + _taHideTemplates->setChecked(_hideTemplates); + + setupPartSelection(_partSelection); + + // KCachegrind for KDE 3.0.x does not allow to hide toolbars... + setStandardToolBarMenuEnabled(true); + _openRecent->loadEntries( KConfigGroup( kconfig, "" ) ); + + // QT dock windows are created before (using QT position restoring) + createGUI(); + + setAutoSaveSettings(); + + // restore current state settings (not configuration options) + restoreCurrentState(QString::null); //krazy:exclude=nullstrassign for old broken gcc + + // if this is the first toplevel, show tip of day + if (memberList().count() == 1) + QTimer::singleShot( 200, this, SLOT(slotShowTipOnStart()) ); +} + +void TopLevel::resetState() +{ + _activeParts.clear(); + _hiddenParts.clear(); + + _data = 0; + _function = 0; + _eventType = 0; + _eventType2 = 0; + _groupType = ProfileContext::InvalidType; + _group = 0; + + // for delayed slots + _traceItemDelayed = 0; + _eventTypeDelayed = 0; + _eventType2Delayed = 0; + _groupTypeDelayed = ProfileContext::InvalidType; + _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(partsHideSelected()), + this, SLOT(partsHideSelectedSlotDelayed())); + connect(ps, SIGNAL(partsUnhideAll()), + this, SLOT(partsUnhideAllSlotDelayed())); +} + +/** + * 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 queryClose() for Qt docks. + */ +void TopLevel::saveCurrentState(const QString& postfix) +{ + QString eventType = _eventType ? _eventType->name() : QString("?"); + QString eventType2 = _eventType2 ? _eventType2->name() : QString("?"); + + ConfigGroup* stateConfig = ConfigStorage::group(QString("CurrentState") + postfix); + stateConfig->setValue("EventType", eventType); + stateConfig->setValue("EventType2", eventType2); + stateConfig->setValue("GroupType", ProfileContext::typeName(_groupType)); + delete stateConfig; + + _partSelection->saveOptions(QString("PartOverview"), postfix); + _multiView->saveLayout(QString("MainView"), postfix); + _multiView->saveOptions(QString("MainView"), postfix); +} + +/** + * This function is called when a trace is closed. + * Save browsing position for later restoring + */ +void TopLevel::saveTraceSettings() +{ + QString key = traceKey(); + + ConfigGroup* lConfig = ConfigStorage::group("Layouts"); + lConfig->setValue(QString("Count%1").arg(key), _layoutCount); + lConfig->setValue(QString("Current%1").arg(key), _layoutCurrent); + delete lConfig; + + ConfigGroup* pConfig = ConfigStorage::group("TracePositions"); + if (_eventType) + pConfig->setValue(QString("EventType%1").arg(key), _eventType->name()); + if (_eventType2) + pConfig->setValue(QString("EventType2%1").arg(key), _eventType2->name()); + if (_groupType != ProfileContext::InvalidType) + pConfig->setValue(QString("GroupType%1").arg(key), + ProfileContext::typeName(_groupType)); + + if (_data) { + if (_group) + pConfig->setValue(QString("Group%1").arg(key), _group->name()); + saveCurrentState(key); + } + delete pConfig; +} + +/** + * 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(const QString& postfix) +{ + _partSelection->restoreOptions(QString("PartOverview"), postfix); + _multiView->restoreLayout(QString("MainView"), postfix); + _multiView->restoreOptions(QString("MainView"), postfix); + + _taSplit->setChecked(_multiView->childCount()>1); + _taSplitDir->setEnabled(_multiView->childCount()>1); + _taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal); +} + + +void TopLevel::createDocks() +{ + _partDock = new QDockWidget(this); + _partDock->setObjectName("part dock"); + _partDock->setWindowTitle(i18n("Parts Overview")); + _partSelection = new PartSelection(this, _partDock); + _partDock->setWidget(_partSelection); + + _stackDock = new QDockWidget(this); + _stackDock->setObjectName("stack dock"); + // Why is the caption only correct with a close button? + _stackSelection = new StackSelection(_stackDock); + _stackDock->setWidget(_stackSelection); + _stackDock->setWindowTitle(i18n("Top Cost Call Stack")); + _stackSelection->setWhatsThis( i18n( + "The Top Cost Call Stack" + "

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.

" + "

The Cost and Calls columns show the " + "cost used for all calls from the function in the line " + "above.

")); + + connect(_stackSelection, SIGNAL(functionSelected(CostItem*)), + this, SLOT(setTraceItemDelayed(CostItem*))); + + _functionDock = new QDockWidget(this); + _functionDock->setObjectName("function dock"); + _functionDock->setWindowTitle(i18n("Flat Profile")); + _functionSelection = new FunctionSelection(this, _functionDock); + _functionDock->setWidget(_functionSelection); + +#if ENABLE_DUMPDOCK + _dumpDock = new QDockWidget(this); + _dumpDock->setWindowTitle(i18n("Profile Dumps")); + _dumpSelection = new DumpSelection(this, _dumpDock, + "dumpSelection"); + _dumpSelection->setTopLevel(this); + + _dumpDock->setWidget(_dumpSelection); + _dumpSelection->setWhatsThis( i18n( + "Profile Dumps" + "

This dockable shows in the top part the list of " + "loadable profile dumps in all subdirectories of: " + "

  • current working directory of KCachegrind, " + "i.e. where it was started from, and
  • " + "
  • the default profile dump directory given in the " + "configuration.
" + "The list is sorted according to the target command " + "profiled in the corresponding dump.

" + "

On selecting a profile dump, information for it " + "is shown in the bottom area of the dockable: " + "

  • Options 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 Run Profile to start a " + "profile run with these options in the background.
  • " + "
  • Info gives detailed info on the selected " + "dump like event cost summary and properties of the " + "simulated cache.
  • " + "
  • State is only available for current happening " + "profiles runs. Press Update to see different " + "counters of the run, and a stack trace of the current " + "position in the program profiled. Check the Every " + "option to let KCachegrind regularly poll these data. " + "Check the Sync option to let the dockable activate " + "the top function in the current loaded dump.

")); +#endif + + // default positions, will be adjusted automatically by stored state in config + addDockWidget(Qt::LeftDockWidgetArea, _partDock ); + addDockWidget(Qt::LeftDockWidgetArea, _stackDock ); + addDockWidget(Qt::LeftDockWidgetArea, _functionDock ); + _stackDock->hide(); + _partDock->hide(); + +#if ENABLE_DUMPDOCK + addDockWidget( Qt::LeftDockWidgetArea, _dumpDock ); + _dumpDock->hide(); +#endif + + KConfigGroup dockConfig(KGlobal::config(), "Docks"); + _forcePartDock = dockConfig.readEntry("ForcePartDockVisible", false); +} + + +TopLevel::~TopLevel() +{ + delete _data; +} + + +void TopLevel::saveProperties(KConfigGroup & c) +{ + if ( _data ) + c.writeEntry("TraceName", _data->traceName()); +} + +void TopLevel::readProperties(const KConfigGroup &c) +{ + QString traceName = c.readEntry("TraceName"); + if (!traceName.isEmpty()) { + openDataFile(traceName); + } +} + +void TopLevel::createLayoutActions() +{ + QString hint; + KAction* action; + + action = actionCollection()->addAction( "layout_duplicate" ); + action->setText( i18n( "&Duplicate" ) ); + connect(action, SIGNAL(triggered(bool)), SLOT(layoutDuplicate())); + action->setShortcuts(KShortcut(Qt::CTRL+Qt::Key_Plus)); + hint = i18n("Duplicate Current Layout" + "

Make a copy of the current layout.

"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "layout_remove" ); + action->setText( i18n( "&Remove" ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT(layoutRemove())); + hint = i18n("Remove Current Layout" + "

Delete current layout and make the previous active.

"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "layout_next" ); + action->setText( i18n( "&Go to Next" ) ); + connect(action, SIGNAL(triggered(bool)), SLOT(layoutNext())); + action->setShortcuts(KShortcut(Qt::CTRL+Qt::Key_Right)); + hint = i18n("Go to Next Layout"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "layout_previous" ); + action->setText( i18n( "&Go to Previous" ) ); + connect(action, SIGNAL(triggered(bool)), SLOT(layoutPrevious())); + action->setShortcuts(KShortcut(Qt::CTRL+Qt::Key_Left)); + hint = i18n("Go to Previous Layout"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "layout_restore" ); + action->setText( i18n( "&Restore to Default" ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT(layoutRestore())); + hint = i18n("Restore Layouts to Default"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "layout_save" ); + action->setText( i18n( "&Save as Default" ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT(layoutSave())); + hint = i18n("Save Layouts as Default"); + action->setWhatsThis( hint ); +} + +// TODO: split this up... +void TopLevel::createMiscActions() +{ + QString hint; + KAction* action; + + action = KStandardAction::openNew(this, SLOT(newWindow()), actionCollection()); + hint = i18n("New

Open new empty KCachegrind window.

"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "file_add" ); + action->setText( i18n( "&Add..." ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT(add())); + hint = i18n("Add Profile Data" + "

This opens an additional profile data file in the current window.

"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "reload" ); + action->setIcon( KIcon("view-refresh") ); + action->setText( i18nc("Reload a document", "&Reload" ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT( reload() )); + action->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Reload)); + hint = i18n("Reload Profile Data" + "

This loads any new created parts, too.

"); + action->setWhatsThis( hint ); + + action = actionCollection()->addAction( "export" ); + action->setText( i18n( "&Export Graph" ) ); + connect(action, SIGNAL(triggered(bool) ), SLOT(exportGraph())); + + hint = i18n("Export Call Graph" + "

Generates a file with extension .dot for the tools " + "of the GraphViz package.

"); + action->setWhatsThis( hint ); + + + _taDump = actionCollection()->add( "dump" ); + _taDump->setIcon( KIcon("edit-redo") ); + _taDump->setText( i18n( "&Force Dump" ) ); + connect(_taDump, SIGNAL(triggered(bool) ), SLOT( forceTrace() )); + _taDump->setShortcut(KStandardShortcut::shortcut(KStandardShortcut::Redo)); + hint = i18n("Force Dump" + "

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.

" + "

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 is no Callgrind " + "running, press 'Force Dump' again to cancel the dump " + "request. This deletes 'callgrind.cmd' itself and " + "stops polling for a new dump.

" + "

Note: A Callgrind run only detects " + "existence of 'callgrind.cmd' when actively running " + "a few milliseconds, i.e. " + "not sleeping. Tip: For a profiled GUI program, " + "you can awake Callgrind e.g. by resizing a window " + "of the program.

"); + _taDump->setWhatsThis( hint ); + + action = KStandardAction::open(this, SLOT(load()), actionCollection()); + hint = i18n("Open Profile Data" + "

This opens a profile data file, with possible multiple parts

"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + _openRecent = KStandardAction::openRecent(this, SLOT(load(const KUrl&)), + actionCollection()); + + KStandardAction::showStatusbar(this, + SLOT(toggleStatusBar()), actionCollection()); + + _partDockShown = actionCollection()->add("settings_show_partdock"); + _partDockShown->setText(i18n("Parts Overview")); + connect(_partDockShown, SIGNAL(triggered(bool) ), SLOT(togglePartDock())); + + hint = i18n("Show/Hide the Parts Overview Dockable"); + _partDockShown->setToolTip( hint ); + _partDockShown->setWhatsThis( hint ); + + _stackDockShown = actionCollection()->add("settings_show_stackdock"); + _stackDockShown->setText(i18n("Call Stack")); + connect(_stackDockShown, SIGNAL(triggered(bool) ), SLOT(toggleStackDock())); + + hint = i18n("Show/Hide the Call Stack Dockable"); + _stackDockShown->setToolTip( hint ); + _stackDockShown->setWhatsThis( hint ); + + _functionDockShown = actionCollection()->add("settings_show_profiledock"); + _functionDockShown->setText(i18n("Function Profile")); + connect(_functionDockShown, SIGNAL(triggered(bool) ), SLOT(toggleFunctionDock())); + + hint = i18n("Show/Hide the Function Profile Dockable"); + _functionDockShown->setToolTip( hint ); + _functionDockShown->setWhatsThis( hint ); + +#if ENABLE_DUMPDOCK + _dumpDockShown = actionCollection()->add("settings_show_dumpdock", + this, SLOT(toggleDumpDock())); + _dumpDockShown->setText(i18n("Profile Dumps")); + hint = i18n("Show/Hide the Profile Dumps Dockable"); + _dumpDockShown->setToolTip( hint ); + _dumpDockShown->setWhatsThis( hint ); +#endif + + _taPercentage = actionCollection()->add("view_percentage"); + _taPercentage->setIcon(KIcon("percent")); + _taPercentage->setText(i18n("Relative")); + connect(_taPercentage, SIGNAL(triggered(bool) ), SLOT(togglePercentage())); + hint = i18n("Show relative instead of absolute costs"); + _taPercentage->setToolTip( hint ); + _taPercentage->setWhatsThis( hint ); + + _taExpanded = actionCollection()->add("view_expanded"); + _taExpanded->setIcon(KIcon("move")); + _taExpanded->setText(i18n("Relative to Parent")); + connect(_taExpanded, SIGNAL(triggered(bool) ), SLOT(toggleExpanded())); + + hint = i18n("Show percentage costs relative to parent"); + _taExpanded->setToolTip( hint ); + _taExpanded->setWhatsThis( hint ); + + hint = i18n("Show percentage costs relative to parent" + "

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.

" + "
    " + "" + "" + "" + "" + "" + "
    Cost TypeParent Cost
    Function CumulativeTotal
    Function SelfFunction Group (*) / Total
    CallFunction Inclusive
    Source LineFunction Inclusive
" + "

(*) Only if function grouping is switched on (e.g. ELF object grouping).

"); + _taExpanded->setWhatsThis( hint ); + + _taCycles = actionCollection()->add("view_cycles"); + _taCycles->setIcon(KIcon("edit-undo")); + _taCycles->setText(i18n( "Cycle Detection" )); + connect(_taCycles, SIGNAL(triggered(bool) ), SLOT( toggleCycles() )); + + hint = i18n("Detect recursive cycles" + "

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).

" + "

The correct handling for cycles is to detect them and collapse all " + "functions of a cycle into an artificial 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 ); + + _taHideTemplates = actionCollection()->add("hide_templates"); + _taHideTemplates->setIcon(KIcon("hidetemplates")); + _taHideTemplates->setText(i18n( "Shorten Templates" )); + connect(_taHideTemplates, SIGNAL(triggered(bool) ), SLOT( toggleHideTemplates() )); + _taHideTemplates->setToolTip(i18n( "Hide Template Parameters in C++ Symbols" )); + hint = i18n("Hide Template Parameters in C++ Symbols" + "

If this is switched on, every symbol displayed will have " + "any C++ template parameters hidden, just showing <> " + "instead of a potentially nested template parameter.

" + "

In this mode, you can hover the mouse pointer over the " + "activated symbol label to show a tooltip with the " + "unabbreviated symbol.

"); + _taHideTemplates->setWhatsThis(hint); + + KStandardAction::quit(this, SLOT(close()), actionCollection()); + KStandardAction::preferences(this, SLOT(configure()), actionCollection()); + KStandardAction::keyBindings(this, SLOT(configureKeys()), actionCollection()); + KStandardAction::configureToolbars(this,SLOT(configureToolbars()), + actionCollection()); +#if 0 + action = KStandardAction::back(_stackSelection, SLOT(browserBack()), + actionCollection()); + hint = i18n("Go back in function selection history"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStandardAction::forward(_stackSelection, SLOT(browserForward()), + actionCollection()); + hint = i18n("Go forward in function selection history"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); + + action = KStandardAction::up(_stackSelection, SLOT(browserUp()), + actionCollection()); + hint = i18n("Go Up" + "

Go to last selected caller of current function. " + "If no caller was visited, use that with highest cost.

"); + action->setToolTip( hint ); + action->setWhatsThis( hint ); +#else + _paUp = new KToolBarPopupAction( KIcon( "go-up" ), i18n( "&Up" ), this ); + _paUp->setShortcuts( KShortcut(Qt::ALT+Qt::Key_Up) ); + connect( _paUp, SIGNAL( triggered( bool ) ), _stackSelection, SLOT( browserUp() ) ); + actionCollection()->addAction( "go_up", _paUp ); + connect( _paUp->menu(), SIGNAL( aboutToShow() ), + this, SLOT( upAboutToShow() ) ); + connect( _paUp->menu(), SIGNAL( triggered( QAction* ) ), + this, SLOT( upTriggered( QAction* ) ) ); + hint = i18n("Go Up" + "

Go to last selected caller of current function. " + "If no caller was visited, use that with highest cost.

"); + _paUp->setToolTip( hint ); + _paUp->setWhatsThis( hint ); + + QPair< KGuiItem, KGuiItem > backForward = KStandardGuiItem::backAndForward(); + _paBack = new KToolBarPopupAction( backForward.first.icon(), backForward.first.text(), this ); + _paBack->setShortcuts( KShortcut(Qt::ALT+Qt::Key_Left) ); + connect( _paBack, SIGNAL( triggered( bool ) ), _stackSelection, SLOT( browserBack() ) ); + actionCollection()->addAction( "go_back", _paBack ); + connect( _paBack->menu(), SIGNAL( aboutToShow() ), + this, SLOT( backAboutToShow() ) ); + connect( _paBack->menu(), SIGNAL( triggered( QAction* ) ), + this, SLOT( backTriggered( QAction* ) ) ); + hint = i18n("Go back in function selection history"); + _paBack->setToolTip( hint ); + _paBack->setWhatsThis( hint ); + + _paForward = new KToolBarPopupAction( backForward.second.icon(), backForward.second.text(), this ); + _paForward->setShortcuts( KShortcut(Qt::ALT+Qt::Key_Right) ); + connect( _paForward, SIGNAL( triggered( bool ) ), _stackSelection, SLOT( browserForward() ) ); + actionCollection()->addAction( "go_forward", _paForward ); + connect( _paForward->menu(), SIGNAL( aboutToShow() ), + this, SLOT( forwardAboutToShow() ) ); + connect( _paForward->menu(), SIGNAL( triggered( QAction* ) ), + this, SLOT( forwardTriggered( QAction* ) ) ); + hint = i18n("Go forward in function selection history"); + _paForward->setToolTip( hint ); + _paForward->setWhatsThis( hint ); +#endif + + _saCost = actionCollection()->add("view_cost_type"); + _saCost->setText(i18n("Primary Event Type")); + hint = i18n("Select primary event type of costs"); + _saCost->setComboWidth(300); + _saCost->setToolTip( hint ); + _saCost->setWhatsThis( hint ); + + // This is needed because for KDE4, "_saCost->setComboWidth(300);" seems to + // have no effect. Instead, list box entry widths are used to determine the + // combobox width. However, at KCachegrind startup, we do not have yet + // a list of event types, as this depends on the profile data. + // In KDE 4.2, we used a translatable string, which did not really work as + // the semantic is not known to translators. Instead, we use a + // nontranslatable string now... + QStringList dummyItems; + dummyItems << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + _saCost->setItems(dummyItems); + + // cost types are dependent on loaded data, thus KSelectAction + // is filled in setData() + connect( _saCost, SIGNAL(triggered(const QString&)), + this, SLOT(eventTypeSelected(const QString&))); + + _saCost2 = actionCollection()->add("view_cost_type2"); + _saCost2->setText(i18n("Secondary Event Type")); + hint = i18n("Select secondary event type for cost e.g. shown in annotations"); + _saCost2->setComboWidth(300); + _saCost2->setToolTip( hint ); + _saCost2->setWhatsThis( hint ); + _saCost2->setItems(dummyItems); + + connect( _saCost2, SIGNAL(triggered(const QString&)), + this, SLOT(eventType2Selected(const QString&))); + + saGroup = actionCollection()->add("view_group_type"); + saGroup->setText(i18n("Grouping")); + + hint = i18n("Select how functions are grouped into higher level cost items"); + saGroup->setToolTip( hint ); + saGroup->setWhatsThis( hint ); + + QStringList args; + + args << i18n("(No Grouping)") + << ProfileContext::i18nTypeName(ProfileContext::Object) + << ProfileContext::i18nTypeName(ProfileContext::File) + << ProfileContext::i18nTypeName(ProfileContext::Class) + << ProfileContext::i18nTypeName(ProfileContext::FunctionCycle); + + saGroup->setItems(args); + connect( saGroup, SIGNAL(triggered(int)), + this, SLOT(groupTypeSelected(int))); + + _taSplit = actionCollection()->add("view_split"); + _taSplit->setIcon(KIcon("view-split-left-right")); + _taSplit->setText(i18n("Split")); + connect(_taSplit, SIGNAL(triggered(bool) ), SLOT(splitSlot())); + + hint = i18n("Show two information panels"); + _taSplit->setToolTip( hint ); + _taSplit->setWhatsThis( hint ); + + _taSplitDir = actionCollection()->add("view_split_dir"); + _taSplitDir->setIcon(KIcon("view-split-left-right")); + _taSplitDir->setText(i18n("Split Horizontal")); + connect(_taSplitDir, SIGNAL(triggered(bool) ), SLOT(splitDirSlot())); + + hint = i18n("Change Split Orientation when main window is split."); + _taSplitDir->setToolTip( hint ); + _taSplitDir->setWhatsThis( hint ); + + // copied from KMail... + KStandardAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() ); +} + +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::updateViewsOnChange(int change) +{ + _partSelection->notifyChange(change); + _functionSelection->notifyChange(change); + _multiView->notifyChange(change); +} + +void TopLevel::setPercentage(bool show) +{ + if (_showPercentage == show) return; + _showPercentage = show; + if (_taPercentage->isChecked() != show) + _taPercentage->setChecked(show); + + GlobalConfig::setShowPercentage(_showPercentage); + + _stackSelection->refresh(); + + updateViewsOnChange(TraceItemView::configChanged); +} + +void TopLevel::toggleExpanded() +{ + bool show = _taExpanded->isChecked(); + if (_showExpanded == show) return; + _showExpanded = show; + + GlobalConfig::setShowExpanded(_showExpanded); + + _stackSelection->refresh(); + + updateViewsOnChange(TraceItemView::configChanged); +} + +void TopLevel::toggleCycles() +{ + bool show = _taCycles->isChecked(); + if (_showCycles == show) return; + _showCycles = show; + + GlobalConfig::setShowCycles(_showCycles); + + if (!_data) return; + + _data->invalidateDynamicCost(); + _data->updateFunctionCycles(); + + _stackSelection->rebuildStackList(); + + updateViewsOnChange(TraceItemView::configChanged); +} + +void TopLevel::toggleHideTemplates() +{ + bool b = _taHideTemplates->isChecked(); + if (_hideTemplates == b) return; + _hideTemplates = b; + + GlobalConfig::setHideTemplates(_hideTemplates); + + _stackSelection->refresh(); + + updateViewsOnChange(TraceItemView::configChanged); +} + +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() +{ + KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); +} + + +void TopLevel::configureToolbars() +{ + KEditToolBar *dlg = new KEditToolBar(guiFactory(), this); + + if (dlg->exec()) + createGUI(); + + delete dlg; +} + + +void TopLevel::newWindow() +{ + TopLevel* t = new TopLevel(); + t->show(); +} + + +void TopLevel::load() +{ + KUrl url = KFileDialog::getOpenUrl(KUrl("kfiledialog:///"), + i18n("cachegrind.out* callgrind.out*|Callgrind Profile Data\n*|All Files"), + this, + i18n("Select Callgrind Profile Data")); + load(url); +} + +void TopLevel::load(const KUrl& url) +{ + if (url.isEmpty()) return; + + // network transparency + QString tmpFile; + // for KDE 3.2: KIO::NetAccess::download with 2 args is deprecated + if(KIO::NetAccess::download( url, tmpFile, this )) { + _openRecent->addUrl(url); + _openRecent->saveEntries( KConfigGroup( KGlobal::config(), QString() ) ); + + load(tmpFile); + KIO::NetAccess::removeTempFile( tmpFile ); + } else { + KMessageBox::error(this, i18n("Could not open the file \"%1\". " + "Check it exists and you have enough " + "permissions to read it.", url.prettyUrl())); + } +} + +/* if file name is ".": load first file found in current directory, but do + * not show an error message if nothing could be loaded + */ +void TopLevel::load(QString file) +{ + if (file.isEmpty()) return; + + bool showError = true; + if (file == QString(".")) + showError = false; + + if (_data && _data->parts().count()>0) { + + // In new window + TopLevel* t = new TopLevel(); + t->show(); + t->loadDelayed(file); + return; + } + + bool loaded = openDataFile(file); + if (!loaded && showError) + KMessageBox::error(this, i18n("Could not open the file \"%1\". " + "Check it exists and you have enough " + "permissions to read it.", file)); +} + + +void TopLevel::add() +{ + KUrl url = KFileDialog::getOpenUrl(KUrl(), + i18n("cachegrind.out* callgrind.out*|Callgrind Profile Data\n*|All Files"), + this, + i18n("Add Callgrind Profile Data")); + add(url); +} + +void TopLevel::add(const KUrl& url) +{ + if (url.isEmpty()) return; + + // network transparency + QString tmpFile; + if(KIO::NetAccess::download( url, tmpFile, this )) { + _openRecent->addUrl(url); + _openRecent->saveEntries( KGlobal::config()->group( QString() ) ); + + add(tmpFile); + KIO::NetAccess::removeTempFile( tmpFile ); + } +} + +void TopLevel::add(QString file) +{ + if (file.isEmpty()) return; + + if (_data) { + _data->load(file); + + // GUI update for added data + configChanged(); + return; + } + + openDataFile(file); +} + + + +void TopLevel::loadDelayed(QString file) +{ + _loadFilesDelayed << file; + QTimer::singleShot(0, this, SLOT(loadTraceDelayed())); +} + +void TopLevel::loadDelayed(QStringList files) +{ + _loadFilesDelayed << files; + QTimer::singleShot(0, this, SLOT(loadTraceDelayed())); +} + +void TopLevel::loadTraceDelayed() +{ + if (_loadFilesDelayed.isEmpty()) return; + + if (_loadFilesDelayed.count()>1) { + // FIXME: we expect all files to be local and existing + TraceData* d = new TraceData(this); + d->load(_loadFilesDelayed); + setData(d); + } + else { + QString file = _loadFilesDelayed[0]; + + // if URL scheme is missing (URL is relative), this is a local file + if (KUrl::isRelativeUrl(file)) + load(file); + else + load(KUrl(file)); + } + _loadFilesDelayed.clear(); +} + + +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... + openDataFile(trace); +} + +void TopLevel::exportGraph() +{ + if (!_data || !_function) return; + + QString n = QString("callgraph.dot"); + GraphExporter ge(_data, _function, _eventType, _groupType, n); + ge.writeDot(); + +#ifdef Q_OS_UNIX + // shell commands only work in UNIX + QString cmd = QString("(dot %1 -Tps > %2.ps; xdg-open %3.ps)&") + .arg(n).arg(n).arg(n); + if (::system(QFile::encodeName( cmd ))<0) + qDebug() << "TopLevel::exportGraph: can not run " << cmd; +#endif +} + + +bool TopLevel::setEventType(QString s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->type(s) : 0; + + // if costtype with given name not found, use first available + if (!ct && _data) ct = _data->eventTypes()->type(0); + + return setEventType(ct); +} + +bool TopLevel::setEventType2(QString s) +{ + EventType* ct; + + // Special type i18n("(Hidden)") gives 0 + ct = (_data) ? _data->eventTypes()->type(s) : 0; + + return setEventType2(ct); +} + +void TopLevel::eventTypeSelected(const QString& s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; + setEventType(ct); +} + +void TopLevel::eventType2Selected(const QString& s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; + setEventType2(ct); +} + +bool TopLevel::setEventType(EventType* ct) +{ + if (_eventType == ct) return false; + _eventType = ct; + + if (ct) { + QStringList l = _saCost->items(); + int idx = l.indexOf(ct->longName()); + if (idx >= 0) + _saCost->setCurrentItem(idx); + } + + _partSelection->setEventType(_eventType); + _stackSelection->setEventType(_eventType); + _functionSelection->setEventType(_eventType); + _multiView->setEventType(_eventType); + + updateStatusBar(); + + return true; +} + +bool TopLevel::setEventType2(EventType* ct) +{ + if (_eventType2 == ct) return false; + _eventType2 = ct; + + QString longName = ct ? ct->longName() : i18n("(Hidden)"); + QStringList l = _saCost2->items(); + int idx = l.indexOf(longName); + if (idx >= 0) + _saCost2->setCurrentItem(idx); + + _partSelection->setEventType2(_eventType2); + _stackSelection->setEventType2(_eventType2); + _functionSelection->setEventType2(_eventType2); + _multiView->setEventType2(_eventType2); + + updateStatusBar(); + + return true; +} + + +void TopLevel::groupTypeSelected(int cg) +{ + switch(cg) { + case 0: setGroupType( ProfileContext::Function ); break; + case 1: setGroupType( ProfileContext::Object ); break; + case 2: setGroupType( ProfileContext::File ); break; + case 3: setGroupType( ProfileContext::Class ); break; + case 4: setGroupType( ProfileContext::FunctionCycle ); break; + default: break; + } +} + +bool TopLevel::setGroupType(QString s) +{ + ProfileContext::Type gt; + + gt = ProfileContext::type(s); + // only allow Function/Object/File/Class as grouptype + switch(gt) { + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + case ProfileContext::FunctionCycle: + break; + default: + gt = ProfileContext::Function; + } + + return setGroupType(gt); +} + +bool TopLevel::setGroupType(ProfileContext::Type gt) +{ + if (_groupType == gt) return false; + _groupType = gt; + + int idx = -1; + switch(gt) { + case ProfileContext::Function: idx = 0; break; + case ProfileContext::Object: idx = 1; break; + case ProfileContext::File: idx = 2; break; + case ProfileContext::Class: idx = 3; break; + case ProfileContext::FunctionCycle: idx = 4; break; + default: + break; + } + + if (idx==-1) return false; + + if (saGroup->currentItem() != idx) + saGroup->setCurrentItem(idx); + + _stackSelection->setGroupType(_groupType); + _partSelection->set(_groupType); + _functionSelection->set(_groupType); + _multiView->set(_groupType); + + updateStatusBar(); + + return true; +} + +bool TopLevel::setGroup(QString s) +{ + TraceCostItem* ci = _functionSelection->group(s); + if (!ci) + return false; + + return setGroup(ci); +} + + +bool TopLevel::setGroup(TraceCostItem* g) +{ + if (_group == g) return false; + _group = g; + + _functionSelection->setGroup(g); + updateStatusBar(); + + return true; +} + +bool TopLevel::setFunction(QString s) +{ + if (!_data) return false; + + ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType); + if (!f) return false; + + return setFunction((TraceFunction*)f); +} + +bool TopLevel::setFunction(TraceFunction* f) +{ + if (_function == f) return false; + _function = f; + + _multiView->activate(f); + _functionSelection->activate(f); + _partSelection->activate(f); + _stackSelection->setFunction(_function); + + StackBrowser* b = _stackSelection->browser(); + if (b) { + // do not 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::setEventTypeDelayed(EventType* ct) +{ + _eventTypeDelayed = ct; + QTimer::singleShot (0, this, SLOT(setEventTypeDelayed())); +} + +void TopLevel::setEventType2Delayed(EventType* ct) +{ + _eventType2Delayed = ct; + QTimer::singleShot (0, this, SLOT(setEventType2Delayed())); +} + +void TopLevel::setEventTypeDelayed() +{ + setEventType(_eventTypeDelayed); +} + +void TopLevel::setEventType2Delayed() +{ + setEventType2(_eventType2Delayed); +} + +void TopLevel::setGroupTypeDelayed(ProfileContext::Type 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(CostItem* i) +{ + // no need to select same item a 2nd time... + if (_traceItemDelayed == i) return; + _traceItemDelayed = i; + _lastSender = sender(); + + kDebug() << "Selected " << (i ? i->prettyName() : "(none)"); + +#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 ProfileContext::Function: + case ProfileContext::FunctionCycle: + setFunction((TraceFunction*)_traceItemDelayed); + break; + + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + _multiView->activate(_traceItemDelayed); + break; + +#if 0 + // this conflicts with the selection policy of InstrView ?!? + case ProfileContext::Instr: + case ProfileContext::Line: + // only for multiview + _multiView->activate(_traceItemDelayed); + 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); + _multiView->setData(0); + _multiView->updateView(true); + + // we are the owner... + delete _data; + } + + // reset members + resetState(); + + _data = data; + + // fill cost type list + QStringList types; + + if (_data) { + /* add all supported virtual types */ + EventTypeSet* m = _data->eventTypes(); + m->addKnownDerivedTypes(); + + /* first, fill selection list with available cost types */ + for (int i=0;irealCount();i++) + types << m->realType(i)->longName(); + for (int i=0;iderivedCount();i++) + types << m->derivedType(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); + _multiView->setData(_data); + // Force update of _data in all children of _multiView + // This is needed to make restoring of activeItem work! + _multiView->updateView(true); + + /* 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() + ']'; + } + setWindowTitle(caption); + + if (!_data || (!_forcePartDock && _data->parts().count()<2)) { + _partDock->hide(); + _partDockShown->setChecked(false); + } + else { + _partDock->show(); + _partDockShown->setChecked(true); + } + + updateStatusBar(); +} + +void TopLevel::addEventTypeMenu(QMenu* popup, bool withCost2) +{ + if (_data) { + QMenu *popup1, *popup2 = 0; + QAction* action; + + popup1 = popup->addMenu(i18n("Primary Event Type")); + connect(popup1, SIGNAL(triggered(QAction*)), + this, SLOT(setEventType(QAction*))); + + if (withCost2) { + popup2 = popup->addMenu(i18n("Secondary Event Type")); + connect(popup2, SIGNAL(triggered(QAction*)), + this, SLOT(setEventType2(QAction*))); + + if (_eventType2) { + action = popup2->addAction(i18n("Hide")); + action->setData(199); + popup2->addSeparator(); + } + } + + EventTypeSet* m = _data->eventTypes(); + EventType* ct; + for (int i=0;irealCount();i++) { + ct = m->realType(i); + + action = popup1->addAction(ct->longName()); + action->setCheckable(true); + action->setData(100+i); + if (_eventType == ct) action->setChecked(true); + + if (popup2) { + action = popup2->addAction(ct->longName()); + action->setCheckable(true); + action->setData(100+i); + if (_eventType2 == ct) action->setChecked(true); + } + } + + for (int i=0;iderivedCount();i++) { + ct = m->derivedType(i); + + action = popup1->addAction(ct->longName()); + action->setCheckable(true); + action->setData(200+i); + if (_eventType == ct) action->setChecked(true); + + if (popup2) { + action = popup2->addAction(ct->longName()); + action->setCheckable(true); + action->setData(200+i); + if (_eventType2 == ct) action->setChecked(true); + } + } + } + + if (_showPercentage) + popup->addAction(i18n("Show Absolute Cost"), + this, SLOT(setAbsoluteCost())); + else + popup->addAction(i18n("Show Relative Cost"), + this, SLOT(setRelativeCost())); +} + +bool TopLevel::setEventType(QAction* action) +{ + if (!_data) return false; + int id = action->data().toInt(0); + + EventTypeSet* m = _data->eventTypes(); + EventType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->derivedType(id-200); + + return ct ? setEventType(ct) : false; +} + +bool TopLevel::setEventType2(QAction* action) +{ + if (!_data) return false; + int id = action->data().toInt(0); + + EventTypeSet* m = _data->eventTypes(); + EventType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->derivedType(id-200); + + return setEventType2(ct); +} + +void TopLevel::addGoMenu(QMenu* popup) +{ + popup->addAction(i18n("Go Back"), this, SLOT(goBack())); + popup->addAction(i18n("Go Forward"), this, SLOT(goForward())); + popup->addAction(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(); + + QString name = _data->command(); + QString key; + for (int l=0;lvalue(QString("GroupType%1").arg(key),QString()).toString(); + eventType = pConfig->value(QString("EventType%1").arg(key),QString()).toString(); + eventType2 = pConfig->value(QString("EventType2%1").arg(key),QString()).toString(); + delete pConfig; + + ConfigGroup* cConfig = ConfigStorage::group("CurrentState"); + if (groupType.isEmpty()) + groupType = cConfig->value("GroupType",QString()).toString(); + if (eventType.isEmpty()) + eventType = cConfig->value("EventType",QString()).toString(); + if (eventType2.isEmpty()) + eventType2 = cConfig->value("EventType2",QString()).toString(); + delete cConfig; + + setGroupType(groupType); + setEventType(eventType); + setEventType2(eventType2); + + // if still no cost type set, use first available + if (!_eventType && !_saCost->items().isEmpty()) + eventTypeSelected(_saCost->items().first()); + + ConfigGroup* aConfig = ConfigStorage::group("Layouts"); + _layoutCount = aConfig->value(QString("Count%1").arg(key), 0).toInt(); + _layoutCurrent = aConfig->value(QString("Current%1").arg(key), 0).toInt(); + delete aConfig; + + 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(); + + restoreCurrentState(key); + + ConfigGroup* pConfig = ConfigStorage::group("TracePositions"); + QString group = pConfig->value(QString("Group%1").arg(key),QString()).toString(); + delete pConfig; + if (!group.isEmpty()) setGroup(group); + + + // 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->selectTopFunction(); + } +} + + +/* Layout */ + +void TopLevel::layoutDuplicate() +{ + // save current and allocate a new slot + _multiView->saveLayout(QString("Layout%1-MainView").arg(_layoutCurrent), + traceKey()); + _layoutCurrent = _layoutCount; + _layoutCount++; + + updateLayoutActions(); + + if (0) kDebug() << "TopLevel::layoutDuplicate: count " << _layoutCount; +} + +void TopLevel::layoutRemove() +{ + if (_layoutCount <2) return; + + int from = _layoutCount-1; + if (_layoutCurrent == from) { _layoutCurrent--; from--; } + + // restore from last and decrement count + _multiView->restoreLayout(QString("Layout%1-MainView").arg(from), + traceKey()); + _layoutCount--; + + updateLayoutActions(); +} + +void TopLevel::layoutNext() +{ + if (_layoutCount <2) return; + + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + _layoutCurrent++; + if (_layoutCurrent == _layoutCount) _layoutCurrent = 0; + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + if (0) kDebug() << "TopLevel::layoutNext: current " + << _layoutCurrent << endl; +} + +void TopLevel::layoutPrevious() +{ + if (_layoutCount <2) return; + + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + _layoutCurrent--; + if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1; + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + if (0) kDebug() << "TopLevel::layoutPrevious: current " + << _layoutCurrent << endl; +} + +void TopLevel::layoutSave() +{ + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + + // save all layouts as defaults (ie. without any group name postfix) + for(int i=0;i<_layoutCount;i++) { + _multiView->restoreLayout(layoutPrefix.arg(i), key); + _multiView->saveLayout(layoutPrefix.arg(i), QString()); + } + // restore the previously saved current layout + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + ConfigGroup* layoutConfig = ConfigStorage::group("Layouts"); + layoutConfig->setValue("DefaultCount", _layoutCount); + layoutConfig->setValue("DefaultCurrent", _layoutCurrent); + delete layoutConfig; +} + +void TopLevel::layoutRestore() +{ + KConfig *config = KGlobal::config().data(); + KConfigGroup aConfig(config, "Layouts"); + _layoutCount = aConfig.readEntry("DefaultCount", 0); + _layoutCurrent = aConfig.readEntry("DefaultCurrent", 0); + if (_layoutCount == 0) { + _layoutCount++; + return; + } + + QString layoutPrefix = QString("Layout%1-MainView"); + _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey()); + + updateLayoutActions(); +} + + +void TopLevel::updateLayoutActions() +{ + QAction* 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->showMessage(i18n("Layout Count: %1", _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 (_eventType) { + status += i18n("Total %1 Cost: %2", + _eventType->longName(), + _data->prettySubCost(_eventType)); + + /* this gets too long... + if (_eventType2 && (_eventType2 != _eventType)) + status += i18n(", %1 Cost: %2") + .arg(_eventType2->longName()) + .arg(_data->prettySubCost(_eventType2)); + */ + } + else + status += i18n("No event type selected"); + + /* Not working... should give group of selected function + + if (_groupType != ProfileContext::Function) { + status += QString(" - %1 '%2'") + .arg(ProfileContext::i18nTypeName(_groupType)) + .arg(_group ? _group->prettyName() : i18n("(None)")); + } + */ + + _statusLabel->setText(status); +} + +void TopLevel::configure() +{ + if (ConfigDlg::configure( GlobalGUIConfig::config(), + _data, this)) { + GlobalGUIConfig::config()->saveOptions(); + + configChanged(); + } + else + GlobalGUIConfig::config()->readOptions(); +} + +bool TopLevel::queryClose() +{ + saveTraceSettings(); + + // save current toplevel options as defaults... + GlobalConfig::setShowPercentage(_showPercentage); + GlobalConfig::setShowExpanded(_showExpanded); + GlobalConfig::setShowCycles(_showCycles); + GlobalConfig::setHideTemplates(_hideTemplates); + GlobalGUIConfig::config()->saveOptions(); + + saveCurrentState(QString()); + + // toolbar and dock positions are automatically stored + + // 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; + KConfigGroup dockConfig(KGlobal::config(), "Docks"); + 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() +{ + // Invalidate found/cached dirs of source files if we have TraceData loaded. + if (_data) { + _data->resetSourceDirs(); + } + + _stackSelection->refresh(); + + updateViewsOnChange(TraceItemView::configChanged); +} + +void TopLevel::slotShowTipOnStart() { + KTipDialog::showTip(this); +} + +void TopLevel::slotShowTip() { + KTipDialog::showTip( this, QString(), 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->set(list); + _multiView->set(list); + _functionSelection->set(list); + _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; + + TracePartList newHidden, newActive; + foreach(TracePart* part, _data->parts()) { + if (_activeParts.contains(part) || + _hiddenParts.contains(part)) + 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() +{ + if (_ccProcess) { + // callgrind_control still running, cancel request + qDebug("TopLevel::forceTrace: killing old callgrind_control"); + _ccProcess->kill(); + delete _ccProcess; + _ccProcess = 0; + _ccOutput = QString(); + } + if (!_taDump->isChecked()) return; + + // get PID of first loaded part + int pid = 0; + TracePart* p = 0; + TracePartList pl; + if (_data) pl = _data->parts(); + if (!pl.isEmpty()) p = pl.first(); + if (p) pid = p->processID(); + if (pid == 0) { + showMessage(i18n("Cannot determine receiver PID for dump request"), + 5000); + _taDump->setChecked(false); + return; + } + + qDebug("TopLevel::forceTrace: run 'callgrind_control -d %d'", pid); + + _ccProcess = new QProcess(this); + connect(_ccProcess, SIGNAL(readyReadStandardOutput()), + SLOT(ccReadOutput())); + connect(_ccProcess, SIGNAL(error(QProcess::ProcessError)), + SLOT(ccError(QProcess::ProcessError))); + connect(_ccProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + SLOT(ccExit(int,QProcess::ExitStatus))); + _ccProcess->start(QString("callgrind_control -d %1").arg(pid), + QIODevice::ReadOnly); +} + +void TopLevel::ccReadOutput() +{ + QProcess* p = qobject_cast(sender()); + qDebug("TopLevel::ccReadOutput: QProcess %p", p); + + // signal from old/uninteresting process? + if (!_ccProcess) return; + if (p != _ccProcess) return; + + _ccOutput.append(_ccProcess->readAllStandardOutput()); +} + +void TopLevel::ccError(QProcess::ProcessError e) +{ + QProcess* p = qobject_cast(sender()); + qDebug("TopLevel::ccError: Got %d from QProcess %p", + e, p); + + // signal from old/uninteresting process? + if (!_ccProcess) return; + if (p != _ccProcess) return; + + showMessage(i18n("Error running callgrind_control"), 5000); + + _ccProcess->deleteLater(); + _ccProcess = 0; +} + +void TopLevel::ccExit(int exitCode, QProcess::ExitStatus s) +{ + QProcess* p = qobject_cast(sender()); + qDebug("TopLevel::ccExit: QProcess %p, exitCode %d", + p, exitCode); + + // signal from old/uninteresting process? + if (!_ccProcess) return; + if (p != _ccProcess) return; + _ccProcess->deleteLater(); + _ccProcess = 0; + _taDump->setChecked(false); + + // if not successful no need to reload + if ((s == QProcess::CrashExit) || (exitCode != 0)) + return; + + // FIXME: Are we sure that file is completely + // dumped after waiting one second? + QTimer::singleShot( 1000, this, SLOT(reload()) ); +} + +void TopLevel::forwardAboutToShow() +{ + QMenu *popup = _paForward->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + QAction* action; + + if (!hi) { + popup->addAction(i18n("(No Stack)")); + return; + } + + hi = hi->next(); + if (!hi) { + popup->addAction(i18n("(No next function)")); + return; + } + + int count = 1; + while (countfunction(); + if (!f) break; + + QString name = GlobalConfig::shortenSymbol(f->prettyName()); + + //qDebug("forward: Adding %s", name.ascii()); + action = popup->addAction(name); + action->setData(count); + + hi = hi->next(); + count++; + } +} + +void TopLevel::backAboutToShow() +{ + QMenu *popup = _paBack->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + QAction* action; + + if (!hi) { + popup->addAction(i18n("(No Stack)")); + return; + } + + hi = hi->last(); + if (!hi) { + popup->addAction(i18n("(No previous function)")); + return; + } + + int count = 1; + while (countfunction(); + if (!f) break; + + QString name = GlobalConfig::shortenSymbol(f->prettyName()); + + //qDebug("back: Adding %s", name.ascii()); + action = popup->addAction(name); + action->setData(count); + + hi = hi->last(); + count++; + } +} + +void TopLevel::upAboutToShow() +{ + QMenu *popup = _paUp->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f = hi ? hi->function() : 0; + QAction* action; + + if (!f) { + popup->addAction(i18n("(No Stack)")); + return; + } + f = hi->stack()->caller(f, false); + if (!f) { + popup->addAction(i18n("(No Function Up)")); + return; + } + + int count = 1; + while (countprettyName()); + + action = popup->addAction(name); + action->setData(count); + + f = hi->stack()->caller(f, false); + count++; + } + +} + +void TopLevel::forwardTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("forwardTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (count>1) { + b->goForward(); + count--; + } + _stackSelection->browserForward(); +} + +void TopLevel::backTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("backTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (count>1) { + b->goBack(); + count--; + } + _stackSelection->browserBack(); +} + +void TopLevel::upTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("upTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + if (!hi) return; + + TraceFunction* f = hi->function(); + + while (count>0 && f) { + f = hi->stack()->caller(f, false); + count--; + } + + //qDebug("upActivated: %s", f ? f->prettyName().ascii() : "??" ); + if (f) + setFunction(f); +} + +void TopLevel::showMessage(const QString& msg, int ms) +{ + if (_statusbar) + _statusbar->showMessage(msg, ms); +} + +void TopLevel::showStatus(const QString& msg, int progress) +{ + static bool msgUpdateNeeded = true; + + if (!_statusbar) return; + + if (msg.isEmpty()) { + //reset status + if (_progressBar) { + _statusbar->removeWidget(_progressBar); + delete _progressBar; + _progressBar = 0; + } + _statusbar->clearMessage(); + _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->addPermanentWidget(_progressBar, 1); + _progressBar->show(); + msgUpdateNeeded = true; + } + + _progressStart.restart(); + + if (msgUpdateNeeded) { + _statusbar->showMessage(msg); + msgUpdateNeeded = false; + } + _progressBar->setValue(progress); + + // let the progress bar update itself + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); +} + +void TopLevel::loadStart(const QString& filename) +{ + showStatus(i18n("Loading %1").arg(filename), 0); + Logger::_filename = filename; +} + +void TopLevel::loadFinished(const QString& msg) +{ + showStatus(QString(), 0); + if (!msg.isEmpty()) + showMessage(i18n("Error loading %1: %2").arg(_filename).arg(msg), + 2000); +} + +void TopLevel::loadProgress(int progress) +{ + showStatus(i18n("Loading %1").arg(_filename), progress); +} + +void TopLevel::loadError(int line, const QString& msg) +{ + kError() << "Loading" << _filename << ":" << line << ": " << msg; +} + +void TopLevel::loadWarning(int line, const QString& msg) +{ + kWarning() << "Loading" << _filename << ":" << line << ": " << msg; +} + +bool TopLevel::openDataFile(const QString& file) +{ + TraceData* d = new TraceData(this); + int filesLoaded; + + // see whether this file is compressed, than take the direct route + QString mimeType = KMimeType::findByFileContent(file)->name (); + QIODevice* compressed = KFilterDev::deviceForFile (file, mimeType, true); + if (compressed) { + filesLoaded = d->load(compressed, file); + } else { + // else fallback to string based method that can also find multi-part callgrind data. + filesLoaded = d->load(file); + } + if (filesLoaded > 0) { + setData(d); + return true; + } else { + return false; + } +} + + +#include "moc_toplevel.cpp" diff --git a/kcachegrind/kcachegrind/toplevel.h b/kcachegrind/kcachegrind/toplevel.h new file mode 100644 index 00000000..e92df34d --- /dev/null +++ b/kcachegrind/kcachegrind/toplevel.h @@ -0,0 +1,296 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * KCachegrind top level window + */ + +#ifndef TOPLEVEL_H +#define TOPLEVEL_H + +#include +#include +#include + +#include + +#include "logger.h" +#include "traceitemview.h" +#include "tracedata.h" +#include "toplevelbase.h" + +class MultiView; +class QLineEdit; +class QDockWidget; +class QLabel; +class QProgressBar; +class QMenu; + +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 KXmlGuiWindow, public Logger, public TopLevelBase +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kcachegrind") +public: + TopLevel(); + ~TopLevel(); + + TraceData* data() { return _data; } + void setData(TraceData*); + + virtual void saveProperties(KConfigGroup &); + virtual void readProperties(const KConfigGroup &); + + void createActions(); + void createDocks(); + + ProfileContext::Type groupType() { return _groupType; } + EventType* eventType() { return _eventType; } + EventType* eventType2() { return _eventType2; } + 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 addEventTypeMenu(QMenu*,bool); + void addGoMenu(QMenu*); + + // Logger overwrites: notifications for file loading + virtual void loadStart(const QString& filename); + virtual void loadProgress(int progress); // 0 - 100 + virtual void loadWarning(int line, const QString& msg); + virtual void loadError(int line, const QString& msg); + virtual void loadFinished(const QString& msg); // msg could be error + +public slots: + void load(); + void load(const KUrl&); + void load(QString); + void add(); + void add(const KUrl&); + void add(QString); + + // for quickly showing the main window... + void loadDelayed(QString); + void loadDelayed(QStringList); + + 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 eventTypeSelected(const QString&); + void eventType2Selected(const QString&); + void groupTypeSelected(int); + void splitSlot(); + void splitDirSlot(); + void configureToolbars(); + void configureKeys(); + 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 toggleHideTemplates(); + void forceTrace(); + void forwardAboutToShow(); + void forwardTriggered(QAction*); + void backAboutToShow(); + void backTriggered(QAction*); + void upAboutToShow(); + void upTriggered(QAction*); + + bool setEventType(EventType*); + bool setEventType2(EventType*); + bool setEventType(QString); + bool setEventType2(QString); + bool setEventType(QAction*); + bool setEventType2(QAction*); + bool setGroupType(ProfileContext::Type); + 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 + * are not allowed to delete list entries. + */ + void setEventTypeDelayed(EventType*); + void setEventType2Delayed(EventType*); + void setGroupTypeDelayed(ProfileContext::Type); + void setGroupDelayed(TraceCostItem*); + void setTraceItemDelayed(CostItem*); + void partsHideSelectedSlotDelayed(); + void partsUnhideAllSlotDelayed(); + void goBack(); + void goForward(); + void goUp(); + void setDirectionDelayed(TraceItemView::Direction); + + /* SingleShot Slots (without parameters) for the delayed versions */ + void setEventTypeDelayed(); + void setEventType2Delayed(); + 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(const QString& msg, int progress); + void showMessage(const QString&, int msec); + + // for running callgrind_control in the background + void ccReadOutput(); + void ccError(QProcess::ProcessError); + void ccExit(int,QProcess::ExitStatus); + +private: + void resetState(); + void createLayoutActions(); + void createMiscActions(); + void setupMainWidget(MainWidget*); + void setupPartSelection(PartSelection*); + void restoreCurrentState(const QString& postfix); + void saveCurrentState(const QString& postfix); + void saveTraceSettings(); + QString traceKey(); + void restoreTraceTypes(); + void restoreTraceSettings(); + void updateViewsOnChange(int); + /// open @p file, might be compressed + /// @return true when the file could be opened, false otherwise. + bool openDataFile(const QString& file); + + KStatusBar* _statusbar; + QLabel* _statusLabel; + KRecentFilesAction* _openRecent; + bool _twoMainWidgets; + Qt::Orientation _spOrientation; + + MultiView* _multiView; + FunctionSelection* _functionSelection; + DumpSelection* _dumpSelection; + PartSelection* _partSelection; + StackSelection* _stackSelection; + QLineEdit* queryLineEdit; + + QDockWidget *_partDock, *_stackDock, *_functionDock, *_dumpDock; + bool _forcePartDock; + + KSelectAction *_saCost, *_saCost2, *saGroup; + KToggleAction *_partDockShown, *_stackDockShown; + KToggleAction *_functionDockShown, *_dumpDockShown; + KToggleAction *_taPercentage, *_taExpanded, *_taCycles, *_taHideTemplates; + 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 visualization + EventType* _eventType; + EventType* _eventType2; + // grouping of function list + ProfileContext::Type _groupType; + // selected group + TraceCostItem* _group; + // selected parts + TracePartList _activeParts; + // hidden parts + TracePartList _hiddenParts; + // layouts + int _layoutCurrent, _layoutCount; + + // for delayed slots + EventType* _eventTypeDelayed; + EventType* _eventType2Delayed; + ProfileContext::Type _groupTypeDelayed; + TraceCostItem* _groupDelayed; + CostItem* _traceItemDelayed; + QStringList _loadFilesDelayed; + TraceItemView::Direction _directionDelayed; + + // for status progress display + QString _progressMsg; + QTime _progressStart; + QProgressBar* _progressBar; + + // toplevel configuration options + bool _showPercentage, _showExpanded, _showCycles, _hideTemplates; + + // for running callgrind_control in the background + QProcess* _ccProcess; + QString _ccOutput; +}; + +#endif diff --git a/kcachegrind/libcore/BUGS b/kcachegrind/libcore/BUGS new file mode 100644 index 00000000..0118db70 --- /dev/null +++ b/kcachegrind/libcore/BUGS @@ -0,0 +1,19 @@ +Visualization state problem (16.12.08) +-------------------------------------- + +A view on profile data should use its own visualization state, such +as currently selected/activated item, attributes such as +absolute/percentage and so on. +A view can be console output, a toplevel window (we want to support +multiple for the same data!), or even subviews inside of a view +(different cost item activations (REALLY NEEDED)). + +Currently, this visualization state is stored either +* in the single Configuration object +* attribute of toplevel windows (eg. options from tool bar) + +Problems: +* Which configuration options persistent between runs of the app? + +Todo: +* Use separate VisualizationState objects diff --git a/kcachegrind/libcore/CMakeLists.txt b/kcachegrind/libcore/CMakeLists.txt new file mode 100644 index 00000000..dfbe0f66 --- /dev/null +++ b/kcachegrind/libcore/CMakeLists.txt @@ -0,0 +1,20 @@ +set(core_SRCS + context.cpp + costitem.cpp + eventtype.cpp + subcost.cpp + addr.cpp + tracedata.cpp + loader.cpp + cachegrindloader.cpp + fixcost.cpp + pool.cpp + coverage.cpp + stackbrowser.cpp + utils.cpp + logger.cpp + config.cpp + globalconfig.cpp ) + +qt4_automoc(${core_SRCS}) +add_library(core STATIC ${core_SRCS}) diff --git a/kcachegrind/libcore/addr.cpp b/kcachegrind/libcore/addr.cpp new file mode 100644 index 00000000..d74ed0b5 --- /dev/null +++ b/kcachegrind/libcore/addr.cpp @@ -0,0 +1,92 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#include "addr.h" + +//--------------------------------------------------- +// 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; + hex.reserve(16); + + 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; + hex.reserve(20); + + 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); +} diff --git a/kcachegrind/libcore/addr.h b/kcachegrind/libcore/addr.h new file mode 100644 index 00000000..7b5d671c --- /dev/null +++ b/kcachegrind/libcore/addr.h @@ -0,0 +1,62 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#ifndef ADDR_H +#define ADDR_H + +#include + +#include "utils.h" + +/** + * 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; +}; + +#endif // ADDR_H diff --git a/kcachegrind/libcore/cachegrindloader.cpp b/kcachegrind/libcore/cachegrindloader.cpp new file mode 100644 index 00000000..d114946b --- /dev/null +++ b/kcachegrind/libcore/cachegrindloader.cpp @@ -0,0 +1,1350 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2010 Josef Weidendorfer + + 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. +*/ + +#include "loader.h" + +#include +#include +#include + +#include "addr.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 canLoad(QIODevice* file); + int load(TraceData*, QIODevice* file, const QString& filename); + +private: + void error(QString); + void warning(QString); + + int loadInternal(TraceData*, QIODevice* file, const QString& filename); + + 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&); + + void prepareNewPart(); + + QString _emptyString; + + // current line in file to read in + QString _filename; + int _lineNo; + + EventTypeMapping* mapping; + TraceData* _data; + TracePart* _part; + int partsAdded; + + // current position + lineType nextLineType; + bool hasLineInfo, hasAddrInfo; + PositionSpec currentPos; + + // current function/line + TraceObject* currentObject; + TracePartObject* currentPartObject; + TraceFile* currentFile; + TraceFile* currentFunctionFile; + 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 + * "() Name": this is a compression specification, + * mapping the integer number to Name and using Name. + * "()" : 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*); + + QVector _objectVector, _fileVector, _functionVector; +}; + + + +/********************************************************** + * Loader + */ + + +CachegrindLoader::CachegrindLoader() + : Loader("Callgrind", + QObject::tr( "Import filter for Cachegrind/Callgrind generated profile data files") ) +{ + _emptyString = QString(""); +} + +bool CachegrindLoader::canLoad(QIODevice* file) +{ + if (!file) return false; + + Q_ASSERT(file->isOpen()); + + /* + * We recognize this as cachegrind/callgrind format if in the first + * 2047 bytes we see the string "\nevents:" + */ + char buf[2048]; + int read = file->read(buf,2047); + if (read < 0) + return false; + buf[read] = 0; + + QByteArray s = QByteArray::fromRawData(buf, read+1); + int pos = s.indexOf("events:"); + if (pos>0 && buf[pos-1] != '\n') pos = -1; + return (pos>=0); +} + +int CachegrindLoader::load(TraceData* d, + QIODevice* file, const QString& filename) +{ + /* do the loading in a new object so parallel load + * operations do not interfere each other. + */ + CachegrindLoader l; + + l.setLogger(_logger); + + return l.loadInternal(d, file, filename); +} + +Loader* createCachegrindLoader() +{ + return new CachegrindLoader(); +} + +void CachegrindLoader::error(QString msg) +{ + loadError(_lineNo, msg); +} + +void CachegrindLoader::warning(QString msg) +{ + loadWarning(_lineNo, msg); +} + +/** + * 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) + qDebug() << " Got Addr " << newPos.fromAddr.toString(); + else + qDebug() << " Got AddrRange " << newPos.fromAddr.toString() + << ":" << newPos.toAddr.toString(); +#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) { + error(QString("Negative line number %1") + .arg((int)currentPos.fromLine - (int)diff)); + 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) + qDebug() << " Got Line " << newPos.fromLine; + else + qDebug() << " Got LineRange " << newPos.fromLine + << ":" << newPos.toLine; +#endif + + } + + return true; +} + +// Support for compressed strings +void CachegrindLoader::clearCompression() +{ + // this does not 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.indexOf(')'); + if (p<2) { + error(QString("Invalid compressed ELF object ('%1')").arg(name)); + return 0; + } + int index = name.mid(1, p-1).toInt(); + TraceObject* o = 0; + p++; + while((name.length()>p) && name.at(p).isSpace()) p++; + if (name.length()>p) { + if (_objectVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + qDebug() << " CachegrindLoader: objectVector enlarged to " + << newSize; +#endif + _objectVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + o = (TraceObject*) _objectVector.at(index); + if (o && (o->name() != realName)) { + error(QString("Redefinition of compressed ELF object index %1 (was '%2') to %3") + .arg(index).arg(o->name()).arg(realName)); + } + + o = _data->object(realName); + _objectVector.replace(index, o); + } + else { + if ((_objectVector.size() <= index) || + ( (o=(TraceObject*)_objectVector.at(index)) == 0)) { + error(QString("Undefined compressed ELF object index %1").arg(index)); + 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.indexOf(')'); + if (p<2) { + error(QString("Invalid compressed file ('%1')").arg(name)); + return 0; + } + int index = name.mid(1, p-1).toUInt(); + TraceFile* f = 0; + p++; + while((name.length()>p) && name.at(p).isSpace()) p++; + if (name.length()>p) { + if (_fileVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + qDebug() << " CachegrindLoader::fileVector enlarged to " + << newSize; +#endif + _fileVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + f = (TraceFile*) _fileVector.at(index); + if (f && (f->name() != realName)) { + error(QString("Redefinition of compressed file index %1 (was '%2') to %3") + .arg(index).arg(f->name()).arg(realName)); + } + + f = _data->file(realName); + _fileVector.replace(index, f); + } + else { + if ((_fileVector.size() <= index) || + ( (f=(TraceFile*)_fileVector.at(index)) == 0)) { + error(QString("Undefined compressed file index %1").arg(index)); + 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.indexOf(')'); + if (p<2) { + error(QString("Invalid compressed function ('%1')").arg(name)); + return 0; + } + + + int index = name.mid(1, p-1).toUInt(); + TraceFunction* f = 0; + p++; + while((name.length()>p) && name.at(p).isSpace()) p++; + if (name.length()>p) { + if (_functionVector.size() <= index) { + int newSize = index * 2; +#if TRACE_LOADER + qDebug() << " CachegrindLoader::functionVector enlarged to " + << newSize; +#endif + _functionVector.resize(newSize); + } + + QString realName = checkUnknown(name.mid(p)); + f = (TraceFunction*) _functionVector.at(index); + if (f && (f->name() != realName)) { + error(QString("Redefinition of compressed function index %1 (was '%2') to %3") + .arg(index).arg(f->name()).arg(realName)); + } + + f = _data->function(realName, file, object); + _functionVector.replace(index, f); + +#if TRACE_LOADER + qDebug() << "compressedFunction: Inserted at Index " << index + << "\n " << f->fullName() + << "\n in " << f->cls()->fullName() + << "\n in " << f->file()->fullName() + << "\n in " << f->object()->fullName(); +#endif + } + else { + if ((_functionVector.size() <= index) || + ( (f=(TraceFunction*)_functionVector.at(index)) == 0)) { + error(QString("Undefined compressed function index %1").arg(index)); + 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) { + error(QString("Invalid ELF object specification, setting to unknown")); + + currentObject = _data->object(_emptyString); + } + + currentPartObject = currentObject->partObject(_part); + currentFunction = 0; + currentPartFunction = 0; +} + +void CachegrindLoader::setCalledObject(const QString& name) +{ + currentCalledObject = compressedObject(name); + + if (!currentCalledObject) { + error(QString("Invalid specification of called ELF object, setting to unknown")); + + 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) { + error(QString("Invalid file specification, setting to unknown")); + + currentFile = _data->file(_emptyString); + } + + currentPartFile = currentFile->partFile(_part); + currentLine = 0; + currentPartLine = 0; +} + +void CachegrindLoader::setCalledFile(const QString& name) +{ + currentCalledFile = compressedFile(name); + + if (!currentCalledFile) { + error(QString("Invalid specification of called file, setting to unknown")); + + 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; + + error(QString("Function not specified, setting to unknown")); + + 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) { + error(QString("Invalid function specification, setting to unknown")); + + 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) { + error("Invalid called function, setting to unknown"); + + 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; + currentFunctionFile = 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; + + mapping = 0; +} + +void CachegrindLoader::prepareNewPart() +{ + if (_part) { + // really new part needed? + if (mapping == 0) return; + + // yes + _part->invalidate(); + _part->totals()->clear(); + _part->totals()->addCost(_part); + _data->addPart(_part); + partsAdded++; + } + + clearCompression(); + clearPosition(); + + _part = new TracePart(_data); + _part->setName(_filename); +} + +/** + * The main import function... + */ +int CachegrindLoader::loadInternal(TraceData* data, + QIODevice* device, const QString& filename) +{ + if (!data || !device) return 0; + + _data = data; + _filename = filename; + _lineNo = 0; + + loadStart(_filename); + + FixFile file(device, _filename); + if (!file.exists()) { + loadFinished("File does not exist"); + return 0; + } + + int statusProgress = 0; + +#if USE_FIXCOST + // FixCost Memory Pool + FixPool* pool = _data->fixPool(); +#endif + + _part = 0; + partsAdded = 0; + prepareNewPart(); + + FixString line; + char c; + + // current position + nextLineType = SelfCost; + // default if there is no "positions:" line + hasLineInfo = true; + hasAddrInfo = false; + + while (file.nextLine(line)) { + + _lineNo++; + +#if TRACE_LOADER + qDebug() << "[CachegrindLoader] " << _filename << ":" << _lineNo + << " - '" << QString(line) << "'"; +#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)) { + error(QString("Invalid position specification '%1'").arg(line)); + continue; + } + + // go through after big switch + } + else { // if (c > '9') + + line.stripFirst(c); + + /* in order of probability */ + switch(c) { + + case 'f': + + // fl= + if (line.stripPrefix("l=")) { + + setFile(line); + // this is the default for new functions + currentFunctionFile = currentFile; + continue; + } + + // fi=, fe= + if (line.stripPrefix("i=") || + line.stripPrefix("e=")) { + + setFile(line); + continue; + } + + // fn= + if (line.stripPrefix("n=")) { + + if (currentFile != currentFunctionFile) + currentFile = currentFunctionFile; + 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. + */ + loadProgress(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).trimmed(); + if (!_data->command().isEmpty() && + _data->command() != command) { + + error(QString("Redefined command, was '%1'").arg(_data->command())); + } + _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) { + error(QString("Invalid line after 'jcnd'")); + } + else + nextLineType = CondJump; + continue; + } + + if (line.stripPrefix("ump=")) { + bool valid; + + valid = line.stripUInt64(jumpsExecuted) && + parsePosition(line, targetPos); + + if (!valid) { + error(QString("Invalid line after 'jump'")); + } + 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:")) { + prepareNewPart(); + _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:")) { + prepareNewPart(); + mapping = _data->eventTypes()->createMapping(line); + _part->setEventMapping(mapping); + continue; + } + + // event:[=][:] + if (line.stripPrefix("vent:")) { + line.stripSurroundingSpaces(); + + FixString e, f, l; + if (!line.stripName(e)) { + error(QString("Invalid event")); + 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; + EventType::add(new EventType(e,line,f)); + continue; + } + break; + + case 'p': + + // part: + if (line.stripPrefix("art:")) { + prepareNewPart(); + _part->setPartNumber(QString(line).toInt()); + continue; + } + + // pid: + if (line.stripPrefix("id:")) { + prepareNewPart(); + _part->setProcessID(QString(line).toInt()); + continue; + } + + // positions: + if (line.stripPrefix("ositions:")) { + prepareNewPart(); + QString positions(line); + hasLineInfo = positions.contains("line"); + hasAddrInfo = positions.contains("instr"); + continue; + } + break; + + case 'v': + + // version: + if (line.stripPrefix("ersion:")) { + // ignore for now + continue; + } + break; + + case 's': + + // summary: + if (line.stripPrefix("ummary:")) { + if (!mapping) { + error(QString("No event line found. Skipping file")); + delete _part; + return false; + } + + _part->totals()->set(mapping, 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; + + warning(QString("Old file format using deprecated 'rcalls'")); + continue; + } + break; + + default: + break; + } + + error(QString("Invalid line '%1%2'").arg(c).arg(line)); + continue; + } + + if (!mapping) { + error(QString("No event line found. Skipping file")); + delete _part; + return false; + } + + // for a cost line, we always need a current function + ensureFunction(); + + + + if (!currentFunctionSource || + (currentFunctionSource->file() != currentFile)) { + currentFunctionSource = currentFunction->sourceFile(currentFile, + true); + } + +#if !USE_FIXCOST + if (hasAddrInfo) { + if (!currentInstr || + (currentInstr->addr() != currentPos.fromAddr)) { + currentInstr = currentFunction->instr(currentPos.fromAddr, + true); + + if (!currentInstr) { + error(QString("Invalid address '%1'").arg(currentPos.fromAddr.toString())); + + continue; + } + + currentPartInstr = currentInstr->partInstr(_part, + currentPartFunction); + } + } + + if (hasLineInfo) { + if (!currentLine || + (currentLine->lineno() != currentPos.fromLine)) { + + currentLine = currentFunctionSource->line(currentPos.fromLine, + true); + currentPartLine = currentLine->partLine(_part, + currentPartFunction); + } + if (hasAddrInfo && currentInstr) + currentInstr->setLine(currentLine); + } +#endif + +#if TRACE_LOADER + qDebug() << _filename << ":" << _lineNo; + qDebug() << " currentInstr " + << (currentInstr ? qPrintable(currentInstr->toString()) : "."); + qDebug() << " currentLine " + << (currentLine ? qPrintable(currentLine->toString()) : ".") + << "( file " << currentFile->name() << ")"; + qDebug() << " currentFunction " + << qPrintable(currentFunction->prettyName()); + qDebug() << " currentCalled " + << (currentCalledFunction ? qPrintable(currentCalledFunction->prettyName()) : "."); +#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 back after reading for the line + int l = line.len(); + const char* s = line.ascii(); + + partInstr->addCost(mapping, line); + line.set(s,l); + } + else + partInstr->addCost(mapping, line); + } + + if (hasLineInfo) { + TracePartLine* partLine; + partLine = currentLine->partLine(_part, currentPartFunction); + partLine->addCost(mapping, line); + } +#endif + + if (!line.isEmpty()) { + error(QString("Garbage at end of cost line ('%1')").arg(line)); + } + } + 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 back after reading for the line + int l = line.len(); + const char* s = line.ascii(); + + partInstrCall->addCost(mapping, line); + line.set(s,l); + } + else + partInstrCall->addCost(mapping, 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(mapping, line); + + // update maximum of call cost + _data->callMax()->maxCost(partLineCall); + } +#endif + currentCalledFile = 0; + currentCalledPartFile = 0; + currentCalledObject = 0; + currentCalledPartObject = 0; + currentCallCount = 0; + + if (!line.isEmpty()) { + error(QString("Garbage at end of call cost line ('%1')").arg(line)); + } + } + 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); +#else + if (hasAddrInfo) { + TraceInstr* jumpToInstr; + TraceInstrJump* instrJump; + TracePartInstrJump* partInstrJump; + + jumpToInstr = currentJumpToFunction->instr(targetPos.fromAddr, + true); + instrJump = currentInstr->instrJump(jumpToInstr, + (nextLineType == CondJump)); + partInstrJump = instrJump->partInstrJump(_part); + partInstrJump->addExecutedCount(jumpsExecuted); + if (nextLineType == CondJump) + partInstrJump->addFollowedCount(jumpsFollowed); + } + + if (hasLineInfo) { + TraceLine* jumpToLine; + TraceLineJump* lineJump; + TracePartLineJump* partLineJump; + + jumpToLine = targetSource->line(targetPos.fromLine, true); + lineJump = currentLine->lineJump(jumpToLine, + (nextLineType == CondJump)); + partLineJump = lineJump->partLineJump(_part); + + partLineJump->addExecutedCount(jumpsExecuted); + if (nextLineType == CondJump) + partLineJump->addFollowedCount(jumpsFollowed); + } +#endif + + if (0) { + qDebug() << _filename << ":" << _lineNo + << " - jump from 0x" << currentPos.fromAddr.toString() + << " (line " << currentPos.fromLine + << ") to 0x" << targetPos.fromAddr.toString() + << " (line " << targetPos.fromLine << ")"; + + if (nextLineType == BoringJump) + qDebug() << " Boring Jump, count " << jumpsExecuted.pretty(); + else + qDebug() << " Cond. Jump, followed " << jumpsFollowed.pretty() + << ", executed " << jumpsExecuted.pretty(); + } + + nextLineType = SelfCost; + currentJumpToFunction = 0; + currentJumpToFile = 0; + + if (!line.isEmpty()) { + error(QString("Garbage at end of jump cost line ('%1')").arg(line)); + } + + } + } + + loadFinished(); + + if (mapping) { + _part->invalidate(); + _part->totals()->clear(); + _part->totals()->addCost(_part); + data->addPart(_part); + partsAdded++; + } + else { + delete _part; + } + + device->close(); + + return partsAdded; +} + diff --git a/kcachegrind/libcore/config.cpp b/kcachegrind/libcore/config.cpp new file mode 100644 index 00000000..75fed0e5 --- /dev/null +++ b/kcachegrind/libcore/config.cpp @@ -0,0 +1,101 @@ +/* This file is part of KCachegrind. + Copyright (C) 2008 Josef Weidendorfer + + 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. +*/ + +#include "config.h" + +#include +#include + +// helper functions + +QList toIntList(QStringList l) +{ + QList iList; + + foreach(const QString& s, l) + iList << s.toInt(); + + return iList; +} + +QStringList toStringList(QList l) +{ + QStringList sList; + + foreach(int i, l) + sList << QString::number(i); + + return sList; +} + + +// +// ConfigGroup +// + +ConfigGroup::ConfigGroup() +{} + +ConfigGroup::~ConfigGroup() +{} + +void ConfigGroup::setValue(const QString&, const QVariant&, const QVariant&) +{} + +QVariant ConfigGroup::value(const QString&, const QVariant& def) const +{ + return def; +} + + +// +// ConfigStorage +// + +ConfigStorage* ConfigStorage::_storage = 0; + +ConfigStorage::ConfigStorage() +{ + _storage = 0; +} + +ConfigStorage::~ConfigStorage() +{} + +ConfigGroup* ConfigStorage::group(const QString& group, + const QString& optSuffix) +{ + Q_ASSERT(_storage != 0); + + return _storage->getGroup(group, optSuffix); +} + +void ConfigStorage::setStorage(ConfigStorage* storage) +{ + _storage = storage; +} + +void ConfigStorage::cleanup() +{ + delete _storage; +} + +ConfigGroup* ConfigStorage::getGroup(const QString&, const QString&) +{ + return new ConfigGroup(); +} diff --git a/kcachegrind/libcore/config.h b/kcachegrind/libcore/config.h new file mode 100644 index 00000000..c848e30e --- /dev/null +++ b/kcachegrind/libcore/config.h @@ -0,0 +1,85 @@ +/* This file is part of KCachegrind. + Copyright (C) 2008 Josef Weidendorfer + + 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. +*/ + +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +class ConfigStorage; + +// helper functions for storing specific values +QList toIntList(QStringList l); +QStringList toStringList(QList l); + +/** + * A group of configuration settings. + * Actual implementation for the backends is done in derived classes. + */ +class ConfigGroup +{ + friend class ConfigStorage; + +public: + virtual ~ConfigGroup(); + + // when value is defaultValue, any previous stored value is removed + virtual void setValue(const QString& key, const QVariant& value, + const QVariant& defaultValue = QVariant()); + virtual QVariant value(const QString & key, + const QVariant& defaultValue) const; + + // the template version is needed for GUI Qt types + template + inline T value(const QString & key, + const QVariant & defaultValue = QVariant()) const + { return qvariant_cast( value(key, defaultValue) ); } + +protected: + ConfigGroup(); +}; + +/** + * This is an adapter class for different configuration backends. + * A singleton. + */ + +class ConfigStorage +{ +public: + ConfigStorage(); + virtual ~ConfigStorage(); + + // if second parameter is not-empty, first search for an existing + // group using the optional suffix, and then without. + // the group gets readonly. + static ConfigGroup* group(const QString& group, + const QString& optSuffix = QString()); + static void setStorage(ConfigStorage*); + static void cleanup(); + +protected: + virtual ConfigGroup* getGroup(const QString& group, + const QString& optSuffix); + + static ConfigStorage* _storage; +}; + + +#endif // CONFIG_H diff --git a/kcachegrind/libcore/context.cpp b/kcachegrind/libcore/context.cpp new file mode 100644 index 00000000..76c42fd9 --- /dev/null +++ b/kcachegrind/libcore/context.cpp @@ -0,0 +1,141 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#include + +#include "context.h" + +//--------------------------------------------------- +// ProfileContext + +ProfileContext* ProfileContext::_contexts = 0; +QString* ProfileContext::_typeName = 0; +QString* ProfileContext::_i18nTypeName = 0; + + +ProfileContext::ProfileContext(ProfileContext::Type t) +{ + _type = t; +} + +ProfileContext* ProfileContext::context(ProfileContext::Type t) +{ + if (!_contexts) { + _contexts = new ProfileContext[MaxType+1]; + for(int i=0;i<=MaxType;i++) + _contexts[i] = ProfileContext((ProfileContext::Type)i); + } + return &(_contexts[t]); +} + +void ProfileContext::cleanup() +{ + if (_typeName) { + delete [] _typeName; + _typeName = 0; + } + if (_i18nTypeName) { + delete [] _i18nTypeName; + _i18nTypeName = 0; + } + if (_contexts) { + delete [] _contexts; + _contexts = 0; + } +} + +QString ProfileContext::typeName(ProfileContext::Type t) +{ + if (!_typeName) { + _typeName = new QString [MaxType+1]; + QString* strs = _typeName; + for(int i=0;i<=MaxType;i++) + strs[i] = QString("?"); + + strs[InvalidType] = QT_TR_NOOP("Invalid Context"); + strs[UnknownType] = QT_TR_NOOP("Unknown Context"); + strs[PartLine] = QT_TR_NOOP("Part Source Line"); + strs[Line] = QT_TR_NOOP("Source Line"); + strs[PartLineCall] = QT_TR_NOOP("Part Line Call"); + strs[LineCall] = QT_TR_NOOP("Line Call"); + strs[PartLineJump] = QT_TR_NOOP("Part Jump"); + strs[LineJump] = QT_TR_NOOP("Jump"); + strs[PartInstr] = QT_TR_NOOP("Part Instruction"); + strs[Instr] = QT_TR_NOOP("Instruction"); + strs[PartInstrJump] = QT_TR_NOOP("Part Instruction Jump"); + strs[InstrJump] = QT_TR_NOOP("Instruction Jump"); + strs[PartInstrCall] = QT_TR_NOOP("Part Instruction Call"); + strs[InstrCall] = QT_TR_NOOP("Instruction Call"); + strs[PartCall] = QT_TR_NOOP("Part Call"); + strs[Call] = QT_TR_NOOP("Call"); + strs[PartFunction] = QT_TR_NOOP("Part Function"); + strs[FunctionSource] = QT_TR_NOOP("Function Source File"); + strs[Function] = QT_TR_NOOP("Function"); + strs[FunctionCycle] = QT_TR_NOOP("Function Cycle"); + strs[PartClass] = QT_TR_NOOP("Part Class"); + strs[Class] = QT_TR_NOOP("Class"); + strs[PartFile] = QT_TR_NOOP("Part Source File"); + strs[File] = QT_TR_NOOP("Source File"); + strs[PartObject] = QT_TR_NOOP("Part ELF Object"); + strs[Object] = QT_TR_NOOP("ELF Object"); + strs[Part] = QT_TR_NOOP("Profile Part"); + strs[Data] = QT_TR_NOOP("Program Trace"); + } + if (t<0 || t> MaxType) t = MaxType; + return _typeName[t]; +} + +ProfileContext::Type ProfileContext::type(const QString& s) +{ + // This is the default context type + if (s.isEmpty()) return Function; + + Type type; + for (int i=0; i MaxType) t = MaxType; + return _i18nTypeName[t]; +} + +ProfileContext::Type ProfileContext::i18nType(const QString& s) +{ + // This is the default context type + if (s.isEmpty()) return Function; + + Type type; + for (int i=0; i + + 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. +*/ + +#ifndef CONTEXT_H +#define CONTEXT_H + +#include +#include + +/** + * Base class for source contexts which event costs contained + * in a ProfileData instance, ie. a profiling experiment, can relate to. + * + * This only includes code/source related context. Any other cost + * context such as thread number, see DataContext, which relates to ProfileData. + */ +class ProfileContext +{ +public: + // RTTI for trace item classes, using type() method + enum Type { + InvalidType = 0, UnknownType, + 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, + MaxType }; + + ProfileContext(ProfileContext::Type = InvalidType); + + ProfileContext::Type type() { return _type; } + + static ProfileContext* context(ProfileContext::Type); + + // conversion of context type to locale independent string (e.g. for config) + static QString typeName(Type); + static Type type(const QString&); + // the versions below should be used for user visible strings, as + // these use localization settings + static QString i18nTypeName(Type); + static Type i18nType(const QString&); + + // clean up some static data + static void cleanup(); + +private: + Type _type; + + static ProfileContext* _contexts; + static QString* _typeName; + static QString* _i18nTypeName; +}; + +#endif // CONTEXT_H diff --git a/kcachegrind/libcore/costitem.cpp b/kcachegrind/libcore/costitem.cpp new file mode 100644 index 00000000..1cd2bb43 --- /dev/null +++ b/kcachegrind/libcore/costitem.cpp @@ -0,0 +1,618 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#include "costitem.h" + +#include "tracedata.h" + +#define TRACE_DEBUG 0 +#define TRACE_ASSERTIONS 0 + +//--------------------------------------------------- +// ProfileCost + +CostItem::CostItem(ProfileContext* c) +{ + _position = 0; + _dep = 0; + _dirty = true; + + _context = c; +} + +CostItem::~CostItem() +{} + + +void CostItem::clear() +{ + invalidate(); +} + + +QString CostItem::costString(EventTypeSet*) +{ + return QString("(no cost)"); +} + +QString CostItem::name() const +{ + if (part()) { + return QObject::tr("%1 from %2").arg(_dep->name()).arg(part()->name()); + } + + if (_dep) + return _dep->name(); + + return QObject::tr("(unknown)"); +} + +QString CostItem::prettyName() const +{ + if (name().isEmpty()) return QObject::tr("(unknown)"); + return name(); +} + +QString CostItem::formattedName() const +{ + return QString(); +} + +QString CostItem::fullName() const +{ + return QString("%1 %2") + .arg(ProfileContext::typeName(type())).arg(prettyName()); +} + +QString CostItem::toString() +{ + return QString("%1\n [%3]").arg(fullName()).arg(costString(0)); +} + +void CostItem::invalidate() +{ + if (_dirty) return; + _dirty = true; + + if (_dep) + _dep->invalidate(); +} + +void CostItem::update() +{ + _dirty = false; +} + +TracePart* CostItem::part() +{ + return _position ? _position->part() : 0; +} + +const TracePart* CostItem::part() const +{ + return _position ? _position->part() : 0; +} + +TraceData* CostItem::data() +{ + return _position ? _position->data() : 0; +} + +const TraceData* CostItem::data() const +{ + return _position ? _position->data() : 0; +} + + +//--------------------------------------------------- +// ProfileCostArray + + +const int ProfileCostArray::MaxRealIndex = MaxRealIndexValue; +const int ProfileCostArray::InvalidIndex = -1; + + +ProfileCostArray::ProfileCostArray(ProfileContext* context) + : CostItem(context) +{ + _cachedType = 0; // no virtual value cached + _allocCount = 0; + _count = 0; + _cost = 0; +} + +ProfileCostArray::ProfileCostArray() + : CostItem(ProfileContext::context(ProfileContext::UnknownType)) +{ + _cachedType = 0; // no virtual value cached + _allocCount = 0; + _count = 0; + _cost = 0; +} + +ProfileCostArray::~ProfileCostArray() +{ + if (_cost) delete[] _cost; +} + + +void ProfileCostArray::clear() +{ + _count = 0; + invalidate(); +} + +void ProfileCostArray::reserve(int count) +{ + if (count <= _allocCount) return; + + SubCost* newcost = new SubCost[count]; + if (_cost) { + /* first _count values are valid and have to be preserved */ + for(int i=0; i<_count; i++) + newcost[i] = _cost[i]; + delete[] _cost; + } + _cost = newcost; + _allocCount = count; +} + +void ProfileCostArray::set(EventTypeMapping* mapping, const char* s) +{ + if (!mapping) return; + if (!s) { + clear(); + return; + } + + reserve(mapping->set()->realCount()); + + while(*s == ' ') s++; + + if (mapping->isIdentity()) { + int i = 0; + while(icount()) { + if (!_cost[i].set(&s)) break; + i++; + } + _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + index = mapping->realIndex(i); + if (maxIndexfirstUnused(); i<=maxIndex; i=mapping->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex; + } + Q_ASSERT(_count <= _allocCount); + // a cost change has to be propagated (esp. in subclasses) + invalidate(); +} + +void ProfileCostArray::set(EventTypeMapping* mapping, FixString & s) +{ + if (!mapping) return; + s.stripSpaces(); + if (s.isEmpty()) { + clear(); + return; + } + + reserve(mapping->set()->realCount()); + + if (mapping->isIdentity()) { + int i = 0; + while(icount()) { + if (!s.stripUInt64(_cost[i])) break; + i++; + } + _count = i; + } + else { + int i = 0, maxIndex = 0, index; + while(1) { + index = mapping->realIndex(i); + if (maxIndexfirstUnused(); i<=maxIndex; i=mapping->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + Q_ASSERT(_count <= _allocCount); + invalidate(); +} + + +void ProfileCostArray::addCost(EventTypeMapping* mapping, const char* s) +{ + if (!mapping || !s) return; + reserve(mapping->set()->realCount()); + + SubCost v; + if (mapping->isIdentity()) { + int i = 0; + while(icount()) { + 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 = mapping->realIndex(i); + if (maxIndex= _count) { + /* we have to set all costs of unused indexes in the interval + * [_count;maxIndex] to zero */ + for(i=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + Q_ASSERT(_count <= _allocCount); + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // do not recurse ! + qDebug("%s\n now %s", qPrintable( fullName() ), + qPrintable( ProfileCostArray::costString(0) )); + _dirty = true; // because of invalidate() +#endif +} + +void ProfileCostArray::addCost(EventTypeMapping* mapping, FixString & s) +{ + if (!mapping) return; + s.stripSpaces(); + if (s.isEmpty()) return; + reserve(mapping->set()->realCount()); + + SubCost v; + if (mapping->isIdentity()) { + int i = 0; + while(icount()) { + 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 = mapping->realIndex(i); + if (maxIndex= _count) { + /* we have to set all costs of unused indexes in the interval + * [_count;maxIndex] to zero */ + for(i=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + Q_ASSERT(_count <= _allocCount); + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // do not recurse ! + qDebug("%s\n now %s", qPrintable( fullName() ), + qPrintable( ProfileCostArray::costString(0 ) ) ); + _dirty = true; // because of invalidate() +#endif +} + + +// update each subcost to be maximum of old and given costs +void ProfileCostArray::maxCost(EventTypeMapping* mapping, FixString & s) +{ + if (!mapping) return; + s.stripSpaces(); + if (s.isEmpty()) return; + reserve(mapping->set()->realCount()); + + SubCost v; + if (mapping->isIdentity()) { + int i = 0; + while(icount()) { + 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 = mapping->realIndex(i); + if (maxIndex_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=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i)) + _cost[i] = 0; + _count = maxIndex+1; + } + } + + Q_ASSERT(_count <= _allocCount); + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // do not recurse ! + qDebug("%s\n now %s", qPrintable( fullName() ), + qPrintable(ProfileCostArray::costString(0))); + _dirty = true; // because of invalidate() +#endif +} + + +void ProfileCostArray::addCost(ProfileCostArray* 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(); + + // make sure we have enough space allocated + reserve(item->_count); + + if (item->_count < _count) { + for (i = 0; i_count; i++) + _cost[i] += item->_cost[i]; + } + else { + for (i = 0; i<_count; i++) + _cost[i] += item->_cost[i]; + for (; i_count; i++) + _cost[i] = item->_cost[i]; + _count = item->_count; + } + + Q_ASSERT(_count <= _allocCount); + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // do not recurse ! + qDebug("%s added cost item\n %s\n now %s", + qPrintable( fullName() ), qPrintable(item->fullName()), + qPrintable(ProfileCostArray::costString(0))); + _dirty = true; // because of invalidate() +#endif +} + +void ProfileCostArray::maxCost(ProfileCostArray* 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(); + + // make sure we have enough space allocated + reserve(item->_count); + + if (item->_count < _count) { + for (i = 0; i_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_count; i++) + _cost[i] = item->_cost[i]; + _count = item->_count; + } + + Q_ASSERT(_count <= _allocCount); + invalidate(); + +#if TRACE_DEBUG + _dirty = false; // do not recurse ! + qDebug("%s added cost item\n %s\n now %s", + qPrintable( fullName() ), qPrintable(item->fullName()), + qPrintable( ProfileCostArray::costString(0) )); + _dirty = true; // because of invalidate() +#endif +} + +void ProfileCostArray::addCost(int realIndex, SubCost value) +{ + if (realIndex<0 || realIndex>=MaxRealIndex) return; + if (value == 0) return; + + reserve(realIndex+1); + if (realIndex < _count) + _cost[realIndex] += value; + else { + for(int i=_count;i=MaxRealIndex) return; + + reserve(realIndex+1); + if (realIndex<_count) { + if (value>_cost[realIndex]) _cost[realIndex] = value; + } + else { + for(int i=_count;i_dirty) item->update(); + + int maxCount = (item->_count > _count) ? item->_count : _count; + + res.reserve(maxCount); + for (int i=0; isubCost(i) - subCost(i); + res._count = maxCount; + + Q_ASSERT(res._count <= res._allocCount); + return res; +} + +QString ProfileCostArray::costString(EventTypeSet* set) +{ + QString res; + + if (_dirty) update(); + + int maxIndex = set ? set->realCount() : ProfileCostArray::MaxRealIndex; + for (int i = 0; itype(i)->name() + ' '; + + res += subCost(i).pretty(); + } + return res; +} + + +void ProfileCostArray::invalidate() +{ + if (_dirty) return; + _dirty = true; + _cachedType = 0; // cached value is invalid, too + + if (_dep) + _dep->invalidate(); +} + +void ProfileCostArray::update() +{ + _dirty = false; +} + +// this is only for real types +SubCost ProfileCostArray::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 ProfileCostArray::subCost(EventType* t) +{ + if (!t) return 0; + if (_cachedType != t) { + _cachedType = t; + _cachedCost = t->subCost(this); + } + return _cachedCost; +} + +QString ProfileCostArray::prettySubCost(EventType* t) +{ + return subCost(t).pretty(); +} + +QString ProfileCostArray::prettySubCostPerCall(EventType* t, uint64 calls) +{ + if (calls == 0) { + /* For callgrind, a call count of zero means that + * a function was already active when measuring started + * (even without that possibility, we never should crash). + * To show full cost, set to 1. + */ + calls = 1; + } + return SubCost(subCost(t) / calls).pretty(); +} + diff --git a/kcachegrind/libcore/costitem.h b/kcachegrind/libcore/costitem.h new file mode 100644 index 00000000..094f072a --- /dev/null +++ b/kcachegrind/libcore/costitem.h @@ -0,0 +1,212 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#ifndef COST_H +#define COST_H + +#include + +#include "subcost.h" +#include "context.h" +#include "utils.h" + +class EventType; +class EventTypeSet; +class EventTypeMapping; +class TracePart; +class TraceData; + +/** + * Base class for cost items. + */ +class CostItem +{ +public: + + CostItem(ProfileContext*); + virtual ~CostItem(); + + ProfileContext* context() const { return _context; } + ProfileContext::Type type() const { return context()->type(); } + + /** + * Returns dynamic name info (without type) + */ + virtual QString name() const; + + /** + * Similar to name, but prettyfied = more descriptive to humans + */ + virtual QString prettyName() const; + + /** + * A HTMLified version of name, can return empty string + */ + virtual QString formattedName() const; + + /** + * Returns text of all cost metrics + */ + virtual QString costString(EventTypeSet*); + + /** + * 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(CostItem* d) { _dep = d; } + + CostItem* 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(CostItem* p) { _position = p; } + + /** + * Redefine the context after construction + */ + void setContext(ProfileContext* context) { _context = context; } + + // 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(); + + ProfileContext* _context; + bool _dirty; + + CostItem* _position; + CostItem* _dep; +}; + + +// The maximal number of subcosts in a ProfileCostArray and +// event types in a EventSet. Does not really matter for memory +// consumption as cost for a ProfileCostArray is dynamically +// allocated depending on used number of event types, and there +// will be only a low number of objects for EventType{,Set,Mapping}. +#define MaxRealIndexValue 200 + +/** + * An array of basic cost metrics for a trace item. + * + * The semantic of specific indexes is stored in the + * EventTypeSet of the TraceData object holding this ProfileCostArray. + */ +class ProfileCostArray: public CostItem +{ + friend class EventType; +public: + /** + */ + static const int MaxRealIndex; + static const int InvalidIndex; + + + ProfileCostArray(ProfileContext*); + ProfileCostArray(); + virtual ~ProfileCostArray(); + + virtual QString costString(EventTypeSet*); + + virtual void clear(); + + // reserve space for cost + void reserve(int); + + // set costs according to the mapping order of event types + void set(EventTypeMapping*, const char*); + void set(EventTypeMapping*, FixString&); + // add costs according to the mapping order of event types + void addCost(EventTypeMapping*, const char*); + void addCost(EventTypeMapping*, FixString&); + // add the cost of another item + void addCost(ProfileCostArray* item); + void addCost(int index, SubCost value); + + // maximal cost + void maxCost(EventTypeMapping*, FixString&); + void maxCost(ProfileCostArray* item); + void maxCost(int index, SubCost value); + ProfileCostArray diff(ProfileCostArray* item); + + virtual void invalidate(); + + /** Returns a sub cost. This automatically triggers + * a call to update() if needed. + */ + SubCost subCost(EventType*); + + /** Returns a cost attribute converted to a string + * (with space after every 3 digits) + */ + QString prettySubCost(EventType*); + + QString prettySubCostPerCall(EventType* t, uint64 calls); + + protected: + virtual void update(); + + private: + // Only used by friend class EventType: return subcost by index + SubCost subCost(int); + + SubCost* _cost; + int _count; // only _count first indexes of _cost are used + int _allocCount; // number of allocated subcost entries + + // cache last virtual subcost for faster access + SubCost _cachedCost; + EventType* _cachedType; +}; + + +#endif // COST_H diff --git a/kcachegrind/libcore/coverage.cpp b/kcachegrind/libcore/coverage.cpp new file mode 100644 index 00000000..2dd3d35b --- /dev/null +++ b/kcachegrind/libcore/coverage.cpp @@ -0,0 +1,319 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer + + 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. +*/ + +/* + * Function Coverage Analysis + */ + +#include "coverage.h" + +//#define DEBUG_COVERAGE 1 + +EventType* 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;imaxP) { + maxP = _inclHisto[i]; + medD = i; + } + + return medD; +} + +int Coverage::selfMedian() +{ + double maxP = _selfHisto[0]; + int medD = 0; + for (int i = 1;imaxP) { + maxP = _selfHisto[i]; + medD = i; + } + + return medD; +} + +TraceFunctionList Coverage::coverage(TraceFunction* f, CoverageMode m, + EventType* 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) +{ + 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, + qPrintable(_function->prettyName()), _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 (dprettyName()), _incl, pBack); +#endif + } + + double callVal, pBackNew; + + foreach(TraceCall* call, _function->callers()) { + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (call->subCost(_costType)>0) { + TraceFunction* caller = call->caller(); + + Coverage* c = (Coverage*) caller->association(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) +{ + 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, + qPrintable(_function->prettyName()), _incl, _self, pForward, pBack); +#endif + + + if (_active) { + _inRecursion = true; + +#ifdef DEBUG_COVERAGE + qDebug("CngCov:%s < %s: STOP (is active)", + spaces+strlen(spaces)-d, + qPrintable(_function->prettyName())); +#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 (dprettyName()), _incl, _self); +#endif + } + + double callVal, pForwardNew, pBackNew; + + foreach(TraceCall* call, _function->callings()) { + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (call->subCost(_costType)>0) { + TraceFunction* calling = call->called(); + + Coverage* c = (Coverage*) calling->association(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, + qPrintable(calling->prettyName()), + 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/libcore/coverage.h b/kcachegrind/libcore/coverage.h new file mode 100644 index 00000000..b30a7366 --- /dev/null +++ b/kcachegrind/libcore/coverage.h @@ -0,0 +1,102 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 Josef Weidendorfer + + 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. +*/ + +/* + * 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 TraceAssociation +{ +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, + EventType* 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 EventType* _costType; +}; + +#endif + diff --git a/kcachegrind/libcore/eventtype.cpp b/kcachegrind/libcore/eventtype.cpp new file mode 100644 index 00000000..fae6836d --- /dev/null +++ b/kcachegrind/libcore/eventtype.cpp @@ -0,0 +1,620 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +#include "eventtype.h" + +#include +#include + +#include "globalconfig.h" + +//--------------------------------------------------- +// EventType + +QList* EventType::_knownTypes = 0; + +EventType::EventType(const QString& name, const QString& longName, + const QString& formula) +{ + _name = name; + _longName = longName; + _formula = formula; + _isReal = formula.isEmpty(); + _set = 0; + _realIndex = ProfileCostArray::InvalidIndex; + _parsed = false; + _inParsing = false; + + for (int i=0; iProfileCostArray::MaxRealIndex) + i=ProfileCostArray::InvalidIndex; + + _realIndex = i; + _formula = QString(); + _isReal = true; +} + +// checks for existing types and sets coefficients +bool EventType::parseFormula() +{ + if (isReal()) return true; + if (_parsed) return true; + + if (_inParsing) { + qDebug("TraceEventType::parseFormula: Recursion detected."); + return false; + } + + if (!_set) { + qDebug("TraceEventType::parseFormula: Container of this event type unknown!"); + return false; + } + + _inParsing = true; + + for (int i=0; itype(costName); + if (!eventType) { + //qDebug("Cost type '%s': In formula cost '%s' unknown.", + // qPrintable(_name), qPrintable(costName)); + continue; + } + + factor = (rx.cap(2).isEmpty()) ? 1 : rx.cap(2).toInt(); + if (rx.cap(1) == "-") factor = -factor; + if (factor == 0) continue; + + matching++; + + if (!_parsedFormula.isEmpty()) { + _parsedFormula += QString(" %1 ").arg((factor>0) ? '+':'-'); + } + else if (factor<0) + _parsedFormula += "- "; + if ((factor!=-1) && (factor!=1)) + _parsedFormula += QString::number( (factor>0)?factor:-factor ) + ' '; + _parsedFormula += costName; + + if (eventType->isReal()) + _coefficient[eventType->realIndex()] += factor; + else { + eventType->parseFormula(); + for (int i=0; i_coefficient[i]; + } + } + + _inParsing = false; + if (found == 0) { + // empty formula + _parsedFormula = QString("0"); + _parsed = true; + return true; + } + if (matching>0) { + _parsed = true; + return true; + } + return false; +} + + +QString EventType::parsedFormula() +{ + if (isReal()) return QString(); + + parseFormula(); + return _parsedFormula; +} + +QString EventType::parsedRealFormula() +{ + QString res; + + if (!parseFormula()) return res; + + for (int i=0; i0) res += "+ "; + } + if (c<0) { res += "- "; c = -c; } + res += QString::number(c); + + EventType* t = _set->type(i); + if (!t) continue; + + if (!t->name().isEmpty()) + res += QString(" * %1").arg(t->name()); + } + + return res; +} + +SubCost EventType::subCost(ProfileCostArray* c) +{ + if (_realIndex != ProfileCostArray::InvalidIndex) + return c->subCost(_realIndex); + + if (!_parsed) { + if (!parseFormula()) return 0; + } + SubCost res = 0; + + int rc = _set->realCount(); + for (int i = 0;isubCost(i); + + return res; +} + +int EventType::histCost(ProfileCostArray* c, double total, double* hist) +{ + if (total == 0.0) return 0; + + if (!_parsed) { + if (!parseFormula()) return 0; + } + + int rc = _set->realCount(); + for (int i = 0;isubCost(i) / total; + else + hist[i] = 0.0; + } + + return rc; +} + + + + +EventType* EventType::knownRealType(const QString& n) +{ + if (!_knownTypes) return 0; + + foreach (EventType* t, *_knownTypes) + if (t->isReal() && (t->name() == n)) { + EventType* type = new EventType(*t); + return type; + } + + return 0; +} + +EventType* EventType::knownDerivedType(const QString& n) +{ + if (!_knownTypes) return 0; + + foreach (EventType* t, *_knownTypes) + if (!t->isReal() && (t->name() == n)) { + EventType* type = new EventType(*t); + return type; + } + + return 0; +} + +// we take ownership +void EventType::add(EventType* t, bool overwriteExisting) +{ + if (!t) return; + + t->setEventTypeSet(0); + + if (!_knownTypes) + _knownTypes = new QList; + + /* Already known? */ + foreach (EventType* kt, *_knownTypes) + if (kt->name() == t->name()) { + if (overwriteExisting) { + // 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; + return; + } + + if (t->longName().isEmpty()) t->setLongName(t->name()); + _knownTypes->append(t); +} + + +int EventType::knownTypeCount() +{ + if (!_knownTypes) return 0; + + return _knownTypes->count(); +} + +bool EventType::remove(const QString& n) +{ + if (!_knownTypes) return false; + + foreach (EventType* t, *_knownTypes) + if (!t->isReal() && (t->name() == n)) { + _knownTypes->removeAll(t); + delete t; + return true; + } + + return false; +} + +EventType* EventType::knownType(int i) +{ + if (!_knownTypes) return 0; + if (i<0 || i>=(int)_knownTypes->count()) return 0; + + return _knownTypes->at(i); +} + + +//--------------------------------------------------- +// EventTypeSet + +EventTypeSet::EventTypeSet() +{ + _realCount = 0; + _derivedCount = 0; + for (int i=0;i ProfileCostArray::MaxRealIndex) { + qDebug() << "EventTypeSet::createMapping: No space for " + << newCount << " cost entries."; + qDebug() << "Increase MaxRealIndexValue in libcore/costitem.h and recompile."; + return 0; + } + + EventTypeMapping* mapping = new EventTypeMapping(this); + + pos = 0; + while (1) { + // skip space + while((posappend(addReal(types.mid(pos,pos2-pos))); + + pos = pos2; + } + + return mapping; +} + +int EventTypeSet::addReal(const QString& t) +{ + int index = realIndex(t); + if (index>=0) return index; + + EventType* ct = EventType::knownRealType(t); + if (!ct) ct = new EventType(t, t); + + // make it real + ct->setRealIndex(); + + return add(ct); +} + +// add an event type to a set +// this transfers ownership of the type! +int EventTypeSet::add(EventType* et) +{ + if (!et) return ProfileCostArray::InvalidIndex; + + et->setEventTypeSet(this); + + if (et->isReal()) { + if (_realCount >= ProfileCostArray::MaxRealIndex) { + qDebug("WARNING: Maximum for real event types reached (on adding '%s')", + qPrintable(et->name())); + return ProfileCostArray::InvalidIndex; + } + _real[_realCount] = et; + et->setRealIndex(_realCount); + + _realCount++; + return _realCount-1; + } + + if (_derivedCount >= ProfileCostArray::MaxRealIndex) { + qDebug("WARNING: Maximum for virtual event types reached (on adding '%s')", + qPrintable(et->name())); + return ProfileCostArray::InvalidIndex; + } + _derived[_derivedCount] = et; + _derivedCount++; + return _derivedCount-1; +} + +// we delete the type: t is invalid when returning true! +bool EventTypeSet::remove(EventType* t) +{ + if (!t) return false; + if (t->set() != this) return false; + + // do not delete real types + if (t->isReal()) return false; + + int i; + for(i=0;i<_derivedCount;i++) + if (_derived[i] == t) break; + + // not found? + if (i == _derivedCount) return false; + + // delete known type with same name + EventType::remove(t->name()); + + // delete this type + _derived[i] = 0; + delete t; + if (i+1 == _derivedCount) { + // we can reuse the last index + _derivedCount--; + } + return true; +} + + +EventType* EventTypeSet::realType(int t) +{ + if (t<0 || t>=_realCount) return 0; + return _real[t]; +} + +EventType* EventTypeSet::derivedType(int t) +{ + if (t<0 || t>=_derivedCount) return 0; + return _derived[t]; +} + + +EventType* EventTypeSet::type(int t) +{ + if (t<0) return 0; + if (t<_realCount) return _real[t]; + + t -= ProfileCostArray::MaxRealIndex; + if (t<0) return 0; + if (t<_derivedCount) return _derived[t]; + + return 0; +} + +EventType* EventTypeSet::type(const QString& name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return _real[i]; + + for (int i=0;i<_derivedCount;i++) + if (_derived[i] && (_derived[i]->name() == name)) + return _derived[i]; + + return 0; +} + +EventType* EventTypeSet::typeForLong(const QString& name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->longName() == name)) + return _real[i]; + + for (int i=0;i<_derivedCount;i++) + if (_derived[i] && (_derived[i]->longName() == name)) + return _derived[i]; + + return 0; +} + + +int EventTypeSet::realIndex(const QString& name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return i; + + return ProfileCostArray::InvalidIndex; +} + +int EventTypeSet::index(const QString& name) +{ + for (int i=0;i<_realCount;i++) + if (_real[i] && (_real[i]->name() == name)) + return i; + + for (int i=0;i<_derivedCount;i++) + if (_derived[i] && (_derived[i]->name() == name)) + return ProfileCostArray::MaxRealIndex + 1 + i; + + return ProfileCostArray::InvalidIndex; +} + +int EventTypeSet::addKnownDerivedTypes() +{ + int addCount = 0; + int addDiff, i; + int knownCount = EventType::knownTypeCount(); + + while (1) { + addDiff = 0; + for (i=0; iisReal()) continue; + if (index(t->name()) != ProfileCostArray::InvalidIndex) continue; + t->setEventTypeSet(this); + if (t->parseFormula()) { + addDiff++; + add(new EventType(t->name(), t->longName(), t->formula())); + } + t->setEventTypeSet(0); + } + if (addDiff == 0) break; + addCount += addDiff; + } + return addCount; +} + + +//--------------------------------------------------- +// EventTypeMapping + +EventTypeMapping::EventTypeMapping(EventTypeSet* set) +{ + _set = set; + clear(); +} + +void EventTypeMapping::clear() +{ + _count = 0; + _isIdentity = true; + _firstUnused = 0; + for(int i=0;i _count) count = _count; + if (_isIdentity) return count-1; + + int maxIndex = -1; + for(int j=0; jaddReal(type) : _set->realIndex(type); + + return append(index); +} + +bool EventTypeMapping::append(int type) +{ + if (!_set) return false; + if ((type<0) || (type >= _set->realCount())) return false; + + if ( _count >= ProfileCostArray::MaxRealIndex) return false; + + _realIndex[_count] = type; + + if (_isIdentity && (_count != type)) _isIdentity = false; + if (type == _firstUnused) + _firstUnused = _nextUnused[type]; + for(int i=0;i + + 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. +*/ + +#ifndef EVENTTYPE_H +#define EVENTTYPE_H + +#include + +#include "subcost.h" +#include "costitem.h" + +class EventTypeSet; + +/** + * 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 EventTypeSet + * with according cost types (e.g. "l1rm" and "l2rm" for above formula). + * + * The cost type with empty name is the "const" cost type. + */ +class EventType +{ +public: + + /** + * is a short (non-localized) identifier for the cost type, + * e.g. "l1rm". + * is a long localized string, e.g. "L1 Read Miss" + * uses short names to reference other types + */ + explicit EventType(const QString& name, + const QString& longName = QString(), + const QString& formula = QString()); + + void setName(const QString& n) { _name = n; } + void setLongName(const QString& n) { _longName = n; } + void setEventTypeSet(EventTypeSet* m); + // enforces event to be derived, even with empty formula + void setFormula(const QString&); + // default arg is for specifying a real type, but index unknown + void setRealIndex(int r = ProfileCostArray::MaxRealIndex); + + const QString& name() { return _name; } + const QString& longName() { return _longName; } + const QString& formula() { return _formula; } + EventTypeSet* set() { return _set; } + int realIndex() { return _realIndex; } + bool isReal() { return _isReal; } + + /* + * returns true if all cost type names can be resolved in formula + */ + bool parseFormula(); + QString parsedFormula(); + QString parsedRealFormula(); + + SubCost subCost(ProfileCostArray*); + + /* + * For virtual costs, returns a histogram for use with + * partitionPixmap(). + * Returns maximal real index. + */ + int histCost(ProfileCostArray* c, double total, double* hist); + + // application wide known types, referenced by short name + // next 2 functions return a new type object instance + static EventType* knownRealType(const QString&); + static EventType* knownDerivedType(const QString&); + static void add(EventType*, bool overwriteExisting = true); + static bool remove(const QString&); + static int knownTypeCount(); + static EventType* knownType(int); + +private: + + QString _name, _longName, _formula, _parsedFormula; + EventTypeSet* _set; + bool _parsed, _inParsing, _isReal; + // index MaxRealIndex is for constant addition + int _coefficient[MaxRealIndexValue]; + int _realIndex; + + static QList* _knownTypes; +}; + + +/** + * A class for managing a set of event types. + * + * Each event type has an index: + * - Real events are in range [0 .. ProfileCostArray:MaxRealIndex[ + * - Derived events are in range [MaxRealIndex, ...] + */ +class EventTypeSet +{ +public: + EventTypeSet(); + ~EventTypeSet(); + + /** + * Defines a mapping from indexes into a list of costs to real event types + * If is false, checks if this is a existing sub set. + */ + EventTypeMapping* createMapping(const QString& types); + + // "knows" about some real types + int addReal(const QString&); + int add(EventType*); + bool remove(EventType*); + int realCount() { return _realCount; } + int derivedCount() { return _derivedCount; } + int minDerivedIndex() { return ProfileCostArray::MaxRealIndex; } + EventType* type(int); + EventType* realType(int); + EventType* derivedType(int); + EventType* type(const QString&); + EventType* typeForLong(const QString&); + int realIndex(const QString&); + int index(const QString&); + + /** + * Adds all known derived event types that can be parsed + */ + int addKnownDerivedTypes(); + +private: + // we support only a fixed number of real and derived types + EventType* _real[MaxRealIndexValue]; + EventType* _derived[MaxRealIndexValue]; + int _realCount, _derivedCount; +}; + +/** + * A index list into a EventTypeSet + * + * This ordered list maps from indexes into real cost types + * of a set. + * + * You can define a set of event types by requesting submaps by name. + * Every new event type name will get a new real type index. + * EventTypeSet s; + * m1 = s.createMapping("Event1 Cost1 Cost2"); // returns mapping [0,1,2] + * m2 = s.createMapping("Event2 Cost3 Event1"); // returns mapping [3,4,0] + * Real types of s will be: + * (0:Event1, 1:Cost1, 2:Cost2, 3:Event2, 4:Cost3) + */ +class EventTypeMapping +{ +public: + EventTypeMapping(EventTypeSet*); + + bool append(const QString&, bool create=true); + bool append(int); + void clear(); + + EventTypeSet* set() { return _set; } + + /** + * Get number of used indexes + */ + int count() { return _count; } + + /** + * Is this mapping 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) ? ProfileCostArray::InvalidIndex : _realIndex[i]; } + + /** + * Get maximal real index for the first mapping indexes. + */ + int maxRealIndex(int count); + + /** + * Allows an iteration over the sorted list of all real indexes not used in + * this mapping, up to topIndex (use ProfileCostArray::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>=ProfileCostArray::MaxRealIndex) return ProfileCostArray::InvalidIndex; + return _nextUnused[i]; } + +private: + EventTypeSet* _set; + int _count, _firstUnused; + bool _isIdentity; + int _realIndex[MaxRealIndexValue]; + int _nextUnused[MaxRealIndexValue]; +}; + + +#endif // EVENTTYPE_H diff --git a/kcachegrind/libcore/fixcost.cpp b/kcachegrind/libcore/fixcost.cpp new file mode 100644 index 00000000..1b294bc1 --- /dev/null +++ b/kcachegrind/libcore/fixcost.cpp @@ -0,0 +1,187 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#include "fixcost.h" +#include "utils.h" +#include "addr.h" + +// FixCost + +FixCost::FixCost(TracePart* part, FixPool* pool, + TraceFunctionSource* functionSource, + PositionSpec& pos, + TracePartFunction* partFunction, + FixString& s) +{ + int maxCount = part->eventTypeMapping()->count(); + + _part = part; + _functionSource = functionSource; + _pos = pos; + + _cost = (SubCost*) pool->reserve(sizeof(SubCost) * maxCount); + s.stripSpaces(); + int i = 0; + while(iallocateReserved(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(ProfileCostArray* c) +{ + EventTypeMapping* sm = _part->eventTypeMapping(); + + int i, realIndex; + + c->reserve(sm->maxRealIndex(_count)+1); + 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", + qPrintable(addr.toString()), line, + qPrintable(callCount.pretty())); + + int maxCount = part->eventTypeMapping()->count(); + + _part = part; + _functionSource = functionSource; + _line = line; + _addr = addr; + + _cost = (SubCost*) pool->reserve(sizeof(SubCost) * (maxCount+1)); + s.stripSpaces(); + int i = 0; + while(iallocateReserved(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) +{ + EventTypeMapping* sm = _part->eventTypeMapping(); + + 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", + qPrintable(_addr.toString()), _line, + qPrintable(_cost[_count].pretty())); +} + +void FixCallCost::setMax(ProfileCostArray* c) +{ + EventTypeMapping* sm = _part->eventTypeMapping(); + + 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/libcore/fixcost.h b/kcachegrind/libcore/fixcost.h new file mode 100644 index 00000000..06120b6a --- /dev/null +++ b/kcachegrind/libcore/fixcost.h @@ -0,0 +1,183 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#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(ProfileCostArray*); + + 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(ProfileCostArray*); + + 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() const { 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/libcore/globalconfig.cpp b/kcachegrind/libcore/globalconfig.cpp new file mode 100644 index 00000000..9f7d4c91 --- /dev/null +++ b/kcachegrind/libcore/globalconfig.cpp @@ -0,0 +1,476 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2008 Josef Weidendorfer + + 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. +*/ + +/* + * Global configuration for KCachegrind + */ + +#include "globalconfig.h" + +#include + +#include "config.h" +#include "tracedata.h" + + +// GlobalConfig defaults +#define DEFAULT_SHOWPERCENTAGE true +#define DEFAULT_SHOWEXPANDED false +#define DEFAULT_SHOWCYCLES true +#define DEFAULT_HIDETEMPLATES false +#define DEFAULT_CYCLECUT 0.0 +#define DEFAULT_PERCENTPRECISION 2 +#define DEFAULT_MAXSYMBOLLENGTH 30 +#define DEFAULT_MAXSYMBOLCOUNT 10 +#define DEFAULT_MAXLISTCOUNT 100 +#define DEFAULT_CONTEXT 3 +#define DEFAULT_NOCOSTINSIDE 20 + + +// +// GlobalConfig +// + +// Some predefined event types... +QStringList GlobalConfig::knownTypes() +{ + QStringList l; + + l << "Ir" << "Dr" << "Dw" + << "I1mr" << "D1mr" << "D1mw" << "L1m"; + // Valgrind < 3.6.0 + l << "I2mr" << "D2mr" << "D2mw" << "L2m"; + // Valgrind 3.6.0: L2 events changed to to LL (last level) events + l << "ILmr" << "DLmr" << "DLmw" << "LLm"; + + // branch simulation + l << "Bi" << "Bim" << "Bc" << "Bcm" << "Bm"; + + // global bus events (e.g. CAS) + l << "Ge"; + + l << "Smp" << "Sys" << "User" << "CEst"; + + return l; +} + + +QString GlobalConfig::knownFormula(const QString& name) +{ + if (name == "L1m") return QString("I1mr + D1mr + D1mw"); + if (name == "L2m") return QString("I2mr + D2mr + D2mw"); + if (name == "LLm") return QString("ILmr + DLmr + DLmw"); + if (name == "Bm") return QString("Bim + Bcm"); + if (name == "CEst") + return QString("Ir + 10 Bm + 10 L1m + 20 Ge + 100 L2m + 100 LLm"); + + return QString(); +} + +QString GlobalConfig::knownLongName(const QString& name) +{ + if (name == "Ir") return QObject::tr("Instruction Fetch"); + if (name == "Dr") return QObject::tr("Data Read Access"); + if (name == "Dw") return QObject::tr("Data Write Access"); + if (name == "I1mr") return QObject::tr("L1 Instr. Fetch Miss"); + if (name == "D1mr") return QObject::tr("L1 Data Read Miss"); + if (name == "D1mw") return QObject::tr("L1 Data Write Miss"); + if (name == "I2mr") return QObject::tr("L2 Instr. Fetch Miss"); + if (name == "D2mr") return QObject::tr("L2 Data Read Miss"); + if (name == "D2mw") return QObject::tr("L2 Data Write Miss"); + if (name == "ILmr") return QObject::tr("LL Instr. Fetch Miss"); + if (name == "DLmr") return QObject::tr("LL Data Read Miss"); + if (name == "DLmw") return QObject::tr("LL Data Write Miss"); + if (name == "L1m") return QObject::tr("L1 Miss Sum"); + if (name == "L2m") return QObject::tr("L2 Miss Sum"); + if (name == "LLm") return QObject::tr("Last-level Miss Sum"); + if (name == "Bi") return QObject::tr("Indirect Branch"); + if (name == "Bim") return QObject::tr("Mispredicted Ind. Branch"); + if (name == "Bc") return QObject::tr("Conditional Branch"); + if (name == "Bcm") return QObject::tr("Mispredicted Cond. Branch"); + if (name == "Bm") return QObject::tr("Mispredicted Branch"); + if (name == "Ge") return QObject::tr("Global Bus Event"); + if (name == "Smp") return QObject::tr("Samples"); + if (name == "Sys") return QObject::tr("System Time"); + if (name == "User") return QObject::tr("User Time"); + if (name == "CEst") return QObject::tr("Cycle Estimation"); + + return QString(); +} + + +GlobalConfig* GlobalConfig::_config = 0; + +GlobalConfig::GlobalConfig() +{ + _config = 0; + + // general presentation options + _showPercentage = DEFAULT_SHOWPERCENTAGE; + _showExpanded = DEFAULT_SHOWEXPANDED; + _showCycles = DEFAULT_SHOWCYCLES; + _cycleCut = DEFAULT_CYCLECUT; + _percentPrecision = DEFAULT_PERCENTPRECISION; + _hideTemplates = DEFAULT_HIDETEMPLATES; + + // max symbol count/length in tooltip/popup + _maxSymbolLength = DEFAULT_MAXSYMBOLLENGTH; + _maxSymbolCount = DEFAULT_MAXSYMBOLCOUNT; + _maxListCount = DEFAULT_MAXLISTCOUNT; + + // annotation behaviour + _context = DEFAULT_CONTEXT; + _noCostInside = DEFAULT_NOCOSTINSIDE; +} + +GlobalConfig::~GlobalConfig() +{ +} + +GlobalConfig* GlobalConfig::config() +{ + if (_config == 0) + _config = new GlobalConfig(); + + return _config; +} + + +void GlobalConfig::saveOptions() +{ + // source options + ConfigGroup* sourceConfig = ConfigStorage::group("Source"); + sourceConfig->setValue("Dirs", _generalSourceDirs); + QHashIterator it( _objectSourceDirs ); + int count = 1; + while( it.hasNext() ) { + it.next(); + sourceConfig->setValue( QString("Object%1").arg(count), + it.key() ); + sourceConfig->setValue( QString("Dirs%1").arg(count), + it.value() ); + count++; + } + sourceConfig->setValue("Count", count-1); + delete sourceConfig; + + // general options + ConfigGroup* generalConfig = ConfigStorage::group("GeneralSettings"); + generalConfig->setValue("ShowPercentage", _showPercentage, + DEFAULT_SHOWPERCENTAGE); + generalConfig->setValue("ShowExpanded", _showExpanded, + DEFAULT_SHOWEXPANDED); + generalConfig->setValue("ShowCycles", _showCycles, + DEFAULT_SHOWCYCLES); + generalConfig->setValue("CycleCut", _cycleCut, + DEFAULT_CYCLECUT); + generalConfig->setValue("PercentPrecision", _percentPrecision, + DEFAULT_PERCENTPRECISION); + generalConfig->setValue("MaxSymbolLength", _maxSymbolLength, + DEFAULT_MAXSYMBOLLENGTH); + generalConfig->setValue("MaxSymbolCount", _maxSymbolCount, + DEFAULT_MAXSYMBOLCOUNT); + generalConfig->setValue("MaxListCount", _maxListCount, + DEFAULT_MAXLISTCOUNT); + generalConfig->setValue("Context", _context, + DEFAULT_CONTEXT); + generalConfig->setValue("NoCostInside", _noCostInside, + DEFAULT_NOCOSTINSIDE); + generalConfig->setValue("HideTemplates", _hideTemplates, + DEFAULT_HIDETEMPLATES); + delete generalConfig; + + // event types + ConfigGroup* etConfig = ConfigStorage::group("EventTypes"); + int etCount = EventType::knownTypeCount(); + etConfig->setValue( "Count", etCount); + for (int i=0; isetValue( QString("Name%1").arg(i+1), t->name()); + etConfig->setValue( QString("Longname%1").arg(i+1), + t->longName(), + knownLongName(t->name()) ); + etConfig->setValue( QString("Formula%1").arg(i+1), + t->formula(), knownFormula(t->name()) ); + } + delete etConfig; +} + +void GlobalConfig::readOptions() +{ + int i, count; + + // source options + ConfigGroup* sourceConfig = ConfigStorage::group("Source"); + QStringList dirs; + dirs = sourceConfig->value("Dirs", QStringList()).toStringList(); + if (dirs.count()>0) _generalSourceDirs = dirs; + count = sourceConfig->value("Count", 0).toInt(); + _objectSourceDirs.clear(); + for (i=1;i<=count;i++) { + QString n = sourceConfig->value(QString("Object%1").arg(i), + QString()).toString(); + dirs = sourceConfig->value(QString("Dirs%1").arg(i), + QStringList()).toStringList(); + + if (n.isEmpty() || (dirs.count()==0)) continue; + + _objectSourceDirs.insert(n, dirs); + } + delete sourceConfig; + + // general options + ConfigGroup* generalConfig = ConfigStorage::group("GeneralSettings"); + _showPercentage = generalConfig->value("ShowPercentage", + DEFAULT_SHOWPERCENTAGE).toBool(); + _showExpanded = generalConfig->value("ShowExpanded", + DEFAULT_SHOWEXPANDED).toBool(); + _showCycles = generalConfig->value("ShowCycles", + DEFAULT_SHOWCYCLES).toBool(); + _cycleCut = generalConfig->value("CycleCut", + DEFAULT_CYCLECUT).toDouble(); + _percentPrecision = generalConfig->value("PercentPrecision", + DEFAULT_PERCENTPRECISION).toInt(); + _maxSymbolLength = generalConfig->value("MaxSymbolLength", + DEFAULT_MAXSYMBOLLENGTH).toInt(); + _maxSymbolCount = generalConfig->value("MaxSymbolCount", + DEFAULT_MAXSYMBOLCOUNT).toInt(); + _maxListCount = generalConfig->value("MaxListCount", + DEFAULT_MAXLISTCOUNT).toInt(); + _context = generalConfig->value("Context", + DEFAULT_CONTEXT).toInt(); + _noCostInside = generalConfig->value("NoCostInside", + DEFAULT_NOCOSTINSIDE).toInt(); + _hideTemplates = generalConfig->value("HideTemplates", + DEFAULT_HIDETEMPLATES).toBool(); + delete generalConfig; + + // event types + if (EventType::knownTypeCount() >0) return; // already read + ConfigGroup* etConfig = ConfigStorage::group("EventTypes"); + int etCount = etConfig->value("Count", 0).toInt(); + + for (int i=1;i<=etCount;i++) { + QString n = etConfig->value(QString("Name%1").arg(i), + QString()).toString(); + QString l = etConfig->value(QString("Longname%1").arg(i), + QString()).toString(); + if (l.isEmpty()) l = knownLongName(n); + QString f = etConfig->value(QString("Formula%1").arg(i), + QString()).toString(); + if (f.isEmpty()) f = knownFormula(n); + + EventType::add(new EventType(n, l, f)); + } + + // this does only add yet non-existing types + addDefaultTypes(); + + delete etConfig; +} + +void GlobalConfig::addDefaultTypes() +{ + QString longName, formula; + EventType* ct; + QStringList l = knownTypes(); + for ( QStringList::const_iterator it = l.constBegin(); + it != l.constEnd(); ++it ) { + longName = knownLongName(*it); + formula = knownFormula(*it); + ct = new EventType(*it, longName, formula); + EventType::add(ct, false); + } +} + +/* 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 GlobalConfig::sourceDirs(TraceData* data, TraceObject* o) +{ + QStringList l = config()->_generalSourceDirs, ol, ol2; + TraceObjectMap::Iterator oit; + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) { + ol = config()->_objectSourceDirs[(*oit).name()]; + if (&(*oit) == o) { + ol2 = ol; + continue; + } + + for(int i=0;i_showPercentage; +} + +bool GlobalConfig::showExpanded() +{ + return config()->_showExpanded; +} + +bool GlobalConfig::showCycles() +{ + return config()->_showCycles; +} + +bool GlobalConfig::hideTemplates() +{ + return config()->_hideTemplates; +} + +void GlobalConfig::setShowPercentage(bool s) +{ + GlobalConfig* c = config(); + if (c->_showPercentage == s) return; + + c->_showPercentage = s; +} + +void GlobalConfig::setShowExpanded(bool s) +{ + GlobalConfig* c = config(); + if (c->_showExpanded == s) return; + + c->_showExpanded = s; +} + +void GlobalConfig::setShowCycles(bool s) +{ + GlobalConfig* c = config(); + if (c->_showCycles == s) return; + + c->_showCycles = s; +} + +void GlobalConfig::setHideTemplates(bool s) +{ + GlobalConfig* c = config(); + if (c->_hideTemplates == s) return; + + c->_hideTemplates = s; +} + +double GlobalConfig::cycleCut() +{ + return config()->_cycleCut; +} + +int GlobalConfig::percentPrecision() +{ + return config()->_percentPrecision; +} + +int GlobalConfig::maxSymbolLength() +{ + return config()->_maxSymbolLength; +} + +QString GlobalConfig::shortenSymbol(const QString& s) +{ + if(s.length() > config()->_maxSymbolLength) + return s.left(config()->_maxSymbolLength) + QLatin1String("..."); + return s; +} + +int GlobalConfig::maxListCount() +{ + return config()->_maxListCount; +} + +int GlobalConfig::maxSymbolCount() +{ + return config()->_maxSymbolCount; +} + +int GlobalConfig::context() +{ + return config()->_context; +} + +int GlobalConfig::noCostInside() +{ + return config()->_noCostInside; +} + +void GlobalConfig::setPercentPrecision(int v) +{ + if ((v<1) || (v >5)) return; + _percentPrecision = v; +} + +void GlobalConfig::setMaxSymbolLength(int v) +{ + if ((v<1) || (v >1000)) return; + _maxSymbolLength = v; +} + +void GlobalConfig::setMaxSymbolCount(int v) +{ + if ((v<1) || (v >50)) return; + _maxSymbolCount = v; +} + +void GlobalConfig::setMaxListCount(int v) +{ + if ((v<1) || (v >500)) return; + _maxListCount = v; +} + +void GlobalConfig::setContext(int v) +{ + if ((v<1) || (v >500)) return; + _context = v; +} + +const QStringList& GlobalConfig::generalSourceDirs() +{ + return _generalSourceDirs; +} + +QStringList GlobalConfig::objectSourceDirs(QString obj) +{ + if (_objectSourceDirs.contains(obj)) + return _objectSourceDirs[obj]; + else + return QStringList(); +} + +void GlobalConfig::setGeneralSourceDirs(QStringList dirs) +{ + _generalSourceDirs = dirs; +} + +void GlobalConfig::setObjectSourceDirs(QString obj, QStringList dirs) +{ + if (dirs.count() == 0) + _objectSourceDirs.remove(obj); + else + _objectSourceDirs.insert(obj, dirs); +} diff --git a/kcachegrind/libcore/globalconfig.h b/kcachegrind/libcore/globalconfig.h new file mode 100644 index 00000000..19dbdd49 --- /dev/null +++ b/kcachegrind/libcore/globalconfig.h @@ -0,0 +1,111 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2008 Josef Weidendorfer + + 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. +*/ + +/* + * Global configuration for KCachegrind (only non-GUI options) + */ + +#ifndef GLOBALCONFIG_H +#define GLOBALCONFIG_H + +#include +#include + +#include "tracedata.h" + +class GlobalConfig; + +/** + * Global configuration (only non-GUI options). + * A singleton. + */ +class GlobalConfig +{ + friend class ConfigDlg; + +public: + GlobalConfig(); + virtual ~GlobalConfig(); + + // gets the singleton instance + static GlobalConfig* config(); + + virtual void saveOptions(); + virtual void readOptions(); + + static QStringList sourceDirs(TraceData*, TraceObject* o = 0); + static bool showPercentage(); + static bool showExpanded(); + static bool showCycles(); + static bool hideTemplates(); + + // 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 + static QString shortenSymbol(const 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(); + + const QStringList& generalSourceDirs(); + QStringList objectSourceDirs(QString); + void setGeneralSourceDirs(QStringList); + void setObjectSourceDirs(QString, QStringList); + + void setPercentPrecision(int); + void setMaxSymbolLength(int); + void setMaxSymbolCount(int); + void setMaxListCount(int); + void setContext(int); + + static void setShowPercentage(bool); + static void setShowExpanded(bool); + + static void setShowCycles(bool); + + static void setHideTemplates(bool); + // upper limit for cutting of a call in cycle detection + static double cycleCut(); + + void addDefaultTypes(); + +protected: + QStringList knownTypes(); + QString knownFormula(const QString& name); + QString knownLongName(const QString& name); + + QStringList _generalSourceDirs; + QHash _objectSourceDirs; + + bool _showPercentage, _showExpanded, _showCycles, _hideTemplates; + double _cycleCut; + int _percentPrecision; + int _maxSymbolLength, _maxSymbolCount, _maxListCount; + int _context, _noCostInside; + + static GlobalConfig* _config; +}; + +#endif // GLOBALCONFIG_H diff --git a/kcachegrind/libcore/libcore.pri b/kcachegrind/libcore/libcore.pri new file mode 100644 index 00000000..617daaec --- /dev/null +++ b/kcachegrind/libcore/libcore.pri @@ -0,0 +1,37 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +NHEADERS += \ + $$PWD/context.h \ + $$PWD/costitem.h \ + $$PWD/subcost.h \ + $$PWD/eventtype.h \ + $$PWD/addr.h \ + $$PWD/config.h \ + $$PWD/globalconfig.h \ + $$PWD/tracedata.h \ + $$PWD/utils.h \ + $$PWD/logger.h \ + $$PWD/loader.h \ + $$PWD/fixcost.h \ + $$PWD/pool.h \ + $$PWD/coverage.h \ + $$PWD/stackbrowser.h + +SOURCES += \ + $$PWD/context.cpp \ + $$PWD/costitem.cpp \ + $$PWD/subcost.cpp \ + $$PWD/eventtype.cpp \ + $$PWD/addr.cpp \ + $$PWD/cachegrindloader.cpp \ + $$PWD/config.cpp \ + $$PWD/coverage.cpp \ + $$PWD/fixcost.cpp \ + $$PWD/globalconfig.cpp \ + $$PWD/loader.cpp \ + $$PWD/logger.cpp \ + $$PWD/pool.cpp \ + $$PWD/stackbrowser.cpp \ + $$PWD/tracedata.cpp \ + $$PWD/utils.cpp diff --git a/kcachegrind/libcore/loader.cpp b/kcachegrind/libcore/loader.cpp new file mode 100644 index 00000000..617820e9 --- /dev/null +++ b/kcachegrind/libcore/loader.cpp @@ -0,0 +1,121 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2010 Josef Weidendorfer + + 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. +*/ + +/* + * Base class for loaders of profiling data. + */ + +#include "loader.h" + +#include "logger.h" + +/// Loader + +QList Loader::_loaderList; + +Loader::Loader(const QString& name, const QString& desc) +{ + _name = name; + _description = desc; + + _logger = 0; +} + +Loader::~Loader() +{} + +bool Loader::canLoad(QIODevice*) +{ + return false; +} + +int Loader::load(TraceData*, QIODevice*, const QString&) +{ + return 0; +} + +Loader* Loader::matchingLoader(QIODevice* file) +{ + foreach (Loader* l, _loaderList) + if (l->canLoad(file)) + return l; + + return 0; +} + +Loader* Loader::loader(const QString& name) +{ + foreach (Loader* l, _loaderList) + 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() +{ + while (!_loaderList.isEmpty()) + delete _loaderList.takeFirst(); +} + +// notifications + +void Loader::setLogger(Logger* l) +{ + _logger = l; +} + +void Loader::loadStart(const QString& filename) +{ + if (_logger) + _logger->loadStart(filename); +} + +void Loader::loadProgress(int progress) +{ + if (_logger) + _logger->loadProgress(progress); +} + +void Loader::loadError(int line, const QString& msg) +{ + if (_logger) + _logger->loadError(line, msg); +} + +void Loader::loadWarning(int line, const QString& msg) +{ + if (_logger) + _logger->loadWarning(line, msg); +} + +void Loader::loadFinished(const QString& msg) +{ + if (_logger) + _logger->loadFinished(msg); +} + diff --git a/kcachegrind/libcore/loader.h b/kcachegrind/libcore/loader.h new file mode 100644 index 00000000..d4a79c7e --- /dev/null +++ b/kcachegrind/libcore/loader.h @@ -0,0 +1,94 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2010 Josef Weidendorfer + + 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. +*/ + +/* + * Base class for loaders of profiling data. + */ + +#ifndef LOADER_H +#define LOADER_H + +#include +#include + +class QIODevice; +class TraceData; +class Loader; +class Logger; + +/** + * To implement a new loader, inherit from the Loader class and + * and reimplement canLoad() and load(). + * + * For registration, put into the static initLoaders() function + * of this base class a _loaderList.append(new MyLoader()). + * + * matchingLoader() returns the first loader able to load a file. + * + * To show progress and warnings while loading, + * loadStatus(), loadError() and loadWarning() should be called. + * These are just shown as status, warnings or errors to the + * user, but do not show real failure, as even errors can be + * recoverable. For unablility to load a file, return 0 in + * load(). + */ + +class Loader +{ +public: + Loader(const QString& name, const QString& desc); + virtual ~Loader(); + + // reimplement for a specific Loader + virtual bool canLoad(QIODevice* file); + /* load a profile data file. + * for every section (time span covered by profile), create a TracePart + * return the number of sections loaded (0 on error) + */ + virtual int load(TraceData*, QIODevice* file, const QString& filename); + + static Loader* matchingLoader(QIODevice* file); + static Loader* loader(const QString& name); + static void initLoaders(); + static void deleteLoaders(); + + QString name() const { return _name; } + QString description() const { return _description; } + + // consumer for notifications + void setLogger(Logger*); + +protected: + // notifications for the user + void loadStart(const QString& filename); + void loadProgress(int progress); // 0 - 100 + void loadError(int line, const QString& msg); + void loadWarning(int line, const QString& msg); + void loadFinished(const QString &msg = QString::null); + +protected: + Logger* _logger; + +private: + QString _name, _description; + + static QList _loaderList; +}; + + +#endif diff --git a/kcachegrind/libcore/logger.cpp b/kcachegrind/libcore/logger.cpp new file mode 100644 index 00000000..3171d002 --- /dev/null +++ b/kcachegrind/libcore/logger.cpp @@ -0,0 +1,69 @@ +/* This file is part of KCachegrind. + Copyright (C) 2008 Josef Weidendorfer + + 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. +*/ + +/* + * Default implementation for notification dispatcher: use qDebug + */ + +#include + +#include "logger.h" + + +/// Logger + +Logger::~Logger() +{} + +void Logger::loadStart(const QString& filename) +{ + _filename = filename; + _timer.setSingleShot(true); + _timer.start(1000); + qDebug() << "Loading" << filename; +} + +void Logger::loadProgress(int progress) +{ + // print progress at most every second + if (_timer.isActive()) return; + _timer.start(1000); + + qDebug() << "Loading" << _filename << "(" << progress << "%)"; +} + +void Logger::loadWarning(int line, const QString& msg) +{ + qDebug() << "Warning in " << _filename << ", line" << line + << ":" << msg; +} + +void Logger::loadError(int line, const QString& msg) +{ + qDebug() << "Error in " << _filename << ", line" << line + << ":" << msg; +} + +void Logger::loadFinished(const QString& msg) +{ + _timer.stop(); + if (msg.isEmpty()) + qDebug() << "File" << _filename << "loaded."; + else + qDebug() << "Error loading file" << _filename << ":" << qPrintable(msg); +} diff --git a/kcachegrind/libcore/logger.h b/kcachegrind/libcore/logger.h new file mode 100644 index 00000000..500dff36 --- /dev/null +++ b/kcachegrind/libcore/logger.h @@ -0,0 +1,53 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2008 Josef Weidendorfer + + 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. +*/ + +/* + * Dispatcher for messages (status/warning/errors) from libcore + * + * For different front ends which want to present the messages to the + * user, a derived class should be implemented + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include + +class Logger +{ +public: + virtual ~Logger(); + + // Notifications for file loading + virtual void loadStart(const QString& filename); + virtual void loadProgress(int progress); // 0 - 100 + virtual void loadWarning(int line, const QString& msg); + virtual void loadError(int line, const QString& msg); + virtual void loadFinished(const QString& msg); // msg could be error + +protected: + QString _filename; + +private: + QTimer _timer; +}; + +#endif // LOGGER_H + + diff --git a/kcachegrind/libcore/pool.cpp b/kcachegrind/libcore/pool.cpp new file mode 100644 index 00000000..06a1c25d --- /dev/null +++ b/kcachegrind/libcore/pool.cpp @@ -0,0 +1,264 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2004 Josef Weidendorfer + + 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. +*/ + +#include +#include +#include +#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 do not allow allocation sizes > CHUNK_SIZE + if (size > CHUNK_SIZE) return false; + + newChunk = (struct SpaceChunk*) malloc(sizeof(struct SpaceChunk) + + CHUNK_SIZE); + if (!newChunk) { + qFatal("ERROR: Out of memory. Sorry. KCachegrind has to terminate.\n\n" + "You probably tried to load a profile data file too huge for" + "this system. You could try loading this file on a 64-bit OS."); + exit(1); + } + 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;i20)) + p.free(bufs+i-20); + } + + for(i=0;i + + 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. +*/ + +#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 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 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 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/libcore/stackbrowser.cpp b/kcachegrind/libcore/stackbrowser.cpp new file mode 100644 index 00000000..d5db4dbd --- /dev/null +++ b/kcachegrind/libcore/stackbrowser.cpp @@ -0,0 +1,399 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#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() +{ + SubCost most; + TraceFunction* f; + + if (!_calls.isEmpty()) + f = _calls.last()->called(); + else + f = _top; + + if (!f) return; + // do not follow calls from cycles + if (f->cycle() == f) return; + + // event type to use for the "most probable" call stack + // we simply take the first real event type + if ((_top->data() == 0) || + (_top->data()->eventTypes()->realCount() <1)) return; + EventType* e = _top->data()->eventTypes()->realType(0); + + int max = 30; + + // try to extend to lower stack frames + while (f && (max-- >0)) { + TraceCall* call = 0; + most = 0; + foreach(TraceCall* c, f->callings()) { + // 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(e); + if (sc == 0) continue; + + if (sc > most) { + most = sc; + call = c; + } + } + if (!call) + break; + + _calls.append(call); + f = call->called(); + } +} + + +void Stack::extendTop() +{ + SubCost most; + + int max = 10; + + // do not follow calls from cycles + if (_top->cycle() == _top) return; + + // event type to use for the "most probable" call stack + // we simply take the first real event type + if ((_top->data() == 0) || + (_top->data()->eventTypes()->realCount() <1)) return; + EventType* e = _top->data()->eventTypes()->realType(0); + + // try to extend to upper stack frames + while (_top && (max-- >0)) { + TraceCall* call = 0; + most = 0; + foreach(TraceCall* c, _top->callers()) { + // 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(e); + 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; + + if (extend && (_top == fn)) { + // extend at top + extendTop(); + f = _top; + } + + foreach(TraceCall* c, _calls) { + f = c->called(); + if (f == fn) + return c->caller(); + } + return 0; +} + +TraceFunction* Stack::called(TraceFunction* fn, bool extend) +{ + TraceFunction* f; + + foreach(TraceCall* c, _calls) { + f = c->caller(); + if (f == fn) + return c->called(); + } + + if (extend) { + // extend at bottom + extendBottom(); + + // and search again + foreach(TraceCall* c, _calls) { + 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; + + foreach(TraceCall* c, _calls) { + f = c->called(); + if (f == fn) + return true; + } + + // try to extend at bottom (even if callCount 0) + foreach(TraceCall* c, f->callings()) { + f = c->called(); + if (f == fn) { + _calls.append(c); + + // extend at bottom after found one + extendBottom(); + return true; + } + } + + // try to extend at top (even if callCount 0) + foreach(TraceCall* c, _top->callers()) { + f = c->caller(); + if (f == fn) { + _calls.prepend(c); + + // extend at top after found one + extendTop(); + return true; + } + } + + return false; +} + +Stack* Stack::split(TraceFunction* f) +{ + TraceCallList calls = _calls; + + // cycles are listed on there own + if (f->cycle() == f) return 0; + if (_top->cycle() == _top) return 0; + + foreach(TraceCall* c, calls) { + foreach(TraceCall* c2, c->called()->callings()) { + if (c2 == c) continue; + if (c2->called() != f) continue; + + // remove bottom part + while (!calls.isEmpty() && (calls.last()!=c)) + calls.removeLast(); + + calls.append(c2); + return new Stack(_top, calls); + } + } + return 0; +} + +QString Stack::toString() +{ + QString res = _top->name(); + foreach(TraceCall *c, _calls) + 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(), qPrintable(_function->name()), + qPrintable(_stack->toString())); +*/ +} + +HistoryItem::~HistoryItem() +{ + if (0) qDebug("Deleting Stack History Item (sRef %d): %s", + _stack->refCount(), + qPrintable(_function->name())); + + 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", qPrintable(f->name())); + + 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/libcore/stackbrowser.h b/kcachegrind/libcore/stackbrowser.h new file mode 100644 index 00000000..0d8c704b --- /dev/null +++ b/kcachegrind/libcore/stackbrowser.h @@ -0,0 +1,109 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#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() const { 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/libcore/subcost.cpp b/kcachegrind/libcore/subcost.cpp new file mode 100644 index 00000000..a7c14725 --- /dev/null +++ b/kcachegrind/libcore/subcost.cpp @@ -0,0 +1,107 @@ +/* This file is part of KCachegrind. + Copyright (C) 2004 Josef Weidendorfer + + 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. +*/ + + +#include "subcost.h" +#include "tracedata.h" + +#include + + +//--------------------------------------------------- +// 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(char sep) const +{ + unsigned long long n = v; + + if (n==0) return QString("0"); + + int i = 0; + QString res = ""; + + while (n) { + if ((i>0) && !(i%3)) res = sep + res; + i++; + res = QChar('0'+int(n%10)) + res; + n /= 10; + } + return res; +} + + + + +// HighestCostList + +HighestCostList::HighestCostList() +{ + _costType = 0; + clear(1); +} + +void HighestCostList::clear(int maxSize) +{ + _maxSize = maxSize; + _count = 0; + _item.resize(maxSize); + _cost.resize(maxSize); +} + +void HighestCostList::addCost(ProfileCostArray* c, SubCost cost) +{ + int i; + + if (_maxSize == 0) return; + + _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/libcore/subcost.h b/kcachegrind/libcore/subcost.h new file mode 100644 index 00000000..b3ad0bd7 --- /dev/null +++ b/kcachegrind/libcore/subcost.h @@ -0,0 +1,102 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2004 Josef Weidendorfer + + 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. +*/ + +#ifndef SUBCOST_H +#define SUBCOST_H + +#include +#include + +#include "utils.h" + +typedef unsigned long long uint64; + +/** + * Cost event counter, simple wrapper around a 64bit entity + */ +class SubCost +{ + public: + SubCost() { v=0; } + 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; } + operator uint64() const { 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(char sep = ' ') const; + + uint64 v; +}; + +class ProfileCostArray; +class EventType; +typedef QList TraceCostList; + +/** + * A class to calculate the ProfileCostArray items + * with highest cost. + */ + +class HighestCostList +{ + public: + HighestCostList(); + + void clear(int maxSize); + void addCost(ProfileCostArray*, SubCost); + int count() { return _count; } + int realCount() { return (_count > _maxSize) ? _maxSize:_count; } + int maxSize() { return _maxSize; } + bool hasMore() { return _count > _maxSize; } + ProfileCostArray* operator[] (int i) + { return (i>=0 && i<_count && i<_maxSize) ? _item[i] : 0; } + + private: + TraceCostList _list; + int _maxSize, _count; + EventType* _costType; + QVector _item; + QVector _cost; +}; + + +#endif diff --git a/kcachegrind/libcore/tracedata.cpp b/kcachegrind/libcore/tracedata.cpp new file mode 100644 index 00000000..b38e65b6 --- /dev/null +++ b/kcachegrind/libcore/tracedata.cpp @@ -0,0 +1,3744 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + + +#include "tracedata.h" + +#include +#include + +#include +#include +#include +#include + +#include "logger.h" +#include "loader.h" +#include "globalconfig.h" +#include "utils.h" +#include "fixcost.h" + + +#define TRACE_DEBUG 0 +#define TRACE_ASSERTIONS 0 + + + + +//--------------------------------------------------- +// TraceJumpCost + +TraceJumpCost::TraceJumpCost(ProfileContext* c) + :CostItem(c) +{ + TraceJumpCost::clear(); +} + +TraceJumpCost::~TraceJumpCost() +{} + +SubCost TraceJumpCost::executedCount() +{ + if (_dirty) update(); + + return _executedCount; +} + +SubCost TraceJumpCost::followedCount() +{ + if (_dirty) update(); + + return _followedCount; +} + +QString TraceJumpCost::costString(EventTypeSet*) +{ + 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(); +} + + + +//--------------------------------------------------- +// TraceCallCost + +TraceCallCost::TraceCallCost(ProfileContext* context) + : ProfileCostArray(context) +{ + _callCount = 0; +} + +TraceCallCost::~TraceCallCost() +{} + + +QString TraceCallCost::costString(EventTypeSet* m) +{ + return QString("%1, Calls %2") + .arg(ProfileCostArray::costString(m)) + .arg(_callCount.pretty()); +} + +QString TraceCallCost::prettyCallCount() +{ + return _callCount.pretty(); +} + +void TraceCallCost::clear() +{ + _callCount = 0; + ProfileCostArray::clear(); +} + +SubCost TraceCallCost::callCount() +{ + if (_dirty) update(); + + return _callCount; +} + +void TraceCallCost::addCallCount(SubCost c) +{ + _callCount += c; + + invalidate(); +} + + +//--------------------------------------------------- +// TraceInclusiveCost + +TraceInclusiveCost::TraceInclusiveCost(ProfileContext* context) + : ProfileCostArray(context), _inclusive(context) +{} + +TraceInclusiveCost::~TraceInclusiveCost() +{} + +QString TraceInclusiveCost::costString(EventTypeSet* m) +{ + return QString("%1, Inclusive %2") + .arg(ProfileCostArray::costString(m)) + .arg(_inclusive.costString(m)); +} + +void TraceInclusiveCost::clear() +{ + _inclusive.clear(); + ProfileCostArray::clear(); +} + +ProfileCostArray* TraceInclusiveCost::inclusive() +{ + if (_dirty) update(); + + return &_inclusive; +} + +void TraceInclusiveCost::addInclusive(ProfileCostArray* c) +{ + _inclusive.addCost(c); + + invalidate(); +} + + +//--------------------------------------------------- +// TraceListCost + +TraceListCost::TraceListCost(ProfileContext* context) + : ProfileCostArray(context) +{ + _lastDep = 0; +} + +TraceListCost::~TraceListCost() +{} + +void TraceListCost::addDep(ProfileCostArray* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.contains(dep)) { + qDebug("addDep: %s already in list!", + qPrintable(dep->fullName())); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(dep->fullName()), + _deps.count()); +#endif +} + +ProfileCostArray* TraceListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + foreach(ProfileCostArray* dep, _deps) { + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + } + return 0; +} + + +void TraceListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + qPrintable( fullName() ), _deps.count()); +#endif + + clear(); + foreach(ProfileCostArray* item, _deps) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + + + +//--------------------------------------------------- +// TraceJumpListCost + +TraceJumpListCost::TraceJumpListCost(ProfileContext* context) + : TraceJumpCost(context) +{ + _lastDep = 0; +} + +TraceJumpListCost::~TraceJumpListCost() +{} + +void TraceJumpListCost::addDep(TraceJumpCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.contains(dep)) { + qDebug("addDep: %s already in list!", + qPrintable(dep->fullName())); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(dep->fullName()), + _deps.count()); +#endif +} + +TraceJumpCost* TraceJumpListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + foreach(TraceJumpCost* dep, _deps) { + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + } + return 0; +} + + +void TraceJumpListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + qPrintable( fullName() ), _deps.count()); +#endif + + clear(); + foreach(TraceJumpCost* item, _deps) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + + + +//--------------------------------------------------- +// TraceCallListCost + +TraceCallListCost::TraceCallListCost(ProfileContext* context) + : TraceCallCost(context) +{ + _lastDep = 0; +} + +TraceCallListCost::~TraceCallListCost() +{} + +void TraceCallListCost::addDep(TraceCallCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.contains(dep)) { + qDebug("addDep: %s already in list!", + qPrintable(dep->fullName())); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(dep->fullName()), + _deps.count()); +#endif +} + +TraceCallCost* TraceCallListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + foreach(TraceCallCost* dep, _deps) { + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + } + return 0; +} + + +void TraceCallListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + qPrintable( fullName() ), _deps.count()); +#endif + + /* Without dependent cost items, assume fixed costs, + * i.e. do not change cost */ + if (_deps.count()>0) { + clear(); + foreach(TraceCallCost* item, _deps) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addCallCount(item->callCount()); + } + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + + +//--------------------------------------------------- +// TraceInclusiveListCost + +TraceInclusiveListCost::TraceInclusiveListCost(ProfileContext* context) + : TraceInclusiveCost(context) +{ + _lastDep = 0; +} + +TraceInclusiveListCost::~TraceInclusiveListCost() +{} + + +void TraceInclusiveListCost::addDep(TraceInclusiveCost* dep) +{ +#if TRACE_ASSERTIONS + if (_deps.contains(dep)) { + qDebug("addDep: %s already in list!", + qPrintable(dep->fullName())); + return; + } +#endif + + _deps.append(dep); + _lastDep = dep; + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(dep->fullName()), + _deps.count()); +#endif +} + +TraceInclusiveCost* TraceInclusiveListCost::findDepFromPart(TracePart* part) +{ + if (_lastDep && _lastDep->part() == part) + return _lastDep; + + foreach(TraceInclusiveCost* dep, _deps) { + if (dep->part() == part) { + _lastDep = dep; + return dep; + } + } + return 0; +} + +void TraceInclusiveListCost::update() +{ + if (!_dirty) return; + +#if TRACE_DEBUG + qDebug("update %s (count %d)", + qPrintable( fullName() ), _deps.count()); +#endif + + clear(); + foreach(TraceInclusiveCost* item, _deps) { + if (onlyActiveParts()) + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addInclusive(item->inclusive()); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + + + +//--------------------------------------------------- +// TracePartInstrJump + +TracePartInstrJump::TracePartInstrJump(TraceInstrJump* instrJump, + TracePartInstrJump* next) + : TraceJumpCost(ProfileContext::context(ProfileContext::PartInstrJump)) +{ + _dep = instrJump; + _next = next; +} + +TracePartInstrJump::~TracePartInstrJump() +{} + + +//--------------------------------------------------- +// TracePartInstrCall + +TracePartInstrCall::TracePartInstrCall(TraceInstrCall* instrCall) + : TraceCallCost(ProfileContext::context(ProfileContext::PartInstrCall)) +{ + _dep = instrCall; +} + +TracePartInstrCall::~TracePartInstrCall() +{} + + + +//--------------------------------------------------- +// TracePartInstr + +TracePartInstr::TracePartInstr(TraceInstr* instr) + : ProfileCostArray(ProfileContext::context(ProfileContext::PartInstr)) +{ + _dep = instr; +} + +TracePartInstr::~TracePartInstr() +{} + + + +//--------------------------------------------------- +// TracePartLineJump + +TracePartLineJump::TracePartLineJump(TraceLineJump* lineJump) + : TraceJumpCost(ProfileContext::context(ProfileContext::PartLineJump)) +{ + _dep = lineJump; +} + +TracePartLineJump::~TracePartLineJump() +{} + + +//--------------------------------------------------- +// TracePartLineCall + +TracePartLineCall::TracePartLineCall(TraceLineCall* lineCall) + : TraceCallCost(ProfileContext::context(ProfileContext::PartLineCall)) +{ + _dep = lineCall; +} + +TracePartLineCall::~TracePartLineCall() +{} + + +//--------------------------------------------------- +// TracePartLine + +TracePartLine::TracePartLine(TraceLine* line) + : ProfileCostArray(ProfileContext::context(ProfileContext::PartLine)) +{ + _dep = line; +} + +TracePartLine::~TracePartLine() +{} + + + + +//--------------------------------------------------- +// TracePartCall + +TracePartCall::TracePartCall(TraceCall* call) + : TraceCallListCost(ProfileContext::context(ProfileContext::PartCall)) +{ + _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", qPrintable( fullName() )); +#endif + + /* Without dependent cost items, assume fixed costs, + * i.e. do not change cost */ + if (_firstFixCallCost) { + clear(); + FixCallCost* item; + for (item = _firstFixCallCost; item; item = item->nextCostOfPartCall()) + item->addTo(this); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif + +#endif // USE_FIXCOST +} + + +//--------------------------------------------------- +// TracePartFunction + +TracePartFunction::TracePartFunction(TraceFunction* function, + TracePartObject* partObject, + TracePartFile *partFile) + : TraceInclusiveCost(ProfileContext::context(ProfileContext::PartFunction)) +{ + _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(EventTypeSet* 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.contains(ref)) { + qDebug("TracePartFunction::addPartInstr: %s already in list!", + qPrintable(ref->name())); + return; + } +#endif + + _partInstr.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(ref->fullName()), + _partInstr.count()); +#endif +} + + +void TracePartFunction::addPartLine(TracePartLine* ref) +{ +#if TRACE_ASSERTIONS + if (_partLines.contains(ref)) { + qDebug("TracePartFunction::addPartLine: %s already in list!", + qPrintable(ref->name())); + return; + } +#endif + + _partLines.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), qPrintable(ref->fullName()), + _partLines.count()); +#endif +} + + +void TracePartFunction::addPartCaller(TracePartCall* ref) +{ +#if TRACE_ASSERTIONS + if (_partCallers.contains(ref)) { + qDebug("TracePartFunction::addPartCaller: %s already in list!", + qPrintable(ref->name())); + return; + } +#endif + + _partCallers.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Caller\n %s (now %d)", + qPrintable( fullName() ), qPrintable(ref->fullName()), + _partCallers.count()); +#endif +} + + +void TracePartFunction::addPartCalling(TracePartCall* ref) +{ +#if TRACE_ASSERTIONS + if (_partCallings.contains(ref)) { + qDebug("TracePartFunction::addPartCalling: %s already in list!", + qPrintable(ref->name())); + return; + } +#endif + + _partCallings.append(ref); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Calling\n %s (now %d)", + qPrintable( fullName() ), qPrintable(ref->fullName()), + _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)", + qPrintable(name()), _partCallers.count(), _partCallings.count(), + _partLines.count()); +#endif + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + + // To calculate context counts, we just use first real event type (FIXME?) + EventType* e = data() ? data()->eventTypes()->realType(0) : 0; + + // calculate additional cost metrics + foreach(TracePartCall* caller, _partCallers) { + if (e && (caller->subCost(e) >0)) + _calledContexts++; + + SubCost c = caller->callCount(); + if (c>0) { + _calledCount += c; + } + } + foreach(TracePartCall* calling, _partCallings) { + if (e && (calling->subCost(e)>0)) + _callingContexts++; + + SubCost c = calling->callCount(); + if (c>0) { + _callingCount += c; + } + } + + // self cost +#if !USE_FIXCOST + if (_partLines.count()>0) { + ProfileCostArray::clear(); + + foreach(TracePartLine* line, _partLines) + addCost(line); + } +#else + if (_firstFixCost) { + ProfileCostArray::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 + foreach(TracePartCall* caller, _partCallers) { + // detect simple recursion (no cycle) + if (caller->isRecursion()) continue; + + addInclusive(caller); + } + } + else { + // without caller info, use calling sum + line costs + foreach(TracePartCall* calling, _partCallings) { + // detect simple recursion (no cycle) + if (calling->isRecursion()) continue; + + addInclusive(calling); + } + _dirty = false; // do not recurse! + addInclusive(this); + } + + _dirty = false; + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + + + +//--------------------------------------------------- +// TracePartClass + +TracePartClass::TracePartClass(TraceClass* cls) + : TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartClass)) +{ + _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) + : TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartFile)) +{ + _dep = file; +} + +TracePartFile::~TracePartFile() +{} + + +//--------------------------------------------------- +// TracePartObject + +TracePartObject::TracePartObject(TraceObject* object) + : TraceInclusiveListCost(ProfileContext::context(ProfileContext::PartObject)) +{ + _dep = object; +} + +TracePartObject::~TracePartObject() +{} + + + + +//--------------------------------------------------- +// TraceInstrJump + +TraceInstrJump::TraceInstrJump(TraceInstr* instrFrom, TraceInstr* instrTo, + bool isCondJump) + : TraceJumpCost(ProfileContext::context(ProfileContext::InstrJump)) +{ + _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 recently used + if (item && + (item->instrJump()==this) && + (item->part() == part)) return item; + + for(item = _first; item; item = item->next()) + if (item->part() == part) + return 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", qPrintable( fullName() )); +#endif + +#if TRACE_DEBUG + qDebug(" > %s", qPrintable(costString(0))); +#endif +} + +QString TraceInstrJump::name() const +{ + return QString("jump at 0x%1 to 0x%2") + .arg(_instrFrom->addr().toString()) + .arg(_instrTo->addr().toString()); +} + + +//--------------------------------------------------- +// TraceLineJump + +TraceLineJump::TraceLineJump(TraceLine* lineFrom, TraceLine* lineTo, + bool isCondJump) + : TraceJumpListCost(ProfileContext::context(ProfileContext::LineJump)) +{ + _lineFrom = lineFrom; + _lineTo = lineTo; + _isCondJump = isCondJump; +} + +TraceLineJump::~TraceLineJump() +{ + // we are the owner of TracePartLineJump's generated in our factory + qDeleteAll(_deps); +} + + +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()); +} + + +//--------------------------------------------------- +// TraceInstrCall + +TraceInstrCall::TraceInstrCall(TraceCall* call, TraceInstr* instr) + : TraceCallListCost(ProfileContext::context(ProfileContext::InstrCall)) +{ + _call = call; + _instr = instr; +} + +TraceInstrCall::~TraceInstrCall() +{ + // we are the owner of TracePartInstrCall's generated in our factory + qDeleteAll(_deps); +} + + +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) + : TraceCallListCost(ProfileContext::context(ProfileContext::LineCall)) +{ + _call = call; + + _line = line; +} + +TraceLineCall::~TraceLineCall() +{ + // we are the owner of TracePartLineCall's generated in our factory + qDeleteAll(_deps); +} + + +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) + : TraceCallListCost(ProfileContext::context(ProfileContext::Call)) +{ + _caller = caller; + _called = called; +} + + +TraceCall::~TraceCall() +{ + // we are the owner of all items generated in our factories + qDeleteAll(_deps); + qDeleteAll(_lineCalls); +} + +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) +{ + foreach(TraceInstrCall* icall, _instrCalls) + if (icall->instr() == i) + return icall; + + TraceInstrCall* icall = new TraceInstrCall(this, i); + _instrCalls.append(icall); + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceCall::instrCall]", qPrintable(icall->fullName())); +#endif + i->addInstrCall(icall); + return icall; +} + + +TraceLineCall* TraceCall::lineCall(TraceLine* l) +{ + foreach(TraceLineCall* lcall, _lineCalls) + if (lcall->line() == l) + return lcall; + + TraceLineCall* lcall = new TraceLineCall(this, l); + _lineCalls.append(lcall); + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceCall::lineCall]", qPrintable(lcall->fullName())); +#endif + l->addLineCall(lcall); + return lcall; +} + + +void TraceCall::invalidateDynamicCost() +{ + foreach(TraceLineCall* lc, _lineCalls) + lc->invalidate(); + + foreach(TraceInstrCall* ic, _instrCalls) + 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 QObject::tr("(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 QObject::tr("%1 via %2").arg(_caller->prettyName()).arg(via); + } + } + + return _caller->prettyName(); +} + +QString TraceCall::calledName(bool skipCycle) const +{ + if (!_called) return QObject::tr("(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 QObject::tr("%1 via %2").arg(c->name()).arg(via); + } + } + return _called->prettyName(); +} + + +//--------------------------------------------------- +// TraceInstr + +TraceInstr::TraceInstr() + : TraceListCost(ProfileContext::context(ProfileContext::Instr)) +{ + _addr = 0; + _line = 0; + _function = 0; +} + +TraceInstr::~TraceInstr() +{ + // we are the owner of items generated in our factories + qDeleteAll(_deps); + qDeleteAll(_instrJumps); +} + +bool TraceInstr::hasCost(EventType* ct) +{ + if (subCost(ct) >0) + return true; + + foreach(TraceInstrCall* ic, _instrCalls) + if (ic->subCost(ct) >0) + return true; + + foreach(TraceInstrJump* ij, _instrJumps) + if (ij->executedCount() >0) + return true; + + return false; +} + +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) +{ + foreach(TraceInstrJump* jump, _instrJumps) + if (jump->instrTo() == to) + return jump; + + TraceInstrJump* jump = new TraceInstrJump(this, to, isJmpCond); + _instrJumps.append(jump); + return jump; +} + + + +void TraceInstr::addInstrCall(TraceInstrCall* instrCall) +{ +#if TRACE_ASSERTIONS + if (_instrCalls.contains(instrCall)) return; + + if (instrCall->instr() != this) { + qDebug("Can not add instruction call to another instruction!"); + return; + } +#endif + + _instrCalls.append(instrCall); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), + qPrintable(instrCall->fullName()), _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() + : TraceListCost(ProfileContext::context(ProfileContext::Line)) +{ + _lineno = 0; + _sourceFile = 0; +} + +TraceLine::~TraceLine() +{ + // we are the owner of items generated in our factories + qDeleteAll(_deps); + qDeleteAll(_lineJumps); +} + +bool TraceLine::hasCost(EventType* ct) +{ + if (subCost(ct) >0) + return true; + + foreach(TraceLineCall* lc, _lineCalls) + if (lc->subCost(ct) >0) + return true; + + foreach(TraceLineJump* lj, _lineJumps) + if (lj->executedCount() >0) + return true; + + return false; +} + +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) +{ + foreach(TraceLineJump* jump, _lineJumps) + if (jump->lineTo() == to) + return jump; + + TraceLineJump* jump = new TraceLineJump(this, to, isJmpCond); + _lineJumps.append(jump); + return jump; +} + + +void TraceLine::addLineCall(TraceLineCall* lineCall) +{ +#if TRACE_ASSERTIONS + if (_lineCalls.contains(lineCall)) return; + + if (lineCall->line() != this) { + qDebug("Can not 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(), + qPrintable(caller->info()), qPrintable(function->info())); + } + } + + _lineCalls.append(lineCall); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), + qPrintable(lineCall->fullName()), _lineCalls.count()); +#endif +} + + +QString TraceLine::name() const +{ + QString fileShortName = _sourceFile->file()->shortName(); + if (fileShortName.isEmpty()) + return TraceFile::prettyEmptyName(); + + 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(ProfileContext* context) + : TraceInclusiveListCost(context) +{ +} + +TraceCostItem::~TraceCostItem() +{} + + +//--------------------------------------------------- +// TraceFunctionSource + +TraceFunctionSource::TraceFunctionSource(TraceFunction* function, + TraceFile* file) + : ProfileCostArray(ProfileContext::context(ProfileContext::FunctionSource)) +{ + _file = file; + _function = function; + + // the function is dependent from our cost sum + _dep = _function; + + _lineMap = 0; + _lineMapFilled = false; + _line0 = 0; +} + +TraceFunctionSource::~TraceFunctionSource() +{ + delete _lineMap; + 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.value()); + } + + 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]", + qPrintable(l.fullName())); +#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 + */ + foreach(TraceInclusiveCost* ic, _function->deps()) { + TracePartFunction* pf = (TracePartFunction*) ic; + + if (0) qDebug("PartFunction %s:%d", + qPrintable(pf->function()->name()), + 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; + } + + // do not 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); + } + + foreach(TracePartCall* pc, pf->partCallings()) { + + if (0) qDebug("PartCall %s:%d", + qPrintable(pc->call()->name()), + 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", + qPrintable(fcc->functionSource()->file()->shortName()), + fcc->line(), qPrintable(fcc->addr().toString()), + qPrintable(fcc->callCount().pretty())); + } + } + } + +#endif + + return _lineMap; +} + + + +//--------------------------------------------------- +// TraceAssociation + +TraceAssociation::TraceAssociation() +{ + _function = 0; + _valid = false; +} + +TraceAssociation::~TraceAssociation() +{ + // do not delete from TraceFunction + if (_function) _function->removeAssociation(this); +} + +bool TraceAssociation::isAssociated() +{ + if (!_function) return false; + + return _function->association(rtti())==this; +} + +bool TraceAssociation::setFunction(TraceFunction* f) +{ + if (_function == f) + return isAssociated(); + + if (_function) { + // do not delete ourself + _function->removeAssociation(this); + } + + _function = f; + if (f && f->association(rtti()) == 0) { + f->addAssociation(this); + return true; + } + return false; +} + +void TraceAssociation::clear(TraceData* d, int rtti) +{ + TraceFunctionMap::Iterator it; + for ( it = d->functionMap().begin(); + it != d->functionMap().end(); ++it ) + (*it).removeAssociation(rtti); +} + +void TraceAssociation::invalidate(TraceData* d, int rtti) +{ + TraceFunctionMap::Iterator it; + for ( it = d->functionMap().begin(); + it != d->functionMap().end(); ++it ) + (*it).invalidateAssociation(rtti); +} + + +//--------------------------------------------------- +// TraceFunction + +TraceFunction::TraceFunction() + : TraceCostItem(ProfileContext::context(ProfileContext::Function)) +{ + _object = 0; + _file = 0; + _cls = 0; + _cycle = 0; + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + + _instrMap = 0; + _instrMapFilled = false; +} + + +TraceFunction::~TraceFunction() +{ + qDeleteAll(_associations); + + // we are the owner of items generated in our factories + qDeleteAll(_deps); + qDeleteAll(_callings); + qDeleteAll(_sourceFiles); + + delete _instrMap; +} + +// no unique check is done! +void TraceFunction::addAssociation(TraceAssociation* a) +{ + if (!a) return; + _associations.append(a); +} + +void TraceFunction::removeAssociation(TraceAssociation* a) +{ + _associations.removeAll(a); +} + +void TraceFunction::removeAssociation(int rtti, bool reallyDelete) +{ + if (rtti==0) { + if (reallyDelete) + qDeleteAll(_associations); + _associations.clear(); + return; + } + + foreach(TraceAssociation* a, _associations) { + if (a->rtti() == rtti) { + if (reallyDelete) delete a; + _associations.removeAll(a); + return; + } + } +} + +void TraceFunction::invalidateAssociation(int rtti) +{ + foreach(TraceAssociation* a, _associations) { + if ((rtti==0) || (a->rtti() == rtti)) + a->invalidate(); + } +} + +TraceAssociation* TraceFunction::association(int rtti) +{ + foreach(TraceAssociation* a, _associations) { + if (a->rtti() == rtti) + return a; + } + return 0; +} + +#if 0 +// helper for prettyName +bool TraceFunction::isUniquePrefix(const 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; +} +#endif + +QString TraceFunction::prettyName() const +{ + QString res = _name; + + if (_name.isEmpty()) + return prettyEmptyName(); + + if (GlobalConfig::hideTemplates()) { + + res = QString(); + int d = 0; + for(int i=0;i<_name.length();i++) { + switch(_name[i].toLatin1()) { + case '<': + if (d<=0) res.append(_name[i]); + d++; + break; + case '>': + d--; + // fall trough + default: + if (d<=0) res.append(_name[i]); + break; + } + } + } +#if 0 + // TODO: make it a configuration, but disabled by default. + // + // Stripping parameter signature of C++ symbols is fine + // if the function name is unique in the whole program. + // However, we only can detect if it is unique in the profile, + // which makes this "beautification" potentially confusing + int p = _name.indexOf('('); + if (p>0) { + // handle C++ "operator()" correct + if ( (p+2 < _name.size()) && (_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); + } +#endif + + // cycle members + if (_cycle) { + if (_cycle != this) + res = QString("%1 ").arg(res).arg(_cycle->cycleNo()); + else + res = QString("").arg(_cycle->cycleNo()); + } + + + return res; +} + +QString TraceFunction::formattedName() const +{ + // produce a "rich" name only if templates are hidden + if (!GlobalConfig::hideTemplates() || _name.isEmpty()) return QString(); + + // bold, but inside template parameters normal, function arguments italic + QString rich(""); + int d = 0; + for(int i=0;i<_name.length();i++) { + switch(_name[i].toLatin1()) { + case '&': + rich.append("&"); + break; + case '<': + d++; + rich.append("<"); + if (d==1) + rich.append(""); + break; + case '>': + d--; + if (d==0) + rich.append(""); + rich.append("> "); // add space to allow for line break + break; + case '(': + rich.append("("); + break; + case ')': + rich.append(")"); + break; + default: + rich.append(_name[i]); + break; + } + } + rich.append(""); + return rich; +} + +QString TraceFunction::prettyEmptyName() +{ + return QObject::tr("(unknown)"); +} + +/* + * 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; + foreach(TraceFunctionSource* sourceFile, _sourceFiles) { + 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 QObject::tr("(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->constBegin(); + return (*it).addr(); +} + +Addr TraceFunction::lastAddress() const +{ + // ignore address 0 here + if (!_instrMap || _instrMap->count() == 0) return 0; + TraceInstrMap::ConstIterator it = _instrMap->constEnd(); + --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.value()); + } + + 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]", + qPrintable(i.fullName())); +#endif + } + return &i; +} + +void TraceFunction::addCaller(TraceCall* caller) +{ +#if TRACE_ASSERTIONS + if (caller->called() != this) { + qDebug("Can not add call to another line!\n"); + return; + } + + if (_callers.contains(caller)) return; +#endif + + _callers.append(caller); + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added Caller\n %s (now %d)", + qPrintable( fullName() ), qPrintable(caller->fullName()), _callers.count()); +#endif +} + + + +TraceCall* TraceFunction::calling(TraceFunction* called) +{ + foreach(TraceCall* calling, _callings) + if (calling->called() == called) + return calling; + + TraceCall* calling = new TraceCall(this, called); + _callings.append(calling); + + // we have to invalidate ourself so invalidations from item propagate up + invalidate(); + +#if TRACE_DEBUG + qDebug("Created %s [TraceFunction::calling]", qPrintable(calling->fullName())); +#endif + called->addCaller(calling); + return calling; +} + +TraceFunctionSource* TraceFunction::sourceFile(TraceFile* file, + bool createNew) +{ + if (!file) file = _file; + + foreach(TraceFunctionSource* sourceFile, _sourceFiles) + if (sourceFile->file() == file) + return sourceFile; + + if (!createNew) return 0; + + TraceFunctionSource* 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]", + qPrintable(file->name())); +#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; + + // inner-cycle-callers + foreach(TraceCall* c, _callers) + if (c->caller()->cycle() == _cycle) + l.append(c); + + // call from cycle itself + foreach(TraceCall* c, _cycle->_callings) + if (c->called() == this) { + l.append(c); + return l; + } + } + + return _callers; +} + +const TraceCallList& TraceFunction::callings(bool /* skipCycle */) const +{ + return _callings; +} + +void TraceFunction::invalidateDynamicCost() +{ + foreach(TraceCall* c, _callings) + c->invalidateDynamicCost(); + + foreach(TraceFunctionSource* sf, _sourceFiles) + 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)", + qPrintable(_name), _callers.count(), + _sourceFiles.count(), _instrMap ? _instrMap->count():0); +#endif + + _calledCount = 0; + _callingCount = 0; + _calledContexts = 0; + _callingContexts = 0; + clear(); + + // To calculate context counts, we just use first real event type (FIXME?) + EventType* e = data() ? data()->eventTypes()->realType(0) : 0; + + // context count is NOT the sum of part contexts + foreach(TraceCall *caller, _callers) { + if (e && (caller->subCost(e) >0)) + _calledContexts++; + _calledCount += caller->callCount(); + } + + foreach(TraceCall* callee, _callings) { + if (e && (callee->subCost(e) >0)) + _callingContexts++; + _callingCount += callee->callCount(); + } + + if (data()->inFunctionCycleUpdate() || !_cycle) { + // usual case (no cycle member) + foreach(TraceInclusiveCost* item, _deps) { + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + addInclusive(item->inclusive()); + } + } + else { + // this is a cycle or cycle member + foreach(TraceCall* callee, _callings) { + + // ignore inner-cycle member calls for inclusive cost + if ((_cycle != this) && + (callee->inCycle()>0)) continue; + + addInclusive(callee); + } + + // self cost + if (type() == ProfileContext::FunctionCycle) { + // cycle: self cost is sum of cycle member self costs, but + // does not add to inclusive cost + foreach(TraceFunction* m, ((TraceFunctionCycle*)this)->members()) + addCost(m); + } + else { + // cycle member + foreach(TraceInclusiveCost* item, _deps) { + if (!item->part() || !item->part()->isActive()) continue; + + addCost(item); + } + _dirty = false; // do not recurse + addInclusive(this); + } + } + _dirty = false; + +#if TRACE_DEBUG + qDebug("> %s", qPrintable(costString(0))); +#endif +} + +bool TraceFunction::isCycle() +{ + return _cycle == this; +} + +bool TraceFunction::isCycleMember() +{ + return _cycle && (_cycle != this); +} + +void TraceFunction::cycleReset() +{ + _cycle = 0; + _cycleStackDown = 0; + _cycleLow = 0; +} + +// this does not mark functions calling themself ! +void TraceFunction::cycleDFS(int d, int& pNo, TraceFunction** pTop) +{ + if (_cycleLow != 0) return; + + // 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 ?! + */ + Q_ASSERT((data() != 0) && (data()->eventTypes()->realCount()>0)); + EventType* e = data()->eventTypes()->realType(0); + + SubCost base = 0; + if (_callers.count()>0) { + foreach(TraceCall* caller, _callers) + if (caller->subCost(e) > base) + base = caller->subCost(e); + } + else base = inclusive()->subCost(e); + + SubCost cutLimit = SubCost(base * GlobalConfig::cycleCut()); + + if (0) { + qDebug("%s (%d) Visiting %s", + qPrintable(QString().fill(' ', d)), + pNo, qPrintable(prettyName())); + qDebug("%s Cum. %s, Max Caller %s, cut limit %s", + qPrintable(QString().fill(' ', d)), + qPrintable(inclusive()->subCost(e).pretty()), + qPrintable(base.pretty()), + qPrintable(cutLimit.pretty())); + } + + foreach(TraceCall *callee, _callings) { + TraceFunction* called = callee->called(); + + // cycle cut heuristic + if (callee->subCost(e) < cutLimit) { + if (0) qDebug("%s Cut call to %s (cum. %s)", + qPrintable(QString().fill(' ', d)), + qPrintable(called->prettyName()), + qPrintable(callee->subCost(e).pretty())); + + 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 (low %d) Back to %s", + qPrintable(QString().fill(' ', d)), + _cycleLow, qPrintable(called->prettyName())); + } + } + + 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("Found Cycle %d with base %s:", + cycle->cycleNo(), qPrintable(prettyName())); + while(*pTop) { + TraceFunction* top = *pTop; + cycle->add(top); + + // remove from stack + *pTop = top->_cycleStackDown; + top->_cycleStackDown = 0; + + if (0) qDebug(" %s", qPrintable(top->prettyName())); + if (top == this) break; + } + } + } +} + + +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; + + foreach(TraceInclusiveCost* icost, deps()) { + TracePartFunction* pf = (TracePartFunction*) icost; + + if (0) qDebug("PartFunction %s:%d", + qPrintable(pf->function()->name()), + 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); + } + + foreach(TracePartCall* pc, pf->partCallings()) { + + if (0) qDebug("PartCall %s:%d", + qPrintable(pc->call()->name()), + 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", + qPrintable(fcc->functionSource()->file()->shortName()), + fcc->line(), qPrintable(fcc->addr().toString()), + qPrintable(fcc->callCount().pretty())); + } + } + } + +#endif + + return _instrMap; +} + + + +//--------------------------------------------------- +// TraceFunctionCycle + +TraceFunctionCycle::TraceFunctionCycle(TraceFunction* f, int n) +{ + _base = f; + _cycleNo = n; + _cycle = this; + + setContext(ProfileContext::context(ProfileContext::FunctionCycle)); + + setPosition(f->data()); + setName(QString("").arg(n)); + + // reset to attributes of base function + setFile(_base->file()); + setClass(_base->cls()); + setObject(_base->object()); +} + +void TraceFunctionCycle::init() +{ + _members.clear(); + _callers.clear(); + // this deletes all TraceCall's to members + _callings.clear(); + + invalidate(); +} + +void TraceFunctionCycle::add(TraceFunction* f) +{ + _members.append(f); +} + +void TraceFunctionCycle::setup() +{ + if (_members.count()==0) return; + + foreach(TraceFunction* f, _members) { + + // the cycle takes all outside callers from its members + foreach(TraceCall* call, f->callers()) { + if (_members.contains(call->caller())) continue; + _callers.append(call); + } + + // the cycle has a call to each member + TraceCall* call = new TraceCall(this, f); + call->invalidate(); + _callings.append(call); + + // now do some faking... + f->setCycle(this); + } + invalidate(); +} + + +//--------------------------------------------------- +// TraceClass + +TraceClass::TraceClass() + : TraceCostItem(ProfileContext::context(ProfileContext::Class)) +{} + +TraceClass::~TraceClass() +{ + // we are the owner of items generated in our factory + qDeleteAll(_deps); +} + +QString TraceClass::prettyName() const +{ + if (_name.isEmpty()) + return prettyEmptyName(); + return _name; +} + +QString TraceClass::prettyEmptyName() +{ + return QObject::tr("(global)"); +} + +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 not add function to a class not enclosing this function\n"); + return; + } + + if (_functions.contains(function)) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), + qPrintable(function->fullName()), _functions.count()); +#endif +} + + + +//--------------------------------------------------- +// TraceFile + +TraceFile::TraceFile() + : TraceCostItem(ProfileContext::context(ProfileContext::File)) +{} + +TraceFile::~TraceFile() +{ + // we are the owner of items generated in our factory + qDeleteAll(_deps); +} + +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 not add function to a file not enclosing this function\n"); + return; + } + + if (_functions.contains(function)) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), + qPrintable(function->fullName()), _functions.count()); +#endif +} + + +void TraceFile::addSourceFile(TraceFunctionSource* sourceFile) +{ +#if TRACE_ASSERTIONS + if (sourceFile->file() != this) { + qDebug("Can not add sourceFile to a file not having lines for it\n"); + return; + } +#endif + + _sourceFiles.append(sourceFile); + // not truly needed, as we do not use the sourceFiles for cost update + invalidate(); + +#if TRACE_DEBUG + qDebug("%s \n added SourceFile %s (now %d)", + qPrintable( fullName() ), qPrintable(sourceFile->fullName()), + _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.indexOf("/", lastIndex)) >=0) + lastIndex = index+1; + + if (lastIndex==0) return QString(); + + // without ending "/" + return _name.left(lastIndex-1); +} + + +QString TraceFile::shortName() const +{ + int lastIndex = 0, index; + while ( (index=_name.indexOf("/", lastIndex)) >=0) + lastIndex = index+1; + + return _name.mid(lastIndex); +} + +QString TraceFile::prettyName() const +{ + QString sn = shortName(); + + if (sn.isEmpty()) + return prettyEmptyName(); + + return sn; +} + +QString TraceFile::prettyEmptyName() +{ + return QObject::tr("(unknown)"); +} + +QString TraceFile::prettyLongName() const +{ + if (_name.isEmpty()) + return prettyEmptyName(); + return _name; +} + + +//--------------------------------------------------- +// TraceObject + +TraceObject::TraceObject() + : TraceCostItem(ProfileContext::context(ProfileContext::Object)) +{} + +TraceObject::~TraceObject() +{ + // we are the owner of items generated in our factory + qDeleteAll(_deps); +} + +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 not add function to an object not enclosing this function\n"); + return; + } + + if (_functions.contains(function)) return; +#endif + + _functions.append(function); + + invalidate(); + +#if TRACE_DEBUG + qDebug("%s added\n %s (now %d)", + qPrintable( fullName() ), + qPrintable(function->fullName()), _functions.count()); +#endif +} + +void TraceObject::setDirectory(const QString& dir) +{ + if (dir.endsWith('/')) + _dir = dir.left(dir.length()-1); + else + _dir = dir; +} + +QString TraceObject::directory() +{ + if (!_dir.isEmpty()) return _dir; + + int lastIndex = 0, index; + while ( (index=_name.indexOf("/", lastIndex)) >=0) + lastIndex = index+1; + + if (lastIndex==0) return QString(); + + // without ending "/" + return _name.left(lastIndex-1); +} + + +QString TraceObject::shortName() const +{ + int lastIndex = 0, index; + while ( (index=_name.indexOf("/", lastIndex)) >=0) + lastIndex = index+1; + + return _name.mid(lastIndex); +} + +QString TraceObject::prettyName() const +{ + QString sn = shortName(); + + if (sn.isEmpty()) + return prettyEmptyName(); + + return sn; +} + +QString TraceObject::prettyEmptyName() +{ + return QObject::tr("(unknown)"); +} + +//--------------------------------------------------- +// TracePart + +TracePart::TracePart(TraceData* data) + : TraceListCost(ProfileContext::context(ProfileContext::Part)) +{ + setPosition(data); + + _dep = data; + _active = true; + _number = 0; + _tid = 0; + _pid = 0; + + _eventTypeMapping = 0; +} + +TracePart::~TracePart() +{ + delete _eventTypeMapping; +} + +void TracePart::setPartNumber(int n) +{ + if (data()->maxPartNumber() setMaxPartNumber(n); + _number = n; +} + +void TracePart::setThreadID(int tid) +{ + if (data()->maxThreadID() setMaxThreadID(tid); + _tid = tid; +} + +void TracePart::setProcessID(int pid) +{ + _pid = pid; +} + + + +// strip path +QString TracePart::shortName() const +{ + int lastIndex = 0, index; + while ( (index=_name.indexOf("/", lastIndex)) >=0) + lastIndex = index+1; + + return _name.mid(lastIndex); +} + +QString TracePart::prettyName() const +{ + if (_pid==0) return shortName(); + QString name = QString("PID %1").arg(_pid); + if (_number>0) + name += QString(", section %2").arg(_number); + if ((data()->maxThreadID()>1) && (_tid>0)) + name += QString(", thread %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; +} + +bool TracePart::operator<(const TracePart& p2) const +{ + if (processID() < p2.processID()) return true; + + if (processID() == p2.processID()) { + if (partNumber() < p2.partNumber()) return true; + + if (partNumber() == p2.partNumber()) + return (threadID() < p2.threadID()); + } + return false; +} + + +//--------------------------------------------------- +// TraceData + + +// create vectors with reasonable default sizes, but not wasting memory +TraceData::TraceData(Logger* l) + : ProfileCostArray(ProfileContext::context(ProfileContext::Data)) +{ + _logger = l; + init(); +} + +void TraceData::init() +{ + _functionCycleCount = 0; + _inFunctionCycleUpdate = false; + + _maxThreadID = 0; + _maxPartNumber = 0; + _fixPool = 0; + _dynPool = 0; +} + +TraceData::~TraceData() +{ + qDeleteAll(_parts); + + delete _fixPool; + delete _dynPool; +} + +QString TraceData::shortTraceName() const +{ + int lastIndex = 0, index; + while ( (index=_traceName.indexOf("/", 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; +} + +bool partLessThan(const TracePart* p1, const TracePart* p2) +{ + return *p1 < *p2; +} + +/** + * Load a list of files. + * If only one file is given, it is assumed to be a prefix, and all + * existing files with that prefix are loaded. + * + * Returns 0 if nothing found to load + */ +int TraceData::load(QStringList files) +{ + if (files.isEmpty()) return 0; + + _traceName = files[0]; + if (files.count() == 1) { + QFileInfo finfo(_traceName); + QString prefix = finfo.fileName(); + QDir dir = finfo.dir(); + if (finfo.isDir()) { + prefix = "callgrind.out"; + _traceName += "/callgrind.out"; + } + + files = dir.entryList(QStringList() << prefix + "*", QDir::Files); + QStringList::Iterator it = files.begin(); + for (; it != files.end(); ++it ) { + *it = dir.path() + "/" + *it; + } + } + + if (files.isEmpty()) { + _traceName += ' ' + QObject::tr("(not found)"); + return 0; + } + + QStringList::const_iterator it; + int partsLoaded = 0; + for (it = files.constBegin(); it != files.constEnd(); ++it ) { + QFile file(*it); + partsLoaded += internalLoad(&file, *it); + } + if (partsLoaded == 0) return 0; + + qSort(_parts.begin(), _parts.end(), partLessThan); + invalidateDynamicCost(); + updateFunctionCycles(); + + return partsLoaded; +} + +int TraceData::load(QString file) +{ + return load(QStringList(file)); +} + +int TraceData::load(QIODevice* file, const QString& filename) +{ + _traceName = filename; + int partsLoaded = internalLoad(file, filename); + if (partsLoaded>0) { + invalidateDynamicCost(); + updateFunctionCycles(); + } + return partsLoaded; +} + +int TraceData::internalLoad(QIODevice* device, const QString& filename) +{ + if (!device->open( QIODevice::ReadOnly ) ) { + _logger->loadStart(filename); + _logger->loadFinished(QString(strerror( errno ))); + return 0; + } + + Loader* l = Loader::matchingLoader(device); + if (!l) { + // special case emtpy file: ignore... + if (device->size() == 0) return 0; + + _logger->loadStart(filename); + _logger->loadFinished(QString("Unknown file format")); + return 0; + } + l->setLogger(_logger); + + int partsLoaded = l->load(this, device, filename); + + l->setLogger(0); + + return partsLoaded; +} + +bool TraceData::activateParts(const TracePartList& l) +{ + bool changed = false; + + foreach(TracePart* part, _parts) + if (part->activate(l.contains(part))) + 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; + + foreach(TracePart* part, l) { + if (_parts.contains(part)) + 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); +} + +void TraceData::addPart(TracePart* part) +{ + if (_parts.contains(part)>0) return; + + if ((part->partNumber()==0) && + (part->processID()==0)) { + _maxPartNumber++; + part->setPartNumber(_maxPartNumber); + } + _parts.append(part); +} + +TracePart* TraceData::partWithName(const QString& name) +{ + foreach(TracePart* part, _parts) + if (part->name() == name) + return part; + return 0; +} + +QString TraceData::activePartRange() +{ + QString res; + int r1=-1, r2=-1, count=0; + foreach(TracePart* part, _parts) { + 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]", + qPrintable(o.fullName())); +#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]", + qPrintable(f.fullName())); +#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.indexOf('(', 0); + +#if 0 + int sIndex=fnName.find(" ", 0); + if (sIndex>=0) + if ((pIndex == -1) || (sIndex < pIndex)) + pIndex = sIndex; +#endif + + while ((index=fnName.indexOf("::", lastIndex)) >=0) { + if (pIndex>=0 && pIndexshortName() + object->shortName(); + + TraceFunctionMap::Iterator it; + it = _functionMap.find(key); + if (it == _functionMap.end()) { + it = _functionMap.insert(key, TraceFunction()); + TraceFunction& f = it.value(); + + 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", + qPrintable(f.fullName()), + qPrintable(c->fullName()), qPrintable(file->fullName()), + object ? qPrintable(object->fullName()) : "(unknown object)"); +#endif + + c->addFunction(&f); + object->addFunction(&f); + file->addFunction(&f); + } + + return &(it.value()); +} + +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(); + + foreach(TracePart* part, _parts) { + _totals.addCost(part->totals()); + if (part->isActive()) + addCost(part->totals()); + } + + _dirty = false; +} + +ProfileCostArray* TraceData::search(ProfileContext::Type t, QString name, + EventType* ct, ProfileCostArray* parent) +{ + ProfileCostArray* result = 0; + ProfileContext::Type pt; + SubCost sc, scTop = 0; + + pt = parent ? parent->type() : ProfileContext::InvalidType; + switch(t) { + case ProfileContext::Function: + { + TraceFunction *f; + TraceFunctionMap::Iterator it; + for ( it = _functionMap.begin(); + it != _functionMap.end(); ++it ) { + f = &(*it); + + if (f->name() != name) continue; + + if ((pt == ProfileContext::Class) && (parent != f->cls())) continue; + if ((pt == ProfileContext::File) && (parent != f->file())) continue; + if ((pt == ProfileContext::Object) && (parent != f->object())) continue; + + if (ct) { + sc = f->inclusive()->subCost(ct); + if (sc <= scTop) continue; + scTop = sc; + } + + result = f; + } + } + break; + + case ProfileContext::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 ProfileContext::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 ProfileContext::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 ProfileContext::Instr: + if (pt == ProfileContext::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 ProfileContext::Line: + { + TraceFunctionSourceList sList; + if (pt == ProfileContext::Function) + sList = ((TraceFunction*)parent)->sourceFiles(); + else if (pt == ProfileContext::FunctionSource) + sList.append((TraceFunctionSource*) parent); + else break; + + TraceLineMap* lineMap; + TraceLine* line; + TraceLineMap::Iterator it; + foreach(TraceFunctionSource* fs, sList) { + 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; + foreach(cycle, _functionCycles) + 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 + foreach(TraceFunctionCycle* cycle, _functionCycles) + cycle->init(); + + TraceFunctionMap::Iterator it; + for ( it = _functionMap.begin(); it != _functionMap.end(); ++it ) + (*it).cycleReset(); + + if (!GlobalConfig::showCycles()) return; + + _inFunctionCycleUpdate = true; + + +#if 0 + int fCount = _functionMap.size(), fNo = 0, progress=0, p; + QString msg = tr("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 + foreach(TraceFunctionCycle* cycle, _functionCycles) + 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(), 0); +#endif +} + +void TraceData::updateObjectCycles() +{ +} + + +void TraceData::updateClassCycles() +{ +} + + +void TraceData::updateFileCycles() +{ +} + diff --git a/kcachegrind/libcore/tracedata.h b/kcachegrind/libcore/tracedata.h new file mode 100644 index 00000000..4a362ee8 --- /dev/null +++ b/kcachegrind/libcore/tracedata.h @@ -0,0 +1,1510 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Classes holding profiling data for + * multiple tracefiles for one command. + * See class TraceData first. + */ + +#ifndef TRACEDATA_H +#define TRACEDATA_H + +#include +#include +#include + +#include "costitem.h" +#include "subcost.h" +#include "utils.h" +#include "addr.h" +#include "context.h" +#include "eventtype.h" + +class QFile; + +/** + * All cost items are classes prefixed with "Trace". + * "ProfileCostArray" 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 ProfileCostArray, and add needed + * cost metrics, e.g. for a call the number of calls that happened. + * + * Abstract, i.e. never instantiated cost items are + * - ProfileCostArray: Basic cost metrics (instr/read/write access + cache events) + * - TraceCallCost: Additional call count cost metric. + * - TraceInclusiveCost: Additional ProfileCostArray aggregated. + * - TraceListCost: Adds dependency to a list of ProfileCostArray'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, it is 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 FixCost; +class FixCallCost; +class FixJump; +class FixPool; +class DynPool; +class Logger; + +class ProfileCostArray; +class EventType; +class EventTypeSet; +class EventTypeMapping; +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 QList TraceCostList; +typedef QList TraceJumpCostList; +typedef QList TraceCallCostList; +typedef QList TraceInclusiveCostList; + +typedef QList TracePartCallList; +typedef QList TracePartInstrList; +typedef QList TracePartLineList; +typedef QList TracePartLineRegionList; +typedef QList TracePartFunctionList; +typedef QList TracePartInstrCallList; +typedef QList TracePartLineCallList; +typedef QList TracePartList; + +typedef QList TraceInstrList; +typedef QList TraceLineList; +typedef QList TraceInstrJumpList; +typedef QList TraceLineJumpList; +typedef QList TraceInstrCallList; +typedef QList TraceLineCallList; +typedef QList TraceCallList; +typedef QList TraceFileList; +typedef QList TraceLineRegionList; +typedef QList TraceFunctionSourceList; +typedef QList TraceFunctionList; +typedef QList TraceFunctionCycleList; +typedef QMap TraceObjectMap; +typedef QMap TraceClassMap; +typedef QMap TraceFileMap; +typedef QMap TraceFunctionMap; +typedef QMap TraceLineMap; +typedef QMap TraceInstrMap; + + +/** + * Cost of a (conditional) jump. + */ +class TraceJumpCost: public CostItem +{ + public: + TraceJumpCost(ProfileContext*); + virtual ~TraceJumpCost(); + + // reimplementations for cost addition + virtual QString costString(EventTypeSet* 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 ProfileCostArray +{ + public: + TraceCallCost(ProfileContext*); + virtual ~TraceCallCost(); + + // reimplementations for cost addition + virtual QString costString(EventTypeSet* 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 ProfileCostArray +{ + public: + TraceInclusiveCost(ProfileContext*); + virtual ~TraceInclusiveCost(); + + // reimplementations for cost addition + virtual QString costString(EventTypeSet* m); + virtual void clear(); + + // additional cost metric + ProfileCostArray* inclusive(); + void addInclusive(ProfileCostArray*); + + protected: + ProfileCostArray _inclusive; +}; + + +/** + * Cost Item + * depends on a list of cost items. + */ +class TraceListCost: public ProfileCostArray +{ + public: + TraceListCost(ProfileContext*); + virtual ~TraceListCost(); + + // reimplementation for dependency list + virtual void update(); + + TraceCostList& deps() { return _deps; } + void addDep(ProfileCostArray*); + ProfileCostArray* findDepFromPart(TracePart*); + + protected: + // overwrite in subclass to change update behaviour + virtual bool onlyActiveParts() { return false; } + + TraceCostList _deps; + + private: + // very temporary: cached + ProfileCostArray* _lastDep; +}; + + +/** + * Jump Cost Item + * depends on a list of Jump cost items. + */ +class TraceJumpListCost: public TraceJumpCost +{ + public: + TraceJumpListCost(ProfileContext*); + 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 + * depends on a list of Call cost items. + */ +class TraceCallListCost: public TraceCallCost +{ + public: + TraceCallListCost(ProfileContext*); + 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 depends on a list of inclusive cost items. + */ +class TraceInclusiveListCost: public TraceInclusiveCost +{ + public: + TraceInclusiveListCost(ProfileContext*); + 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(); + + // 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(); + + // 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 ProfileCostArray +{ +public: + TracePartInstr(TraceInstr*); + virtual ~TracePartInstr(); + + // 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(); + + // 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(); + + // 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 ProfileCostArray +{ +public: + TracePartLine(TraceLine*); + virtual ~TracePartLine(); + + // 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 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(); + + // 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 void update(); + virtual QString costString(EventTypeSet* 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(); + + 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(); + + 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(); + + 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*); + virtual ~TracePart(); + + virtual TracePart* part() { return this; } + virtual const TracePart* part() const { return this; } + + QString shortName() const; + QString prettyName() const; + /// @return Name of the file this part was loaded from + 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() const { return _number; } + int threadID() const { return _tid; } + int processID() const { 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 setName(const QString& n) { _name = n; } + void setPartNumber(int n); + void setThreadID(int t); + void setProcessID(int p); + ProfileCostArray* totals() { return &_totals; } + /* passes ownership of mapping */ + void setEventMapping(EventTypeMapping* sm) { _eventTypeMapping = sm; } + EventTypeMapping* eventTypeMapping() { return _eventTypeMapping; } + + // returns true if something changed + bool activate(bool); + bool isActive() const { return _active; } + + // for sorting + bool operator<(const TracePart&) const; + +private: + QIODevice* _file; + QString _name; + QString _descr; + QString _trigger; + QString _timeframe; + QString _version; + + int _number, _tid, _pid; + + bool _active; + + // the totals line + ProfileCostArray _totals; + + // event type mapping for all fix costs of this part + EventTypeMapping* _eventTypeMapping; +}; + + + +/*----------------------------------------------------------------- + * 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 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; +}; + + +/** + * 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 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; +}; + + +/** + * A call from an instruction of one function to another function + */ +class TraceInstrCall: public TraceCallListCost +{ + public: + TraceInstrCall(TraceCall* call, TraceInstr* instr); + virtual ~TraceInstrCall(); + + 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 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 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 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(EventType*); + + // 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 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(EventType*); + 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(ProfileContext*); + 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 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 ProfileCostArray +{ +public: + TraceFunctionSource(TraceFunction*, TraceFile*); + virtual ~TraceFunctionSource(); + + 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 association of objects with TraceFunctions. + * Used in coverage analysis and TreeMap drawing. + */ +class TraceAssociation +{ + public: + /** + * Creates an invalid association. + */ + TraceAssociation(); + virtual ~TraceAssociation(); + + // for runtime detection + virtual int rtti() { return 0; } + + /** + * Could we set the function association to ourself? + * This only can return false if this is a unique association. + */ + bool isAssociated(); + + /** + * reset function to associate this object to. + * returns true if association could be established + */ + bool setFunction(TraceFunction*); + TraceFunction* function() { return _function; } + + void invalidate() { _valid = false; } + bool isValid() { return _valid; } + + /** + * Delete all associations in TraceFunctions of data with + * rtti runtime info. rtti = 0: delete ALL associations. + */ + static void clear(TraceData* data, int rtti); + + /** + * Invalidate all associations in TraceFunctions of data with + * rtti runtime info. rtti = 0: Invalidate ALL associations. + */ + static void invalidate(TraceData* data, int rtti); + + protected: + TraceFunction* _function; + bool _valid; +}; + +typedef QList TraceAssociationList; + +/** + * 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 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 formattedName() const; + static QString prettyEmptyName(); + 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 TraceFunctionAssociation + void addAssociation(TraceAssociation* a); + void removeAssociation(TraceAssociation* a); + void removeAssociation(int rtti, bool reallyDelete = true); + void invalidateAssociation(int rtti); + TraceAssociation* association(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) + TraceFunctionCycle* _cycle; + + private: + bool isUniquePrefix(const QString&) const; + //TraceFunctionMap::Iterator _myMapIterator; + + TraceClass* _cls; + TraceObject* _object; + TraceFile* _file; + + TraceFunctionSourceList _sourceFiles; // we are owner + TraceInstrMap* _instrMap; // we are owner + bool _instrMapFilled; + + // see TraceAssociation + TraceAssociationList _associations; + + // for cycle detection + int _cycleLow; + TraceFunction* _cycleStackDown; + + // cached + SubCost _calledCount, _callingCount; + int _calledContexts, _callingContexts; +}; + + +/** + * A cycle of recursive calling functions. + * + * This is itself shown as a function + */ +class TraceFunctionCycle: public TraceFunction +{ + public: + TraceFunctionCycle(TraceFunction*, int n); + + // 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; +}; + + +/** + * 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 QString prettyName() const; + static QString prettyEmptyName(); + + 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(); + + void setDirectory(const QString& dir); + void resetDirectory() { _dir = QString(); } + QString directory(); + + void addFunction(TraceFunction*); + void addSourceFile(TraceFunctionSource*); + + // without path + QString shortName() const; + QString prettyName() const; + QString prettyLongName() const; + static QString prettyEmptyName(); + 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(); + + void setDirectory(const QString& dir); + void resetDirectory() { _dir = QString(); } + QString directory(); + + void addFunction(TraceFunction*); + + QString shortName() const; + QString prettyName() const; + static QString prettyEmptyName(); + const TraceFunctionList& functions() const { return _functions; } + + // part factory + TracePartObject* partObject(TracePart*); + + private: + TraceFunctionList _functions; + QString _dir; +}; + + + +/** + * This class holds profiling data of multiple tracefiles + * generated with cachegrind on one command. + * + */ +class TraceData: public ProfileCostArray +{ + public: + TraceData(Logger* l = 0); + virtual ~TraceData(); + + virtual TraceData* data() { return this; } + virtual const TraceData* data() const { return this; } + + /** + * Loads profile data files. + * If a single file is given, it is assumed to be a prefix. + * + * This adjusts the EventTypeSet according to given cost types. + * Returns the number of parts loaded + */ + int load(QStringList files); + int load(QString file); + int load(QIODevice*, const QString&); + + /** returns true if something changed. These do NOT + * invalidate the dynamic costs on a activation change, + * i.e. all cost items depends 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); + + // to be used by loader + void addPart(TracePart*); + + TracePartList parts() const { return _parts; } + TracePart* partWithName(const QString& name); + + // with path + QString traceName() const { return _traceName; } + + // without path + QString shortTraceName() const; + QString activePartRange(); + + EventTypeSet* eventTypes() { return &_eventTypes; } + + // 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 + * is not needed. + */ + ProfileCostArray* search(ProfileContext::Type, QString, + EventType* ct = 0, ProfileCostArray* 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; } + + ProfileCostArray* callMax() { return &_callMax; } + + void setCommand(const QString& command) { _command = command; } + QString command() const { return _command; } + ProfileCostArray* 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 profile parts from one file + int internalLoad(QIODevice* file, const QString& filename); + + // for notification callbacks + Logger* _logger; + + TracePartList _parts; + + // The set for all costs + EventTypeSet _eventTypes; + + FixPool* _fixPool; + DynPool* _dynPool; + + // always the trace totals (not dependent on active parts) + ProfileCostArray _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 is always the same as self cost + ProfileCostArray _callMax; + + // cycles + TraceFunctionCycleList _functionCycles; + int _functionCycleCount; + bool _inFunctionCycleUpdate; +}; + + + +#endif diff --git a/kcachegrind/libcore/utils.cpp b/kcachegrind/libcore/utils.cpp new file mode 100644 index 00000000..48334271 --- /dev/null +++ b/kcachegrind/libcore/utils.cpp @@ -0,0 +1,490 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Utility classes for KCachegrind + */ + +#include "utils.h" + +#include + +#include +#include + + + +// 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(QIODevice* file, const QString& filename) +{ + _file = file; + + if (!file) { + _len = 0; + _currentLeft = 0; + _openError = true; + return; + } + + _filename = filename; + if (!file->isOpen() && !file->open( QIODevice::ReadOnly ) ) { + qWarning( "%s: %s", (const char*)QFile::encodeName(_filename), + strerror( errno ) ); + _len = 0; + _currentLeft = 0; + _openError = true; + return; + } + + _openError = false; + _used_mmap = false; + + uchar* addr = 0; + +#if QT_VERSION >= 0x040400 + // QFile::map was introduced with Qt 4.4 + if (file->size() >0) { + QFile* mappableDevice = dynamic_cast(file); + if (mappableDevice) { + addr = mappableDevice->map( 0, file->size() ); + } + } +#endif + + if (addr) { + // map succeeded + _base = (char*) addr; + _len = file->size(); + _used_mmap = true; + + if (0) qDebug("Mapped '%s'", qPrintable( _filename )); + } + else { + // try reading the data into memory instead + file->seek(0); + _data = file->readAll(); + _base = _data.data(); + _len = _data.size(); + } + + _current = _base; + _currentLeft = _len; +} + +FixFile::~FixFile() +{ + // if the file was read into _data, it will be deleted automatically + + if (_used_mmap && _file) { + if (0) qDebug("Unmapping '%s'", qPrintable( _filename )); +#if QT_VERSION >= 0x040400 + QFile* mappableDevice = dynamic_cast(_file); + Q_ASSERT(mappableDevice); + if (!mappableDevice->unmap( (uchar*) _base )) + qWarning( "munmap: %s", strerror( errno ) ); +#endif + } +} + +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 %lu, len %u: '%s'", + (unsigned long) (_current - _base), _currentLeft-left, tmp); + } + + int len = _currentLeft-left; + // get rid of any carriage return at end + if ((len>0) && (*(current-1) == '\r')) len--; + str.set(_current, len); + + 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 + + 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. +*/ + +/* + * Utility classes for KCachegrind + */ + +#ifndef UTILS_H +#define UTILS_H + +#include + +class QIODevice; + +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 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::fromLocal8Bit(_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(QIODevice*, const QString&); + ~FixFile(); + + /** + * Read next line into . 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; + QIODevice* _file; + QString _filename; +}; + + +/** + * A list of pointers, only able to append items. + * Optimized for speed, not space. + */ +template +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/libviews/CMakeLists.txt b/kcachegrind/libviews/CMakeLists.txt new file mode 100644 index 00000000..7183f4c9 --- /dev/null +++ b/kcachegrind/libviews/CMakeLists.txt @@ -0,0 +1,35 @@ +include_directories( ../libcore ) + +set(libviews_SRCS + globalguiconfig.cpp + stackitem.cpp + stackselection.cpp + partgraph.cpp + partselection.cpp + costlistitem.cpp + functionlistmodel.cpp + functionselection.cpp + toplevelbase.cpp + listutils.cpp + treemap.cpp + traceitemview.cpp + tabview.cpp + multiview.cpp + instrview.cpp + sourceview.cpp + callmapview.cpp + callgraphview.cpp + callview.cpp + coverageview.cpp + eventtypeview.cpp + partview.cpp + eventtypeitem.cpp + callitem.cpp + coverageitem.cpp + sourceitem.cpp + instritem.cpp + partlistitem.cpp ) + +qt4_automoc(${libviews_SRCS}) +add_library(views STATIC ${libviews_SRCS}) +target_link_libraries(views core) diff --git a/kcachegrind/libviews/README b/kcachegrind/libviews/README new file mode 100644 index 00000000..acf685a0 --- /dev/null +++ b/kcachegrind/libviews/README @@ -0,0 +1,87 @@ +OVERVIEW + +This directory contains widgets which display views +into profile data. A view either shows some aspect of +the profile data directly, are is a container for +other views. + +All views are subclasses of TraceItemView. + +Implementation of all views depend on Qt only. +KDE-specific views, these should be in kcachegrind/. + + +VIEWS + + +Container views +--------------- + +TabView + +A Tabview embeds multiple views with the same cost item +selected/activated. The views can be arranged either +on top of each other using tabs for selection, or nearside +each other in four regions (right/top/left/bottom). + +Multiview + +A Multiview is a horizontal or vertical series of embedded views, +separated by splitters. +Eeach embedded view can have its own cost item selection/activation. +One of the views has the focus, and thus providing the +selection/activation of the multiview itself. +Selection in one view changes the activation in the next view to +the right/bottom (with wrap around). + + +Detailed views +-------------- + +EventTypeView + +A list of event types measured in the profile experiment, +attributed with the costs of the currently activated cost item. + +CallView + +A list of callers/callees of the currently activated cost item. + +CallMapView + +A treemap showing the nesting of callers/callees starting from +the currently activated cost item. + +CallGraphView + +A graph around the currently activated cost item, with edges +being the call relations. Only nodes and edges with cost over a +given threshould are shown. + +CoverageView + +Similar to the CallView list, the coverage list includes not +only direct callers/callees, but also indirect. + +SourceView + +Annotated source. + +InstrView + +Annotated assembly. + +PartView + +List of loaded profile data parts + + +Misc +---- + +TreeMap + +Generic widget for treemaps + +listutils.cpp +helpers for lists in views diff --git a/kcachegrind/libviews/callgraphview.cpp b/kcachegrind/libviews/callgraphview.cpp new file mode 100644 index 00000000..de063bea --- /dev/null +++ b/kcachegrind/libviews/callgraphview.cpp @@ -0,0 +1,3185 @@ +/* This file is part of KCachegrind. + Copyright (C) 2007 Josef Weidendorfer + + 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. +*/ + +/* + * Callgraph View + */ + +#include "callgraphview.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "config.h" +#include "globalguiconfig.h" +#include "listutils.h" + + +#define DEBUG_GRAPH 0 + +// CallGraphView defaults + +#define DEFAULT_FUNCLIMIT .05 +#define DEFAULT_CALLLIMIT 1. +#define DEFAULT_MAXCALLER 2 +#define DEFAULT_MAXCALLEE -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 + + +// LessThen functors as helpers for sorting of graph edges +// for keyboard navigation. Sorting is done according to +// the angle at which a edge spline goes out or in of a function. + +// Sort angles of outgoing edges (edge seen as attached to the caller) +class CallerGraphEdgeLessThan +{ +public: + bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const + { + const CanvasEdge* ce1 = ge1->canvasEdge(); + const CanvasEdge* ce2 = ge2->canvasEdge(); + + // sort invisible edges (ie. without matching CanvasEdge) in front + if (!ce1) return true; + if (!ce2) return false; + + QPolygon p1 = ce1->controlPoints(); + QPolygon p2 = ce2->controlPoints(); + QPoint d1 = p1.point(1) - p1.point(0); + QPoint d2 = p2.point(1) - p2.point(0); + double angle1 = atan2(double(d1.y()), double(d1.x())); + double angle2 = atan2(double(d2.y()), double(d2.x())); + + return (angle1 < angle2); + } +}; + +// Sort angles of ingoing edges (edge seen as attached to the callee) +class CalleeGraphEdgeLessThan +{ +public: + bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const + { + const CanvasEdge* ce1 = ge1->canvasEdge(); + const CanvasEdge* ce2 = ge2->canvasEdge(); + + // sort invisible edges (ie. without matching CanvasEdge) in front + if (!ce1) return true; + if (!ce2) return false; + + QPolygon p1 = ce1->controlPoints(); + QPolygon p2 = ce2->controlPoints(); + QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1); + QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1); + double angle1 = atan2(double(d1.y()), double(d1.x())); + double angle2 = atan2(double(d2.y()), double(d2.x())); + + // for ingoing edges sort according to descending angles + return (angle2 < angle1); + } +}; + + + +// +// GraphNode +// + +GraphNode::GraphNode() +{ + _f=0; + self = incl = 0; + _cn = 0; + + _visible = false; + _lastCallerIndex = _lastCalleeIndex = -1; + + _lastFromCaller = true; +} + +void GraphNode::clearEdges() +{ + callees.clear(); + callers.clear(); +} + +CallerGraphEdgeLessThan callerGraphEdgeLessThan; +CalleeGraphEdgeLessThan calleeGraphEdgeLessThan; + +void GraphNode::sortEdges() +{ + qSort(callers.begin(), callers.end(), callerGraphEdgeLessThan); + qSort(callees.begin(), callees.end(), calleeGraphEdgeLessThan); +} + +void GraphNode::addCallee(GraphEdge* e) +{ + if (e) + callees.append(e); +} + +void GraphNode::addCaller(GraphEdge* e) +{ + if (e) + callers.append(e); +} + +void GraphNode::addUniqueCallee(GraphEdge* e) +{ + if (e && (callees.count(e) == 0)) + callees.append(e); +} + +void GraphNode::addUniqueCaller(GraphEdge* e) +{ + if (e && (callers.count(e) == 0)) + callers.append(e); +} + +void GraphNode::removeEdge(GraphEdge* e) +{ + callers.removeAll(e); + callees.removeAll(e); +} + +double GraphNode::calleeCostSum() +{ + double sum = 0.0; + + foreach(GraphEdge* e, callees) + sum += e->cost; + + return sum; +} + +double GraphNode::calleeCountSum() +{ + double sum = 0.0; + + foreach(GraphEdge* e, callees) + sum += e->count; + + return sum; +} + +double GraphNode::callerCostSum() +{ + double sum = 0.0; + + foreach(GraphEdge* e, callers) + sum += e->cost; + + return sum; +} + +double GraphNode::callerCountSum() +{ + double sum = 0.0; + + foreach(GraphEdge* e, callers) + sum += e->count; + + return sum; +} + + +TraceCall* GraphNode::visibleCaller() +{ + if (0) + qDebug("GraphNode::visibleCaller %s: last %d, count %d", + qPrintable(_f->prettyName()), _lastCallerIndex, callers.count()); + + // can not use at(): index can be -1 (out of bounds), result is 0 then + GraphEdge* e = callers.value(_lastCallerIndex); + if (e && !e->isVisible()) + e = 0; + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + for(int i = 0; iisVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCallerIndex = i; + } + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +TraceCall* GraphNode::visibleCallee() +{ + if (0) + qDebug("GraphNode::visibleCallee %s: last %d, count %d", + qPrintable(_f->prettyName()), _lastCalleeIndex, callees.count()); + + GraphEdge* e = callees.value(_lastCalleeIndex); + if (e && !e->isVisible()) + e = 0; + + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + for(int i = 0; iisVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCalleeIndex = i; + } + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +void GraphNode::setCallee(GraphEdge* e) +{ + _lastCalleeIndex = callees.indexOf(e); + _lastFromCaller = false; +} + +void GraphNode::setCaller(GraphEdge* e) +{ + _lastCallerIndex = callers.indexOf(e); + _lastFromCaller = true; +} + +TraceFunction* GraphNode::nextVisible() +{ + TraceCall* c; + + if (_lastFromCaller) { + c = nextVisibleCaller(); + if (c) + return c->called(true); + c = nextVisibleCallee(); + if (c) + return c->caller(true); + } else { + c = nextVisibleCallee(); + if (c) + return c->caller(true); + c = nextVisibleCaller(); + if (c) + return c->called(true); + } + return 0; +} + +TraceFunction* GraphNode::priorVisible() +{ + TraceCall* c; + + if (_lastFromCaller) { + c = priorVisibleCaller(); + if (c) + return c->called(true); + c = priorVisibleCallee(); + if (c) + return c->caller(true); + } else { + c = priorVisibleCallee(); + if (c) + return c->caller(true); + c = priorVisibleCaller(); + if (c) + return c->called(true); + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e) +{ + int idx = e ? callers.indexOf(e) : _lastCallerIndex; + idx++; + while(idx < callers.size()) { + if (callers[idx]->isVisible()) { + _lastCallerIndex = idx; + return callers[idx]->call(); + } + idx++; + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e) +{ + int idx = e ? callees.indexOf(e) : _lastCalleeIndex; + idx++; + while(idx < callees.size()) { + if (callees[idx]->isVisible()) { + _lastCalleeIndex = idx; + return callees[idx]->call(); + } + idx++; + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e) +{ + int idx = e ? callers.indexOf(e) : _lastCallerIndex; + + idx = (idx<0) ? callers.size()-1 : idx-1; + while(idx >= 0) { + if (callers[idx]->isVisible()) { + _lastCallerIndex = idx; + return callers[idx]->call(); + } + idx--; + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e) +{ + int idx = e ? callees.indexOf(e) : _lastCalleeIndex; + + idx = (idx<0) ? callees.size()-1 : idx-1; + while(idx >= 0) { + if (callees[idx]->isVisible()) { + _lastCalleeIndex = idx; + return callees[idx]->call(); + } + 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 QObject::tr("Call(s) from %1").arg(_from->prettyName()); + + if (_to) + return QObject::tr("Call(s) to %1").arg(_to->prettyName()); + + return QObject::tr("(unknown call)"); +} + +TraceFunction* GraphEdge::visibleCaller() +{ + if (_from) { + _lastFromCaller = true; + if (_fromNode) + _fromNode->setCallee(this); + return _from; + } + return 0; +} + +TraceFunction* GraphEdge::visibleCallee() +{ + if (_to) { + _lastFromCaller = false; + if (_toNode) + _toNode->setCaller(this); + return _to; + } + return 0; +} + +TraceCall* GraphEdge::nextVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->nextVisibleCallee(this); + if (!res && _toNode) + res = _toNode->nextVisibleCaller(this); + } else if (_toNode) { + res = _toNode->nextVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->nextVisibleCallee(this); + } + return res; +} + +TraceCall* GraphEdge::priorVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->priorVisibleCallee(this); + if (!res && _toNode) + res = _toNode->priorVisibleCaller(this); + } else if (_toNode) { + res = _toNode->priorVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->priorVisibleCallee(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; + _maxCalleeDepth = DEFAULT_MAXCALLEE; + _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, ProfileContext::InvalidType, QString()); +} + +GraphExporter::GraphExporter(TraceData* d, TraceFunction* f, + EventType* ct, ProfileContext::Type gt, + QString filename) +{ + _go = this; + _tmpFile = 0; + _item = 0; + reset(d, f, ct, gt, filename); +} + +GraphExporter::~GraphExporter() +{ + if (_item && _tmpFile) { +#if DEBUG_GRAPH + _tmpFile->setAutoRemove(true); +#endif + delete _tmpFile; + } +} + + +void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct, + ProfileContext::Type gt, QString filename) +{ + _graphCreated = false; + _nodeMap.clear(); + _edgeMap.clear(); + + if (_item && _tmpFile) { + _tmpFile->setAutoRemove(true); + delete _tmpFile; + } + + if (i) { + switch (i->type()) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + case ProfileContext::Call: + break; + default: + i = 0; + } + } + + _item = i; + _eventType = ct; + _groupType = gt; + if (!i) + return; + + if (filename.isEmpty()) { + _tmpFile = new QTemporaryFile(); + //_tmpFile->setSuffix(".dot"); + _tmpFile->setAutoRemove(false); + _tmpFile->open(); + _dotName = _tmpFile->fileName(); + _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() == ProfileContext::Function) ||(_item->type() + == ProfileContext::FunctionCycle)) { + TraceFunction* f = (TraceFunction*) _item; + + double incl = f->inclusive()->subCost(_eventType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = _realFuncLimit * _go->callLimit(); + + buildGraph(f, 0, true, 1.0); // down to callees + + // 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(_eventType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = _realFuncLimit * _go->callLimit(); + + // create edge + TraceFunction *caller, *called; + caller = c->caller(false); + called = c->called(false); + QPair p(caller, called); + GraphEdge& e = _edgeMap[p]; + e.setCall(c); + e.setCaller(p.first); + e.setCallee(p.second); + e.cost = c->subCost(_eventType); + e.count = c->callCount(); + + SubCost s = called->inclusive()->subCost(_eventType); + buildGraph(called, 0, true, e.cost / s); // down to callees + s = caller->inclusive()->subCost(_eventType); + buildGraph(caller, 0, false, e.cost / s); // up to callers + } +} + + +void GraphExporter::writeDot(QIODevice* device) +{ + if (!_item) + return; + + QFile* file = 0; + QTextStream* stream = 0; + + if (device) + stream = new QTextStream(device); + else { + if (_tmpFile) + stream = new QTextStream(_tmpFile); + else { + file = new QFile(_dotName); + if ( !file->open(QIODevice::WriteOnly ) ) { + qDebug() << "Can not write dot file '"<< _dotName << "'"; + delete file; + 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 ProfileContext::Function: + case ProfileContext::FunctionCycle: + f = (TraceFunction*) _item; + break; + case ProfileContext::Call: + f = ((TraceCall*)_item)->caller(true); + break; + default: + break; + } + if (f) + *stream << QString(" center=F%1;\n").arg((qptrdiff)f, 0, 16); + *stream << QString(" overlap=false;\n splines=true;\n"); + } + + // for clustering + QMap > 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 ProfileContext::Object: + g = f->object(); + break; + case ProfileContext::Class: + g = f->cls(); + break; + case ProfileContext::File: + g = f->file(); + break; + case ProfileContext::FunctionCycle: + g = f->cycle(); + break; + default: + g = 0; + break; + } + nLists[g].append(&n); + } + + QMap >::Iterator lit; + int cluster = 0; + for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) { + QList& l = lit.value(); + TraceCostItem* i = lit.key(); + + if (_go->clusterGroups() && i) { + QString iabr = GlobalConfig::shortenSymbol(i->prettyName()); + *stream << QString("subgraph \"cluster%1\" { label=\"%2\";\n") + .arg(cluster).arg(iabr); + } + + foreach(GraphNode* np, l) { + TraceFunction* f = np->function(); + + QString abr = GlobalConfig::shortenSymbol(f->prettyName()); + *stream << QString(" F%1 [").arg((qptrdiff)f, 0, 16); + if (_useBox) { + // we want a minimal size for cost display + if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_'); + + // 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()) { + // do not show inner cycle calls + if (e.call()->inCycle()>0) + continue; + } + + GraphNode& from = _nodeMap[e.from()]; + GraphNode& to = _nodeMap[e.to()]; + + e.setCallerNode(&from); + e.setCalleeNode(&to); + + if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit)) + continue; + + // remove dumped edges from n.callers/n.callees + from.removeEdge(&e); + to.removeEdge(&e); + + *stream << QString(" F%1 -> F%2 [weight=%3") + .arg((qptrdiff)e.from(), 0, 16) + .arg((qptrdiff)e.to(), 0, 16) + .arg((long)log(log(e.cost))); + + if (_go->detailLevel() ==1) { + *stream << QString(",label=\"%1 (%2x)\"") + .arg(SubCost(e.cost).pretty()) + .arg(SubCost(e.count).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; + + // add edge for all skipped callers if cost sum is high enough + costSum = n.callerCostSum(); + countSum = n.callerCountSum(); + if (costSum > _realCallLimit) { + + QPair p(0, n.function()); + e = &(_edgeMap[p]); + e->setCallee(p.second); + e->cost = costSum; + e->count = countSum; + + *stream << QString(" R%1 [shape=point,label=\"\"];\n") + .arg((qptrdiff)n.function(), 0, 16); + *stream << QString(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((qptrdiff)n.function(), 0, 16) + .arg((qptrdiff)n.function(), 0, 16) + .arg(SubCost(costSum).pretty()) + .arg(SubCost(countSum).pretty()) + .arg((int)log(costSum)); + } + + // add edge for all skipped callees if cost sum is high enough + costSum = n.calleeCostSum(); + countSum = n.calleeCountSum(); + if (costSum > _realCallLimit) { + + QPair 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((qptrdiff)n.function(), 0, 16); + *stream << QString(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((qptrdiff)n.function(), 0, 16) + .arg((qptrdiff)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.clearEdges(); + } + + *stream << "}\n"; + + if (!device) { + if (_tmpFile) { + stream->flush(); + _tmpFile->seek(0); + } 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.sortEdges(); + } +} + +TraceFunction* GraphExporter::toFunc(QString s) +{ + if (s[0] != 'F') + return 0; + bool ok; + TraceFunction* f = (TraceFunction*) s.mid(1).toULongLong(&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 do not 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 depth, bool toCallees, + double factor) +{ +#if DEBUG_GRAPH + qDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor + << ") [to " << (toCallees ? "Callees":"Callers") << "]"; +#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(_eventType) * factor; + n.incl += incl; + n.self += f->subCost(_eventType) * factor; + if (0) + qDebug(" Added Incl. %f, now %f", incl, n.incl); + + // A negative depth limit means "unlimited" + int maxDepth = toCallees ? _go->maxCalleeDepth() + : _go->maxCallerDepth(); + // Never go beyound a depth of 100 + if ((maxDepth < 0) || (maxDepth>100)) maxDepth = 100; + if (depth >= 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 do not follow inner-cycle + // calls + if (oldIncl > 0.0) { + if (0) + qDebug(" Cutoff, 2nd visit to Cycle Member"); + // and takeback cost addition, as it is added twice + n.incl = oldIncl; + n.self -= f->subCost(_eventType) * factor; + return; + } + } else if (incl <= _realFuncLimit) { + if (0) + qDebug(" Cutoff, below limit"); + return; + } + + TraceFunction* f2; + + // on entering a cycle, only go the FunctionCycle + TraceCallList l = toCallees ? f->callings(false) : f->callers(false); + + foreach(TraceCall* call, l) { + + f2 = toCallees ? call->called(false) : call->caller(false); + + double count = call->callCount() * factor; + double cost = call->subCost(_eventType) * 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 p(toCallees ? f : f2, + toCallees ? f2 : f); + GraphEdge& e = _edgeMap[p]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(p.first); + e.setCallee(p.second); + } else + oldCost = e.cost; + + e.cost += cost; + e.count += count; + if (0) + qDebug(" Edge to %s, added cost %f, now %f", + qPrintable(f2->prettyName()), cost, e.cost); + + // if this call goes into a FunctionCycle, we also show the real call + if (f2->cycle() == f2) { + TraceFunction* realF; + realF = toCallees ? call->called(true) : call->caller(true); + QPair + realP(toCallees ? f : realF, toCallees ? realF : f); + GraphEdge& e = _edgeMap[realP]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(realP.first); + e.setCallee(realP.second); + } + e.cost += cost; + e.count += count; + } + + // - do not do a DFS on calls in recursion/cycle + if (call->inCycle()>0) + continue; + if (call->isRecursion()) + continue; + + if (toCallees) + n.addUniqueCallee(&e); + else + n.addUniqueCaller(&e); + + // 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(_eventType); + else + s = f2->inclusive()->subCost(_eventType); + SubCost v = call->subCost(_eventType); + // FIXME: Can s be 0? + buildGraph(f2, depth+1, toCallees, factor * v / s); + } +} + +// +// PannerView +// +PanningView::PanningView(QWidget * parent) + : QGraphicsView(parent) +{ + _movingZoomRect = false; + + // FIXME: Why does this not work? + viewport()->setFocusPolicy(Qt::NoFocus); +} + +void PanningView::setZoomRect(const QRectF& r) +{ + _zoomRect = r; + viewport()->update(); +} + +void PanningView::drawForeground(QPainter * p, const QRectF&) +{ + if (!_zoomRect.isValid()) + return; + + QColor red(Qt::red); + QPen pen(red.dark()); + pen.setWidthF(2.0 / matrix().m11()); + p->setPen(pen); + + QColor c(red.dark()); + c.setAlphaF(0.05); + p->setBrush(QBrush(c)); + + p->drawRect(QRectF(_zoomRect.x(), _zoomRect.y(), + _zoomRect.width()-1, _zoomRect.height()-1)); +} + +void PanningView::mousePressEvent(QMouseEvent* e) +{ + QPointF sPos = mapToScene(e->pos()); + + if (_zoomRect.isValid()) { + if (!_zoomRect.contains(sPos)) + emit zoomRectMoved(sPos.x() - _zoomRect.center().x(), + sPos.y() - _zoomRect.center().y()); + + _movingZoomRect = true; + _lastPos = sPos; + } +} + +void PanningView::mouseMoveEvent(QMouseEvent* e) +{ + QPointF sPos = mapToScene(e->pos()); + if (_movingZoomRect) { + emit zoomRectMoved(sPos.x() - _lastPos.x(), + sPos.y() - _lastPos.y()); + _lastPos = sPos; + } +} + +void PanningView::mouseReleaseEvent(QMouseEvent*) +{ + _movingZoomRect = false; + emit zoomRectMoveFinished(); +} + + + + + +// +// CanvasNode +// + +CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, int x, int y, int w, + int h) : + QGraphicsRectItem(QRect(x, y, w, h)), _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()); + + ProfileCostArray* totalCost; + if (GlobalConfig::showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } else + totalCost = (ProfileCostArray*) _view->activeItem(); + } else + totalCost = ((TraceItemView*)_view)->data(); + double total = totalCost->subCost(_view->eventType()); + double inclP = 100.0 * n->incl/ total; + if (GlobalConfig::showPercentage()) + setText(1, QString("%1 %") + .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(1, SubCost(n->incl).pretty()); + setPixmap(1, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); + + setToolTip(QString("%1 (%2)").arg(text(0)).arg(text(1))); +} + +void CanvasNode::setSelected(bool s) +{ + StoredDrawParams::setSelected(s); + update(); +} + +void CanvasNode::updateGroup() +{ + if (!_view || !_node) + return; + + QColor c = GlobalGUIConfig::functionColor(_view->groupType(), + _node->function()); + setBackColor(c); + update(); +} + +void CanvasNode::paint(QPainter* p, + const QStyleOptionGraphicsItem* option, + QWidget*) +{ + QRect r = rect().toRect(), 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 0 + if (StoredDrawParams::selected() && _view->hasFocus()) { + _view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r, + _view->colorGroup()); + } +#endif + + // draw afterwards to always get a frame even when zoomed + p->setPen(StoredDrawParams::selected() ? Qt::red : Qt::black); + p->drawRect(QRect(origRect.x(), origRect.y(), origRect.width()-1, + origRect.height()-1)); + +#if QT_VERSION >= 0x040600 + if (option->levelOfDetailFromTransform(p->transform()) < .5) + return; +#else + if (option->levelOfDetail < .5) + return; +#endif + + 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) : + QGraphicsRectItem(QRect(x, y, w, h)), _ce(ce), _view(v), _percentage(0.0) +{ + GraphEdge* e = ce->edge(); + if (!e) + return; + + setPosition(1, DrawParams::BottomCenter); + ProfileCostArray* totalCost; + if (GlobalConfig::showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } else + totalCost = (ProfileCostArray*) _view->activeItem(); + } else + totalCost = ((TraceItemView*)_view)->data(); + double total = totalCost->subCost(_view->eventType()); + double inclP = 100.0 * e->cost/ total; + if (GlobalConfig::showPercentage()) + setText(1, QString("%1 %") + .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(1, SubCost(e->cost).pretty()); + + setPosition(0, DrawParams::TopCenter); + SubCost count((e->count < 1.0) ? 1.0 : e->count); + setText(0, QString("%1 x").arg(count.pretty())); + setPixmap(0, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); + + _percentage = inclP; + if (_percentage > 100.0) _percentage = 100.0; + + if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) { + QString icon = "edit-undo"; +#if 0 + KIconLoader* loader = KIconLoader::global(); + QPixmap p= loader->loadIcon(icon, KIconLoader::Small, 0, + KIconLoader::DefaultState, QStringList(), 0, + true); + setPixmap(0, p); +#endif + } + + setToolTip(QString("%1 (%2)").arg(text(0)).arg(text(1))); +} + +void CanvasEdgeLabel::paint(QPainter* p, + const QStyleOptionGraphicsItem* option, QWidget*) +{ + // draw nothing in PanningView +#if QT_VERSION >= 0x040600 + if (option->levelOfDetailFromTransform(p->transform()) < .5) + return; +#else + if (option->levelOfDetail < .5) + return; +#endif + + QRect r = rect().toRect(); + + RectDrawing d(r); + d.drawField(p, 0, this); + d.drawField(p, 1, this); +} + + + +// +// CanvasEdgeArrow + +CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce) + : _ce(ce) +{} + +void CanvasEdgeArrow::paint(QPainter* p, + const QStyleOptionGraphicsItem *, QWidget *) +{ + p->setRenderHint(QPainter::Antialiasing); + p->setBrush(_ce->isSelected() ? Qt::red : Qt::black); + p->drawPolygon(polygon(), Qt::OddEvenFill); +} + + +// +// CanvasEdge +// + +CanvasEdge::CanvasEdge(GraphEdge* e) : + _edge(e) +{ + _label = 0; + _arrow = 0; + _thickness = 0; + + setFlag(QGraphicsItem::ItemIsSelectable); +} + +void CanvasEdge::setLabel(CanvasEdgeLabel* l) +{ + _label = l; + + if (l) { + QString tip = QString("%1 (%2)").arg(l->text(0)).arg(l->text(1)); + + setToolTip(tip); + if (_arrow) _arrow->setToolTip(tip); + + _thickness = log(l->percentage()); + if (_thickness < .9) _thickness = .9; + } +} + +void CanvasEdge::setArrow(CanvasEdgeArrow* a) +{ + _arrow = a; + + if (a && _label) a->setToolTip(QString("%1 (%2)") + .arg(_label->text(0)).arg(_label->text(1))); +} + +void CanvasEdge::setSelected(bool s) +{ + QGraphicsItem::setSelected(s); + update(); +} + +void CanvasEdge::setControlPoints(const QPolygon& pa) +{ + _points = pa; + + QPainterPath path; + path.moveTo(pa[0]); + for (int i = 1; i < pa.size(); i += 3) + path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]); + + setPath(path); +} + + +void CanvasEdge::paint(QPainter* p, + const QStyleOptionGraphicsItem* option, QWidget*) +{ + p->setRenderHint(QPainter::Antialiasing); + + qreal levelOfDetail; +#if QT_VERSION >= 0x040600 + levelOfDetail = option->levelOfDetailFromTransform(p->transform()); +#else + levelOfDetail = option->levelOfDetail; +#endif + + QPen mypen = pen(); + mypen.setWidthF(1.0/levelOfDetail * _thickness); + p->setPen(mypen); + p->drawPath(path()); + + if (isSelected()) { + mypen.setColor(Qt::red); + mypen.setWidthF(1.0/levelOfDetail * _thickness/2.0); + p->setPen(mypen); + p->drawPath(path()); + } +} + + +// +// CanvasFrame +// + +QPixmap* CanvasFrame::_p = 0; + +CanvasFrame::CanvasFrame(CanvasNode* n) +{ + if (!_p) { + + int d = 5; + float v1 = 130.0f, v2 = 10.0f, v = v1, f = 1.03f; + + // 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.translate(-r.x(), -r.y()); + + while (vrect().center().x() - _p->width()/2, + n->rect().center().y() - _p->height()/2, _p->width(), _p->height()) ); +} + + +void CanvasFrame::paint(QPainter* p, + const QStyleOptionGraphicsItem* option, QWidget*) +{ + qreal levelOfDetail; +#if QT_VERSION >= 0x040600 + levelOfDetail = option->levelOfDetailFromTransform(p->transform()); +#else + levelOfDetail = option->levelOfDetail; +#endif + if (levelOfDetail < .5) { + QRadialGradient g(rect().center(), rect().width()/3); + g.setColorAt(0.0, Qt::gray); + g.setColorAt(1.0, Qt::white); + + p->setBrush(QBrush(g)); + p->setPen(Qt::NoPen); + p->drawRect(rect()); + return; + } + + p->drawPixmap(int( rect().x()),int( rect().y()), *_p ); +} + + + +// +// CallGraphView +// +CallGraphView::CallGraphView(TraceItemView* parentView, QWidget* parent, + const char* name) : + QGraphicsView(parent), TraceItemView(parentView) +{ + setObjectName(name); + _zoomPosition = DEFAULT_ZOOMPOS; + _lastAutoPosition = TopLeft; + + _scene = 0; + _xMargin = _yMargin = 0; + _panningView = new PanningView(this); + _panningZoom = 1; + _selectedNode = 0; + _selectedEdge = 0; + _isMoving = false; + + _exporter.setGraphOptions(this); + + _panningView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _panningView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _panningView->raise(); + _panningView->hide(); + + setFocusPolicy(Qt::StrongFocus); + setAttribute(Qt::WA_NoSystemBackground, true); + + connect(_panningView, SIGNAL(zoomRectMoved(qreal,qreal)), this, SLOT(zoomRectMoved(qreal,qreal))); + connect(_panningView, SIGNAL(zoomRectMoveFinished()), this, SLOT(zoomRectMoveFinished())); + + this->setWhatsThis(whatsThis() ); + + // tooltips... + //_tip = new CallGraphTip(this); + + _renderProcess = 0; + _prevSelectedNode = 0; + connect(&_renderTimer, SIGNAL(timeout()), + this, SLOT(showRenderWarning())); +} + +CallGraphView::~CallGraphView() +{ + clear(); + delete _panningView; +} + +QString CallGraphView::whatsThis() const +{ + return tr("Call Graph around active Function" + "

Depending on configuration, this view shows " + "the call graph environment of the active function. " + "Note: the shown cost is only the cost which is " + "spent while the active function was actually running; " + "i.e. the cost shown for main() - if it is visible - should " + "be the same as the cost of the active function, as that is " + "the part of inclusive cost of main() spent while the active " + "function was running.

" + "

For cycles, blue call arrows indicate that this is an " + "artificial call added for correct drawing which " + "actually never happened.

" + "

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.

"); +} + +void CallGraphView::updateSizes(QSize s) +{ + if (!_scene) + return; + + if (s == QSize(0, 0)) + s = size(); + + // the part of the scene that should be visible + int cWidth = (int)_scene->width() - 2*_xMargin + 100; + int cHeight = (int)_scene->height() - 2*_yMargin + 100; + + // hide birds eye view if no overview needed + if (!_data || !_activeItem || + ((cWidth < s.width()) && (cHeight < s.height())) ) { + _panningView->hide(); + return; + } + + // 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 != _panningZoom) { + _panningZoom = zoom; + if (0) + qDebug("Canvas Size: %fx%f, Content: %dx%d, Zoom: %f", + _scene->width(), _scene->height(), cWidth, cHeight, zoom); + + QMatrix m; + _panningView->setMatrix(m.scale(zoom, zoom)); + + // make it a little bigger to compensate for widget frame + _panningView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4); + + // update ZoomRect in panningView + scrollContentsBy(0, 0); + } + + _panningView->centerOn(_scene->width()/2, _scene->height()/2); + + int cvW = _panningView->width(); + int cvH = _panningView->height(); + int x = width()- cvW - verticalScrollBar()->width() -2; + int y = height()-cvH - horizontalScrollBar()->height() -2; + QPoint oldZoomPos = _panningView->pos(); + QPoint newZoomPos = QPoint(0, 0); + ZoomPosition zp = _zoomPosition; + if (zp == Auto) { + int tlCols = items(QRect(0,0, cvW,cvH)).count(); + int trCols = items(QRect(x,0, cvW,cvH)).count(); + int blCols = items(QRect(0,y, cvW,cvH)).count(); + int brCols = items(QRect(x,y, cvW,cvH)).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 TopLeft: + newZoomPos = QPoint(0, 0); + break; + 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) + _panningView->move(newZoomPos); + + if (zp == Hide) + _panningView->hide(); + else + _panningView->show(); +} + +void CallGraphView::focusInEvent(QFocusEvent*) +{ + if (!_scene) return; + + if (_selectedNode && _selectedNode->canvasNode()) { + _selectedNode->canvasNode()->setSelected(true); // requests item update + _scene->update(); + } +} + +void CallGraphView::focusOutEvent(QFocusEvent* e) +{ + // trigger updates as in focusInEvent + focusInEvent(e); +} + +void CallGraphView::keyPressEvent(QKeyEvent* e) +{ + if (!_scene) { + e->ignore(); + return; + } + + if ((e->key() == Qt::Key_Return) ||(e->key() == Qt::Key_Space)) { + if (_selectedNode) + activated(_selectedNode->function()); + else if (_selectedEdge && _selectedEdge->call()) + activated(_selectedEdge->call()); + return; + } + + // move selected node/edge + if (!(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) + &&(_selectedNode || _selectedEdge)&&((e->key() == Qt::Key_Up) + ||(e->key() == Qt::Key_Down)||(e->key() == Qt::Key_Left)||(e->key() + == Qt::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 Qt::Key_Up: + key = Qt::Key_Left; + break; + case Qt::Key_Down: + key = Qt::Key_Right; + break; + case Qt::Key_Left: + key = Qt::Key_Up; + break; + case Qt::Key_Right: + key = Qt::Key_Down; + break; + default: + break; + } + } + + if (_selectedNode) { + if (key == Qt::Key_Up) + c = _selectedNode->visibleCaller(); + if (key == Qt::Key_Down) + c = _selectedNode->visibleCallee(); + if (key == Qt::Key_Right) + f = _selectedNode->nextVisible(); + if (key == Qt::Key_Left) + f = _selectedNode->priorVisible(); + } else if (_selectedEdge) { + if (key == Qt::Key_Up) + f = _selectedEdge->visibleCaller(); + if (key == Qt::Key_Down) + f = _selectedEdge->visibleCallee(); + if (key == Qt::Key_Right) + c = _selectedEdge->nextVisible(); + if (key == Qt::Key_Left) + c = _selectedEdge->priorVisible(); + } + + if (c) + selected(c); + if (f) + selected(f); + return; + } + + // move canvas... + QPointF center = mapToScene(viewport()->rect().center()); + if (e->key() == Qt::Key_Home) + centerOn(center + QPointF(-_scene->width(), 0)); + else if (e->key() == Qt::Key_End) + centerOn(center + QPointF(_scene->width(), 0)); + else if (e->key() == Qt::Key_PageUp) { + QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); + centerOn(center + QPointF(-dy.x()/2, -dy.y()/2)); + } else if (e->key() == Qt::Key_PageDown) { + QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); + centerOn(center + QPointF(dy.x()/2, dy.y()/2)); + } else if (e->key() == Qt::Key_Left) { + QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); + centerOn(center + QPointF(-dx.x()/10, -dx.y()/10)); + } else if (e->key() == Qt::Key_Right) { + QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); + centerOn(center + QPointF(dx.x()/10, dx.y()/10)); + } else if (e->key() == Qt::Key_Down) { + QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); + centerOn(center + QPointF(dy.x()/10, dy.y()/10)); + } else if (e->key() == Qt::Key_Up) { + QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); + centerOn(center + QPointF(-dy.x()/10, -dy.y()/10)); + } else + e->ignore(); +} + +void CallGraphView::resizeEvent(QResizeEvent* e) +{ + QGraphicsView::resizeEvent(e); + if (_scene) + updateSizes(e->size()); +} + +CostItem* CallGraphView::canShow(CostItem* i) +{ + if (i) { + switch (i->type()) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + case ProfileContext::Call: + return i; + default: + break; + } + } + return 0; +} + +void CallGraphView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == eventType2Changed) + return; + + if (changeType == selectedItemChanged) { + if (!_scene) + return; + + if (!_selectedItem) + return; + + GraphNode* n = 0; + GraphEdge* e = 0; + if ((_selectedItem->type() == ProfileContext::Function) + ||(_selectedItem->type() == ProfileContext::FunctionCycle)) { + n = _exporter.node((TraceFunction*)_selectedItem); + if (n == _selectedNode) + return; + } else if (_selectedItem->type() == ProfileContext::Call) { + TraceCall* c = (TraceCall*)_selectedItem; + e = _exporter.edge(c->caller(false), c->called(false)); + if (e == _selectedEdge) + return; + } + + // unselect 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 // do not 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) + ensureVisible(sNode); + + _scene->update(); + return; + } + + if (changeType == groupTypeChanged) { + if (!_scene) + return; + + if (_clusterGroups) { + refresh(); + return; + } + + QList l = _scene->items(); + for (int i = 0; i < l.size(); ++i) + if (l[i]->type() == CANVAS_NODE) + ((CanvasNode*)l[i])->updateGroup(); + + _scene->update(); + return; + } + + if (changeType & dataChanged) { + // invalidate old selection and graph part + _exporter.reset(_data, _activeItem, _eventType, _groupType); + _selectedNode = 0; + _selectedEdge = 0; + } + + refresh(); +} + +void CallGraphView::clear() +{ + if (!_scene) + return; + + _panningView->setScene(0); + setScene(0); + delete _scene; + _scene = 0; +} + +void CallGraphView::showText(QString s) +{ + clear(); + _renderTimer.stop(); + + _scene = new QGraphicsScene; + + _scene->addSimpleText(s); + centerOn(0, 0); + setScene(_scene); + _scene->update(); + _panningView->hide(); +} + +void CallGraphView::showRenderWarning() +{ + QString s; + + if (_renderProcess) + s = tr("Warning: a long lasting graph layouting is in progress.\n" + "Reduce node/edge limits for speedup.\n"); + else + s = tr("Layouting stopped.\n"); + + s.append(tr("The call graph has %1 nodes and %2 edges.\n") + .arg(_exporter.nodeCount()).arg(_exporter.edgeCount())); + + showText(s); +} + +void CallGraphView::showRenderError(QString s) +{ + QString err; + err = tr("No graph available because the layouting process failed.\n"); + if (_renderProcess) + err += tr("Trying to run the following command did not work:\n" + "'%1'\n").arg(_renderProcessCmdLine); + err += tr("Please check that 'dot' is installed (package GraphViz)."); + + if (!s.isEmpty()) + err += QString("\n\n%1").arg(s); + + showText(err); +} + +void CallGraphView::stopRendering() +{ + if (!_renderProcess) + return; + + qDebug("CallGraphView::stopRendering: Killing QProcess %p", + _renderProcess); + + _renderProcess->kill(); + + // forget about this process, not interesting any longer + _renderProcess->deleteLater(); + _renderProcess = 0; + _unparsedOutput = QString(); + + _renderTimer.setSingleShot(true); + _renderTimer.start(200); +} + +void CallGraphView::refresh() +{ + // trigger start of new layouting via 'dot' + if (_renderProcess) + stopRendering(); + + // we want to keep a selected node item at the same global position + _prevSelectedNode = _selectedNode; + _prevSelectedPos = QPoint(-1, -1); + if (_selectedNode) { + QPointF center = _selectedNode->canvasNode()->rect().center(); + _prevSelectedPos = mapFromScene(center); + } + + if (!_data || !_activeItem) { + showText(tr("No item activated for which to " + "draw the call graph.")); + return; + } + + ProfileContext::Type t = _activeItem->type(); + switch (t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + case ProfileContext::Call: + break; + default: + showText(tr("No call graph can be drawn for " + "the active item.")); + return; + } + + if (1) + qDebug() << "CallGraphView::refresh"; + + _selectedNode = 0; + _selectedEdge = 0; + + /* + * Call 'dot' asynchronoulsy in the background with the aim to + * - have responsive GUI while layout task runs (potentially long!) + * - notify user about a long run, using a timer + * - kill long running 'dot' processes when another layout is + * requested, as old data is not needed any more + * + * Even after killing a process, the QProcess needs some time + * to make sure the process is destroyed; also, stdout data + * still can be delivered after killing. Thus, there can/should be + * multiple QProcess's at one time. + * The QProcess we currently wait for data from is <_renderProcess> + * Signals from other QProcesses are ignored with the exception of + * the finished() signal, which triggers QProcess destruction. + */ + QString renderProgram; + QStringList renderArgs; + if (_layout == GraphOptions::Circular) + renderProgram = "twopi"; + else + renderProgram = "dot"; + renderArgs << "-Tplain"; + + _unparsedOutput = QString(); + + // display warning if layouting takes > 1s + _renderTimer.setSingleShot(true); + _renderTimer.start(1000); + + _renderProcess = new QProcess(this); + connect(_renderProcess, SIGNAL(readyReadStandardOutput()), + this, SLOT(readDotOutput())); + connect(_renderProcess, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(dotError())); + connect(_renderProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(dotExited())); + + _renderProcessCmdLine = renderProgram + " " + renderArgs.join(" "); + qDebug("CallGraphView::refresh: Starting process %p, '%s'", + _renderProcess, qPrintable(_renderProcessCmdLine)); + + // _renderProcess can be set to 0 on error after start(). + // thus, we use a local copy afterwards + QProcess* p = _renderProcess; + p->start(renderProgram, renderArgs); + _exporter.reset(_data, _activeItem, _eventType, _groupType); + _exporter.writeDot(p); + p->closeWriteChannel(); +} + +void CallGraphView::readDotOutput() +{ + QProcess* p = qobject_cast(sender()); + qDebug("CallGraphView::readDotOutput: QProcess %p", p); + + // signal from old/uninteresting process? + if ((_renderProcess == 0) || (p != _renderProcess)) { + p->deleteLater(); + return; + } + + _unparsedOutput.append(_renderProcess->readAllStandardOutput()); +} + +void CallGraphView::dotError() +{ + QProcess* p = qobject_cast(sender()); + qDebug("CallGraphView::dotError: Got %d from QProcess %p", + p->error(), p); + + // signal from old/uninteresting process? + if ((_renderProcess == 0) || (p != _renderProcess)) { + p->deleteLater(); + return; + } + + showRenderError(_renderProcess->readAllStandardError()); + + // not interesting any longer + _renderProcess->deleteLater(); + _renderProcess = 0; +} + + +void CallGraphView::dotExited() +{ + QProcess* p = qobject_cast(sender()); + qDebug("CallGraphView::dotExited: QProcess %p", p); + + // signal from old/uninteresting process? + if ((_renderProcess == 0) || (p != _renderProcess)) { + p->deleteLater(); + return; + } + + _unparsedOutput.append(_renderProcess->readAllStandardOutput()); + _renderProcess->deleteLater(); + _renderProcess = 0; + + QString line, cmd; + CanvasNode *rItem; + QGraphicsEllipseItem* eItem; + CanvasEdge* sItem; + CanvasEdgeLabel* lItem; + QTextStream* dotStream; + double scale = 1.0, scaleX = 1.0, scaleY = 1.0; + double dotWidth = 0, dotHeight = 0; + GraphNode* activeNode = 0; + GraphEdge* activeEdge = 0; + + _renderTimer.stop(); + viewport()->setUpdatesEnabled(false); + clear(); + dotStream = new QTextStream(&_unparsedOutput, QIODevice::ReadOnly); + + // First pass to adjust coordinate scaling by node height given from dot + // Normal detail level (=1) should be 3 lines using general KDE font + double nodeHeight = 0.0; + while(1) { + line = dotStream->readLine(); + if (line.isNull()) break; + if (line.isEmpty()) continue; + QTextStream lineStream(&line, QIODevice::ReadOnly); + lineStream >> cmd; + if (cmd != "node") continue; + QString s, h; + lineStream >> s /*name*/ >> s /*x*/ >> s /*y*/ >> s /*width*/ >> h /*height*/; + nodeHeight = h.toDouble(); + break; + } + if (nodeHeight > 0.0) { + scaleY = (8 + (1 + 2 * _detailLevel) * fontMetrics().height()) / nodeHeight; + scaleX = 80; + } + dotStream->seek(0); + int lineno = 0; + while (1) { + line = dotStream->readLine(); + if (line.isNull()) + break; + + lineno++; + if (line.isEmpty()) + continue; + + QTextStream lineStream(&line, QIODevice::ReadOnly); + lineStream >> cmd; + + if (0) + qDebug("%s:%d - line '%s', cmd '%s'", + qPrintable(_exporter.filename()), + lineno, qPrintable(line), qPrintable(cmd)); + + if (cmd == "stop") + break; + + if (cmd == "graph") { + QString dotWidthString, dotHeightString; + // scale will not be used + lineStream >> scale >> dotWidthString >> dotHeightString; + dotWidth = dotWidthString.toDouble(); + dotHeight = dotHeightString.toDouble(); + + if (!_scene) { + 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; + + _scene = new QGraphicsScene( 0.0, 0.0, + qreal(w+2*_xMargin), qreal(h+2*_yMargin)); + // Change background color for call graph from default system color to + // white. It has to blend into the gradient for the selected function. + _scene->setBackgroundBrush(Qt::white); + +#if DEBUG_GRAPH + qDebug() << qPrintable(_exporter.filename()) << ":" << lineno + << " - graph (" << dotWidth << " x " << dotHeight + << ") => (" << w << " x " << h << ")"; +#endif + } else + qDebug() << "Ignoring 2nd 'graph' from dot (" + << _exporter.filename() << ":"<< lineno << ")"; + continue; + } + + if ((cmd != "node") && (cmd != "edge")) { + qDebug() << "Ignoring unknown command '"<< cmd + << "' from dot ("<< _exporter.filename() << ":"<< lineno + << ")"; + continue; + } + + if (_scene == 0) { + qDebug() << "Ignoring '"<< cmd + << "' without 'graph' from dot ("<< _exporter.filename() + << ":"<< lineno << ")"; + 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 + qDebug() << _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 QGraphicsEllipseItem( QRectF(xx-w/2, yy-h/2, w, h) ); + _scene->addItem(eItem); + eItem->setBrush(Qt::gray); + eItem->setZValue(1.0); + eItem->show(); + continue; + } + + if (!n) { + qDebug("Warning: Unknown function '%s' ?!", + qPrintable(nodeName)); + continue; + } + n->setVisible(true); + + rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h); + // limit symbol space to a maximal number of lines depending on detail level + if (_detailLevel>0) rItem->setMaxLines(0, 2*_detailLevel); + _scene->addItem(rItem); + n->setCanvasNode(rItem); + + if (n) { + if (n->function() == activeItem()) + activeNode = n; + if (n->function() == selectedItem()) + _selectedNode = n; + rItem->setSelected(n == _selectedNode); + } + + rItem->setZValue(1.0); + rItem->show(); + + continue; + } + + // edge + + QString node1Name, node2Name, label, edgeX, edgeY; + double x, y; + QPolygon poly; + int points, i; + lineStream >> node1Name >> node2Name >> points; + + GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name), + _exporter.toFunc(node2Name)); + if (!e) { + qDebug() << "Unknown edge '"<< node1Name << "'-'"<< node2Name + << "' from dot ("<< _exporter.filename() << ":"<< lineno + << ")"; + continue; + } + e->setVisible(true); + if (e->fromNode()) + e->fromNode()->addCallee(e); + if (e->toNode()) + e->toNode()->addCaller(e); + + if (0) + qDebug(" Edge with %d points:", points); + + poly.resize(points); + for (i=0; i> 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); + + poly.setPoint(i, xx, yy); + } + if (i < points) { + qDebug("CallGraphView: Can not read %d spline points (%s:%d)", + points, qPrintable(_exporter.filename()), 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); + _scene->addItem(sItem); + e->setCanvasEdge(sItem); + sItem->setControlPoints(poly); + // width of pen will be adjusted in CanvasEdge::paint() + sItem->setPen(QPen(arrowColor)); + sItem->setZValue(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) { + QPointF toCenter = fromNode->rect().center(); + qreal dx0 = poly.point(0).x() - toCenter.x(); + qreal dy0 = poly.point(0).y() - toCenter.y(); + qreal dx1 = poly.point(points-1).x() - toCenter.x(); + qreal dy1 = poly.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() && (indexHead1)) { + indexHead--; + arrowDir = poly.point(indexHead) - poly.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())); + + QPolygonF a; + a << QPointF(poly.point(indexHead) + arrowDir); + a << QPointF(poly.point(indexHead) + QPoint(arrowDir.y()/2, + -arrowDir.x()/2)); + a << QPointF(poly.point(indexHead) + QPoint(-arrowDir.y()/2, + arrowDir.x()/2)); + + if (0) + qDebug(" Arrow: ( %f/%f, %f/%f, %f/%f)", a[0].x(), a[0].y(), + a[1].x(), a[1].y(), a[2].x(), a[1].y()); + + CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem); + _scene->addItem(aItem); + aItem->setPolygon(a); + aItem->setBrush(arrowColor); + aItem->setZValue(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)", + qPrintable(label), 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); + _scene->addItem(lItem); + // edge labels above nodes + lItem->setZValue(1.5); + sItem->setLabel(lItem); + if (h>0) + lItem->show(); + + } + delete dotStream; + + // for keyboard navigation + _exporter.sortEdges(); + + if (!_scene) { + _scene = new QGraphicsScene; + + QString s = tr("Error running the graph layouting tool.\n"); + s += tr("Please check that 'dot' is installed (package GraphViz)."); + _scene->addSimpleText(s); + centerOn(0, 0); + } else if (!activeNode && !activeEdge) { + QString s = tr("There is no call graph available for function\n" + "\t'%1'\n" + "because it has no cost of the selected event type.") + .arg(_activeItem->name()); + _scene->addSimpleText(s); + centerOn(0, 0); + } + + _panningView->setScene(_scene); + setScene(_scene); + + // if we do not 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) { + if (_prevSelectedNode) { + QPointF prevPos = mapToScene(_prevSelectedPos); + if (rect().contains(_prevSelectedPos)) { + QPointF wCenter = mapToScene(viewport()->rect().center()); + centerOn(sNode->rect().center() + + wCenter - prevPos); + } + else + ensureVisible(sNode); + } else + centerOn(sNode); + } + + if (activeNode) { + CanvasNode* cn = activeNode->canvasNode(); + CanvasFrame* f = new CanvasFrame(cn); + _scene->addItem(f); + f->setPos(cn->pos()); + f->setZValue(-1); + } + + _panningZoom = 0; + updateSizes(); + + _scene->update(); + viewport()->setUpdatesEnabled(true); + + delete _renderProcess; + _renderProcess = 0; +} + + +// Called by QAbstractScrollArea to notify about scrollbar changes +void CallGraphView::scrollContentsBy(int dx, int dy) +{ + // call QGraphicsView implementation + QGraphicsView::scrollContentsBy(dx, dy); + + QPointF topLeft = mapToScene(QPoint(0, 0)); + QPointF bottomRight = mapToScene(QPoint(width(), height())); + + QRectF z(topLeft, bottomRight); + if (0) + qDebug("CallGraphView::scrollContentsBy(dx %d, dy %d) - to (%f,%f - %f,%f)", + dx, dy, topLeft.x(), topLeft.y(), + bottomRight.x(), bottomRight.y()); + _panningView->setZoomRect(z); +} + +void CallGraphView::zoomRectMoved(qreal dx, qreal dy) +{ + //FIXME if (leftMargin()>0) dx = 0; + //FIXME if (topMargin()>0) dy = 0; + + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + hBar->setValue(hBar->value() + int(dx)); + vBar->setValue(vBar->value() + int(dy)); +} + +void CallGraphView::zoomRectMoveFinished() +{ + if (_zoomPosition == Auto) + updateSizes(); +} + +void CallGraphView::mousePressEvent(QMouseEvent* e) +{ + // clicking on the viewport sets focus + setFocus(); + + // activate scroll mode on left click + if (e->button() == Qt::LeftButton) _isMoving = true; + + QGraphicsItem* i = itemAt(e->pos()); + if (i) { + + if (i->type() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) + qDebug("CallGraphView: Got Node '%s'", + qPrintable(n->function()->prettyName())); + + selected(n->function()); + } + + // redirect from label / arrow to edge + if (i->type() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->type() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->type() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) + qDebug("CallGraphView: Got Edge '%s'", + qPrintable(e->prettyName())); + + if (e->call()) + selected(e->call()); + } + } + _lastPos = e->pos(); +} + +void CallGraphView::mouseMoveEvent(QMouseEvent* e) +{ + if (_isMoving) { + QPoint delta = e->pos() - _lastPos; + + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + hBar->setValue(hBar->value() - delta.x()); + vBar->setValue(vBar->value() - delta.y()); + + _lastPos = e->pos(); + } +} + +void CallGraphView::mouseReleaseEvent(QMouseEvent*) +{ + _isMoving = false; + if (_zoomPosition == Auto) + updateSizes(); +} + +void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e) +{ + QGraphicsItem* i = itemAt(e->pos()); + if (i == 0) + return; + + if (i->type() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) + qDebug("CallGraphView: Double Clicked on Node '%s'", + qPrintable(n->function()->prettyName())); + + activated(n->function()); + } + + // redirect from label / arrow to edge + if (i->type() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->type() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->type() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (e->call()) { + if (0) + qDebug("CallGraphView: Double Clicked On Edge '%s'", + qPrintable(e->call()->prettyName())); + + activated(e->call()); + } + } +} + +// helper functions for context menu: +// submenu builders and trigger handlers + +QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d) +{ + QAction* a; + + a = m->addAction(s); + a->setData(d); + a->setCheckable(true); + a->setChecked(_maxCallerDepth == d); + + return a; +} + +QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu) +{ + QAction* a; + QMenu* m; + + m = menu->addMenu(tr("Caller Depth")); + a = addCallerDepthAction(m, tr("Unlimited"), -1); + a->setEnabled(_funcLimit>0.005); + m->addSeparator(); + addCallerDepthAction(m, tr("Depth 0", "None"), 0); + addCallerDepthAction(m, tr("max. 2"), 2); + addCallerDepthAction(m, tr("max. 5"), 5); + addCallerDepthAction(m, tr("max. 10"), 10); + addCallerDepthAction(m, tr("max. 15"), 15); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(callerDepthTriggered(QAction*)) ); + + return m; +} + +void CallGraphView::callerDepthTriggered(QAction* a) +{ + _maxCallerDepth = a->data().toInt(0); + refresh(); +} + +QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d) +{ + QAction* a; + + a = m->addAction(s); + a->setData(d); + a->setCheckable(true); + a->setChecked(_maxCalleeDepth == d); + + return a; +} + +QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu) +{ + QAction* a; + QMenu* m; + + m = menu->addMenu(tr("Callee Depth")); + a = addCalleeDepthAction(m, tr("Unlimited"), -1); + a->setEnabled(_funcLimit>0.005); + m->addSeparator(); + addCalleeDepthAction(m, tr("Depth 0", "None"), 0); + addCalleeDepthAction(m, tr("max. 2"), 2); + addCalleeDepthAction(m, tr("max. 5"), 5); + addCalleeDepthAction(m, tr("max. 10"), 10); + addCalleeDepthAction(m, tr("max. 15"), 15); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(calleeDepthTriggered(QAction*)) ); + + return m; +} + +void CallGraphView::calleeDepthTriggered(QAction* a) +{ + _maxCalleeDepth = a->data().toInt(0); + refresh(); +} + +QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l) +{ + QAction* a; + + a = m->addAction(s); + a->setData(l); + a->setCheckable(true); + a->setChecked(_funcLimit == l); + + return a; +} + +QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu) +{ + QAction* a; + QMenu* m; + + m = menu->addMenu(tr("Min. Node Cost")); + a = addNodeLimitAction(m, tr("No Minimum"), 0.0); + // Unlimited node cost easily produces huge graphs such that 'dot' + // would need a long time to layout. For responsiveness, we only allow + // for unlimited node cost if a caller and callee depth limit is set. + a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0)); + m->addSeparator(); + addNodeLimitAction(m, tr("50 %"), .5); + addNodeLimitAction(m, tr("20 %"), .2); + addNodeLimitAction(m, tr("10 %"), .1); + addNodeLimitAction(m, tr("5 %"), .05); + addNodeLimitAction(m, tr("2 %"), .02); + addNodeLimitAction(m, tr("1 %"), .01); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(nodeLimitTriggered(QAction*)) ); + + return m; +} + +void CallGraphView::nodeLimitTriggered(QAction* a) +{ + _funcLimit = a->data().toDouble(0); + refresh(); +} + +QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l) +{ + QAction* a; + + a = m->addAction(s); + a->setData(l); + a->setCheckable(true); + a->setChecked(_callLimit == l); + + return a; +} + +QMenu* CallGraphView::addCallLimitMenu(QMenu* menu) +{ + QMenu* m; + + m = menu->addMenu(tr("Min. Call Cost")); + addCallLimitAction(m, tr("Same as Node"), 1.0); + // xgettext: no-c-format + addCallLimitAction(m, tr("50 % of Node"), .5); + // xgettext: no-c-format + addCallLimitAction(m, tr("20 % of Node"), .2); + // xgettext: no-c-format + addCallLimitAction(m, tr("10 % of Node"), .1); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(callLimitTriggered(QAction*)) ); + + return m; +} + +void CallGraphView::callLimitTriggered(QAction* a) +{ + _callLimit = a->data().toDouble(0); + refresh(); +} + +QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s, + CallGraphView::ZoomPosition p) +{ + QAction* a; + + a = m->addAction(s); + a->setData((int)p); + a->setCheckable(true); + a->setChecked(_zoomPosition == p); + + return a; +} + +QMenu* CallGraphView::addZoomPosMenu(QMenu* menu) +{ + QMenu* m = menu->addMenu(tr("Birds-eye View")); + addZoomPosAction(m, tr("Top Left"), TopLeft); + addZoomPosAction(m, tr("Top Right"), TopRight); + addZoomPosAction(m, tr("Bottom Left"), BottomLeft); + addZoomPosAction(m, tr("Bottom Right"), BottomRight); + addZoomPosAction(m, tr("Automatic"), Auto); + addZoomPosAction(m, tr("Hide"), Hide); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(zoomPosTriggered(QAction*)) ); + + return m; +} + + +void CallGraphView::zoomPosTriggered(QAction* a) +{ + _zoomPosition = (ZoomPosition) a->data().toInt(0); + updateSizes(); +} + +QAction* CallGraphView::addLayoutAction(QMenu* m, QString s, + GraphOptions::Layout l) +{ + QAction* a; + + a = m->addAction(s); + a->setData((int)l); + a->setCheckable(true); + a->setChecked(_layout == l); + + return a; +} + +QMenu* CallGraphView::addLayoutMenu(QMenu* menu) +{ + QMenu* m = menu->addMenu(tr("Layout")); + addLayoutAction(m, tr("Top to Down"), TopDown); + addLayoutAction(m, tr("Left to Right"), LeftRight); + addLayoutAction(m, tr("Circular"), Circular); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(layoutTriggered(QAction*)) ); + + return m; +} + + +void CallGraphView::layoutTriggered(QAction* a) +{ + _layout = (Layout) a->data().toInt(0); + refresh(); +} + + +void CallGraphView::contextMenuEvent(QContextMenuEvent* e) +{ + _isMoving = false; + + QGraphicsItem* i = itemAt(e->pos()); + + QMenu popup; + TraceFunction *f = 0, *cycle = 0; + TraceCall* c = 0; + + QAction* activateFunction = 0; + QAction* activateCycle = 0; + QAction* activateCall = 0; + if (i) { + if (i->type() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) + qDebug("CallGraphView: Menu on Node '%s'", + qPrintable(n->function()->prettyName())); + + f = n->function(); + cycle = f->cycle(); + + QString name = f->prettyName(); + QString menuStr = tr("Go to '%1'") + .arg(GlobalConfig::shortenSymbol(name)); + activateFunction = popup.addAction(menuStr); + if (cycle && (cycle != f)) { + name = GlobalConfig::shortenSymbol(cycle->prettyName()); + activateCycle = popup.addAction(tr("Go to '%1'").arg(name)); + } + popup.addSeparator(); + } + + // redirect from label / arrow to edge + if (i->type() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->type() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->type() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) + qDebug("CallGraphView: Menu on Edge '%s'", + qPrintable(e->prettyName())); + + c = e->call(); + if (c) { + QString name = c->prettyName(); + QString menuStr = tr("Go to '%1'") + .arg(GlobalConfig::shortenSymbol(name)); + activateCall = popup.addAction(menuStr); + + popup.addSeparator(); + } + } + } + + QAction* stopLayout = 0; + if (_renderProcess) { + stopLayout = popup.addAction(tr("Stop Layouting")); + popup.addSeparator(); + } + + addGoMenu(&popup); + popup.addSeparator(); + + QMenu* epopup = popup.addMenu(tr("Export Graph")); + QAction* exportAsDot = epopup->addAction(tr("As DOT file...")); + QAction* exportAsImage = epopup->addAction(tr("As Image...")); + popup.addSeparator(); + + QMenu* gpopup = popup.addMenu(tr("Graph")); + addCallerDepthMenu(gpopup); + addCalleeDepthMenu(gpopup); + addNodeLimitMenu(gpopup); + addCallLimitMenu(gpopup); + gpopup->addSeparator(); + + QAction* toggleSkipped; + toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls")); + toggleSkipped->setCheckable(true); + toggleSkipped->setChecked(_showSkipped); + + QAction* toggleExpand; + toggleExpand = gpopup->addAction(tr("Inner-cycle Calls")); + toggleExpand->setCheckable(true); + toggleExpand->setChecked(_expandCycles); + + QAction* toggleCluster; + toggleCluster = gpopup->addAction(tr("Cluster Groups")); + toggleCluster->setCheckable(true); + toggleCluster->setChecked(_clusterGroups); + + QMenu* vpopup = popup.addMenu(tr("Visualization")); + QAction* layoutCompact = vpopup->addAction(tr("Compact")); + layoutCompact->setCheckable(true); + layoutCompact->setChecked(_detailLevel == 0); + QAction* layoutNormal = vpopup->addAction(tr("Normal")); + layoutNormal->setCheckable(true); + layoutNormal->setChecked(_detailLevel == 1); + QAction* layoutTall = vpopup->addAction(tr("Tall")); + layoutTall->setCheckable(true); + layoutTall->setChecked(_detailLevel == 2); + + addLayoutMenu(&popup); + addZoomPosMenu(&popup); + + QAction* a = popup.exec(e->globalPos()); + + if (a == activateFunction) + activated(f); + else if (a == activateCycle) + activated(cycle); + else if (a == activateCall) + activated(c); + + else if (a == stopLayout) + stopRendering(); + + else if (a == exportAsDot) { + TraceFunction* f = activeFunction(); + if (!f) return; + + QString n; + n = QFileDialog::getSaveFileName(this, + tr("Export Graph As DOT file"), + QString(), tr("Graphviz (*.dot)")); + + if (!n.isEmpty()) { + GraphExporter ge(TraceItemView::data(), f, eventType(), + groupType(), n); + ge.setGraphOptions(this); + ge.writeDot(); + } + } + else if (a == exportAsImage) { + // write current content of canvas as image to file + if (!_scene) return; + + QString n; + n = QFileDialog::getSaveFileName(this, + tr("Export Graph As Image"), + QString(), + tr("Images (*.png *.jpg)")); + + if (!n.isEmpty()) { + QRect r = _scene->sceneRect().toRect(); + QPixmap pix(r.width(), r.height()); + QPainter p(&pix); + _scene->render( &p ); + pix.save(n); + } + } + + else if (a == toggleSkipped) { + _showSkipped = !_showSkipped; + refresh(); + } + else if (a == toggleExpand) { + _expandCycles = !_expandCycles; + refresh(); + } + else if (a == toggleCluster) { + _clusterGroups = !_clusterGroups; + refresh(); + } + + else if (a == layoutCompact) { + _detailLevel = 0; + refresh(); + } + else if (a == layoutNormal) { + _detailLevel = 1; + refresh(); + } + else if (a == layoutTall) { + _detailLevel = 2; + refresh(); + } +} + + +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; + if (s == QString("Hide")) + return Hide; + + return DEFAULT_ZOOMPOS; +} + +QString CallGraphView::zoomPosString(ZoomPosition p) +{ + if (p == TopLeft) + return QString("TopLeft"); + if (p == TopRight) + return QString("TopRight"); + if (p == BottomLeft) + return QString("BottomLeft"); + if (p == BottomRight) + return QString("BottomRight"); + if (p == Auto) + return QString("Automatic"); + if (p == Hide) + return QString("Hide"); + + return QString(); +} + +void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + _maxCallerDepth = g->value("MaxCaller", DEFAULT_MAXCALLER).toInt(); + _maxCalleeDepth = g->value("MaxCallee", DEFAULT_MAXCALLEE).toInt(); + _funcLimit = g->value("FuncLimit", DEFAULT_FUNCLIMIT).toDouble(); + _callLimit = g->value("CallLimit", DEFAULT_CALLLIMIT).toDouble(); + _showSkipped = g->value("ShowSkipped", DEFAULT_SHOWSKIPPED).toBool(); + _expandCycles = g->value("ExpandCycles", DEFAULT_EXPANDCYCLES).toBool(); + _clusterGroups = g->value("ClusterGroups", DEFAULT_CLUSTERGROUPS).toBool(); + _detailLevel = g->value("DetailLevel", DEFAULT_DETAILLEVEL).toInt(); + _layout = GraphOptions::layout(g->value("Layout", + layoutString(DEFAULT_LAYOUT)).toString()); + _zoomPosition = zoomPos(g->value("ZoomPosition", + zoomPosString(DEFAULT_ZOOMPOS)).toString()); + + delete g; +} + +void CallGraphView::saveOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + g->setValue("MaxCaller", _maxCallerDepth, DEFAULT_MAXCALLER); + g->setValue("MaxCallee", _maxCalleeDepth, DEFAULT_MAXCALLEE); + g->setValue("FuncLimit", _funcLimit, DEFAULT_FUNCLIMIT); + g->setValue("CallLimit", _callLimit, DEFAULT_CALLLIMIT); + g->setValue("ShowSkipped", _showSkipped, DEFAULT_SHOWSKIPPED); + g->setValue("ExpandCycles", _expandCycles, DEFAULT_EXPANDCYCLES); + g->setValue("ClusterGroups", _clusterGroups, DEFAULT_CLUSTERGROUPS); + g->setValue("DetailLevel", _detailLevel, DEFAULT_DETAILLEVEL); + g->setValue("Layout", layoutString(_layout), layoutString(DEFAULT_LAYOUT)); + g->setValue("ZoomPosition", zoomPosString(_zoomPosition), + zoomPosString(DEFAULT_ZOOMPOS)); + + delete g; +} + +#include "moc_callgraphview.cpp" + diff --git a/kcachegrind/libviews/callgraphview.h b/kcachegrind/libviews/callgraphview.h new file mode 100644 index 00000000..c8e7bf6d --- /dev/null +++ b/kcachegrind/libviews/callgraphview.h @@ -0,0 +1,686 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Callgraph View + */ + +#ifndef CALLGRAPHVIEW_H +#define CALLGRAPHVIEW_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "treemap.h" // for DrawParams +#include "tracedata.h" +#include "traceitemview.h" + +class QProcess; +class QTemporaryFile; +class QIODevice; + +class CanvasNode; +class CanvasEdge; +class GraphEdge; +class CallGraphView; + + +// temporary parts of call graph to be shown +class GraphNode +{ +public: + GraphNode(); + + TraceFunction* function() const + { + return _f; + } + + void setFunction(TraceFunction* f) + { + _f = f; + } + + CanvasNode* canvasNode() const + { + return _cn; + } + + void setCanvasNode(CanvasNode* cn) + { + _cn = cn; + } + + bool isVisible() const + { + return _visible; + } + + void setVisible(bool v) + { + _visible = v; + } + + void clearEdges(); + void sortEdges(); + void addCallee(GraphEdge*); + void addCaller(GraphEdge*); + void addUniqueCallee(GraphEdge*); + void addUniqueCaller(GraphEdge*); + void removeEdge(GraphEdge*); + double calleeCostSum(); + double calleeCountSum(); + double callerCostSum(); + double callerCountSum(); + + // keyboard navigation + TraceCall* visibleCaller(); + TraceCall* visibleCallee(); + void setCallee(GraphEdge*); + void setCaller(GraphEdge*); + TraceFunction* nextVisible(); + TraceFunction* priorVisible(); + TraceCall* nextVisibleCaller(GraphEdge* = 0); + TraceCall* nextVisibleCallee(GraphEdge* = 0); + TraceCall* priorVisibleCaller(GraphEdge* = 0); + TraceCall* priorVisibleCallee(GraphEdge* = 0); + + double self, incl; + +private: + TraceFunction* _f; + CanvasNode* _cn; + bool _visible; + + QList callers, callees; + + // for keyboard navigation + int _lastCallerIndex, _lastCalleeIndex; + bool _lastFromCaller; +}; + + +class GraphEdge +{ +public: + GraphEdge(); + + CanvasEdge* canvasEdge() const + { + return _ce; + } + + void setCanvasEdge(CanvasEdge* ce) + { + _ce = ce; + } + + TraceCall* call() const + { + return _c; + } + + void setCall(TraceCall* c) + { + _c = c; + } + + bool isVisible() const + { + return _visible; + } + + void setVisible(bool v) + { + _visible = v; + } + + GraphNode* fromNode() const + { + return _fromNode; + } + + GraphNode* toNode() const + { + return _toNode; + } + + TraceFunction* from() const + { + return _from; + } + + TraceFunction* to() const + { + return _to; + } + + // has special cases for collapsed edges + QString prettyName(); + + void setCaller(TraceFunction* f) + { + _from = f; + } + + void setCallee(TraceFunction* f) + { + _to = f; + } + + void setCallerNode(GraphNode* n) + { + _fromNode = n; + } + + void setCalleeNode(GraphNode* n) + { + _toNode = n; + } + + // keyboard navigation + TraceFunction* visibleCaller(); + TraceFunction* visibleCallee(); + 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 GraphNodeMap; +typedef QMap, GraphEdge> GraphEdgeMap; + + +/* Abstract Interface for graph options */ +class GraphOptions +{ +public: + enum Layout {TopDown, LeftRight, Circular}; + virtual ~GraphOptions() {} + virtual double funcLimit() = 0; + virtual double callLimit() = 0; + virtual int maxCallerDepth() = 0; + virtual int maxCalleeDepth() = 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(); + virtual ~StorableGraphOptions(){} + // implementation of getters + virtual double funcLimit() { return _funcLimit; } + virtual double callLimit() { return _callLimit; } + virtual int maxCallerDepth() { return _maxCallerDepth; } + virtual int maxCalleeDepth() { return _maxCalleeDepth; } + 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 setMaxCalleeDepth(int d) { _maxCalleeDepth = 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, _maxCalleeDepth; + 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*, EventType*, + ProfileContext::Type, + QString filename = QString()); + virtual ~GraphExporter(); + + void reset(TraceData*, CostItem*, EventType*, + ProfileContext::Type, + QString filename = QString()); + + 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(QIODevice* = 0); + + // to map back to structures when parsing a layouted graph + + /* is a helper for node() and edge(). + * Do not 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; + CostItem* _item; + EventType* _eventType; + ProfileContext::Type _groupType; + QTemporaryFile* _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 laid over a QGraphicsScene + */ +class PanningView : public QGraphicsView +{ + Q_OBJECT + +public: + PanningView(QWidget * parent = 0); + + void setZoomRect(const QRectF& r); + +signals: + void zoomRectMoved(qreal dx, qreal dy); + void zoomRectMoveFinished(); + +protected: + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void drawForeground(QPainter * p, const QRectF&); + + QRectF _zoomRect; + bool _movingZoomRect; + QPointF _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 QGraphicsRectItem, public StoredDrawParams +{ +public: + CanvasNode(CallGraphView*, GraphNode*, int, int, int, int); + + void updateGroup(); + void setSelected(bool); + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + + GraphNode* node() + { + return _node; + } + + int type() const + { + return CANVAS_NODE; + } + +private: + GraphNode* _node; + CallGraphView* _view; +}; + + +class CanvasEdgeLabel : public QGraphicsRectItem, public StoredDrawParams +{ +public: + CanvasEdgeLabel(CallGraphView*, CanvasEdge*, int, int, int, int); + + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + + CanvasEdge* canvasEdge() + { + return _ce; + } + + int type() const + { + return CANVAS_EDGELABEL; + } + + double percentage() const + { + return _percentage; + } + +private: + CanvasEdge* _ce; + CallGraphView* _view; + + double _percentage; +}; + + +class CanvasEdgeArrow : public QGraphicsPolygonItem +{ +public: + CanvasEdgeArrow(CanvasEdge*); + + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + + CanvasEdge* canvasEdge() + { + return _ce; + } + + int type() const + { + return CANVAS_EDGEARROW; + } + +private: + CanvasEdge* _ce; +}; + + +class CanvasEdge : public QGraphicsPathItem +{ +public: + CanvasEdge(GraphEdge*); + + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + + void setSelected(bool); + + CanvasEdgeLabel* label() + { + return _label; + } + + void setLabel(CanvasEdgeLabel* l); + + CanvasEdgeArrow* arrow() + { + return _arrow; + } + + void setArrow(CanvasEdgeArrow* a); + + const QPolygon& controlPoints() const + { + return _points; + } + + void setControlPoints(const QPolygon& a); + + GraphEdge* edge() + { + return _edge; + } + + int type() const + { + return CANVAS_EDGE; + } + +private: + GraphEdge* _edge; + CanvasEdgeLabel* _label; + CanvasEdgeArrow* _arrow; + QPolygon _points; + + double _thickness; +}; + + +class CanvasFrame : public QGraphicsRectItem +{ +public: + CanvasFrame(CanvasNode*); + + int type() const + { + return CANVAS_FRAME; + } + + bool hit(const QPoint&) const + { + return false; + } + + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + +private: + static QPixmap* _p; +}; + + +class CallGraphTip; + +/** + * A QGraphicsView 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 QGraphicsView, public TraceItemView, + public StorableGraphOptions +{ + Q_OBJECT + +public: + enum ZoomPosition {TopLeft, TopRight, BottomLeft, BottomRight, Auto, Hide}; + + explicit CallGraphView(TraceItemView* parentView, QWidget* parent=0, + const char* name = 0); + ~CallGraphView(); + + void restoreOptions(const QString& prefix, const QString& postfix); + void saveOptions(const QString& prefix, const QString& postfix); + + QWidget* widget() + { + return this; + } + + QString whatsThis() const; + + ZoomPosition zoomPos() const + { + return _zoomPosition; + } + + static ZoomPosition zoomPos(QString); + static QString zoomPosString(ZoomPosition); + +public slots: + void zoomRectMoved(qreal, qreal); + void zoomRectMoveFinished(); + + void showRenderWarning(); + void showRenderError(QString); + void stopRendering(); + void readDotOutput(); + void dotError(); + void dotExited(); + + // context menu trigger handlers + void callerDepthTriggered(QAction*); + void calleeDepthTriggered(QAction*); + void nodeLimitTriggered(QAction*); + void callLimitTriggered(QAction*); + void zoomPosTriggered(QAction*); + void layoutTriggered(QAction*); + +protected: + void resizeEvent(QResizeEvent*); + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void mouseDoubleClickEvent(QMouseEvent*); + void contextMenuEvent(QContextMenuEvent*); + void keyPressEvent(QKeyEvent*); + void focusInEvent(QFocusEvent*); + void focusOutEvent(QFocusEvent*); + void scrollContentsBy(int dx, int dy); + +private: + void updateSizes(QSize s = QSize(0,0)); + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); + void makeFrame(CanvasNode*, bool active); + void clear(); + void showText(QString); + + // context menu builders + QAction* addCallerDepthAction(QMenu*,QString,int); + QMenu* addCallerDepthMenu(QMenu*); + QAction* addCalleeDepthAction(QMenu*,QString,int); + QMenu* addCalleeDepthMenu(QMenu*); + QAction* addNodeLimitAction(QMenu*,QString,double); + QMenu* addNodeLimitMenu(QMenu*); + QAction* addCallLimitAction(QMenu*,QString,double); + QMenu* addCallLimitMenu(QMenu*); + QAction* addZoomPosAction(QMenu*,QString,ZoomPosition); + QMenu* addZoomPosMenu(QMenu*); + QAction* addLayoutAction(QMenu*,QString,Layout); + QMenu* addLayoutMenu(QMenu*); + + QGraphicsScene *_scene; + int _xMargin, _yMargin; + PanningView *_panningView; + double _panningZoom; + + CallGraphTip* _tip; + + bool _isMoving; + QPoint _lastPos; + + GraphExporter _exporter; + + GraphNode* _selectedNode; + GraphEdge* _selectedEdge; + + // widget options + ZoomPosition _zoomPosition, _lastAutoPosition; + + // background rendering + QProcess* _renderProcess; + QString _renderProcessCmdLine; + QTimer _renderTimer; + GraphNode* _prevSelectedNode; + QPoint _prevSelectedPos; + QString _unparsedOutput; +}; + + +#endif diff --git a/kcachegrind/libviews/callitem.cpp b/kcachegrind/libviews/callitem.cpp new file mode 100644 index 00000000..829e13e3 --- /dev/null +++ b/kcachegrind/libviews/callitem.cpp @@ -0,0 +1,195 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items for caller/callee view. + */ + +#include "callitem.h" + +#include + +#include "globalguiconfig.h" +#include "listutils.h" +#include "callview.h" + + +// CallItem + + +CallItem::CallItem(CallView* view, QTreeWidget* parent, TraceCall* c) + : QTreeWidgetItem(parent) +{ + for (int i = 0 ; i < 5; ++i) + setTextAlignment(i, Qt::AlignRight); + + _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(5, fName); + updateGroup(); + updateCost(); +} + +void CallItem::updateGroup() +{ + QColor c = GlobalGUIConfig::functionColor(_view->groupType(), _shown); + setIcon(5, 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 = QObject::tr("(active)"); + else + cStr = _call->prettyCallCount(); + } + setText(4, cStr); + + ProfileCostArray* totalCost; + if (GlobalConfig::showExpanded()) { + if (_active->cycle()) + totalCost = _active->cycle()->inclusive(); + else + totalCost = _active->inclusive(); + } + else + totalCost = _active->data(); + + EventType* ct = _view->eventType(); + _sum = _call->subCost(ct); + double total = totalCost->subCost(ct); + + if (total == 0.0) { + QString str = "-"; + + setText(0, str); + setIcon(0, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (GlobalConfig::showPercentage()) + setText(0, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + else { + setText(0, _call->prettySubCost(ct)); + } + setIcon(0, costPixmap(ct, _call, total, false)); + setText(1, _call->prettySubCostPerCall(ct, _cc)); + } + + // Cost Type 2 + EventType* ct2 = _view->eventType2(); + if (ct2) { + _sum2 = _call->subCost(ct2); + double total = totalCost->subCost(ct2); + + if (total == 0.0) { + QString str = "-"; + + setText(2, str); + setIcon(2, QPixmap()); + } + else { + double sum = 100.0 * _sum2 / total; + + if (GlobalConfig::showPercentage()) + setText(2, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + else { + setText(2, _call->prettySubCost(ct2)); + } + setIcon(2, costPixmap(ct2, _call, total, false)); + setText(3, _call->prettySubCostPerCall(ct2, _cc)); + } + } + + QPixmap p; + if (sameCycle && !selectedIsCycle && !shownIsCycle) { + + QString icon = "edit-undo"; +#if 0 // TODO + KIconLoader* loader = KIconLoader::global(); + p= loader->loadIcon(icon, KIconLoader::Small, 0, + KIconLoader::DefaultState, QStringList(), 0, true); +#endif + } + setIcon(4, p); +} + + +bool CallItem::operator<(const QTreeWidgetItem& other) const +{ + int col = treeWidget()->sortColumn(); + const CallItem* ci1 = this; + const CallItem* ci2 = (CallItem*) &other; + + if (col==0) + return ci1->_sum < ci2->_sum; + + if (col==1) { + uint64 cc1 = ci1->_cc; + uint64 cc2 = ci2->_cc; + if (cc1 == 0) cc1 = 1; + if (cc2 == 0) cc2 = 1; + return (ci1->_sum / cc1) < (ci2->_sum / cc2); + } + + if (col==2) + return ci1->_sum2 < ci2->_sum2; + + if (col==3) { + uint64 cc1 = ci1->_cc; + uint64 cc2 = ci2->_cc; + if (cc1 == 0) cc1 = 1; + if (cc2 == 0) cc2 = 1; + return (ci1->_sum2 / cc1) < (ci2->_sum2 / cc2); + } + + if (col==4) + return ci1->_cc < ci2->_cc; + + return QTreeWidgetItem::operator <(other); +} + diff --git a/kcachegrind/libviews/callitem.h b/kcachegrind/libviews/callitem.h new file mode 100644 index 00000000..9e39cbc0 --- /dev/null +++ b/kcachegrind/libviews/callitem.h @@ -0,0 +1,50 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of call view. + */ + +#ifndef CALLITEM_H +#define CALLITEM_H + +#include +#include "tracedata.h" + +class CallView; + +class CallItem: public QTreeWidgetItem +{ +public: + CallItem(CallView*, QTreeWidget*, TraceCall* c); + + bool operator<(const QTreeWidgetItem& other) 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/libviews/callmapview.cpp b/kcachegrind/libviews/callmapview.cpp new file mode 100644 index 00000000..c368e073 --- /dev/null +++ b/kcachegrind/libviews/callmapview.cpp @@ -0,0 +1,993 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Call Map View + */ + + +#include "callmapview.h" + +#include +#include +#include + +#include "config.h" +#include "globalguiconfig.h" +#include "listutils.h" +#include "toplevelbase.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_STOPNAME "" +#define DEFAULT_MAXDEPTH -1 +#define DEFAULT_MAXAREA 100 + + +CallMapView::CallMapView(bool showCallers, TraceItemView* parentView, + QWidget* parent, const char* name) + : TreeMapWidget(new CallMapBaseItem(), parent), TraceItemView(parentView) +{ + setObjectName(name); + _showCallers = showCallers; + + setFieldType(0, tr("A thing's name", "Name" )); + setFieldType(1, tr( "Cost" )); + setFieldType(2, tr( "Location" )); + setFieldPosition(2, TreeMapItem::TopLeft); + setFieldType(3, tr( "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 &))); + + this->setWhatsThis( whatsThis()); +} + +QString CallMapView::whatsThis() const +{ + QString s = _showCallers ? + tr( "Caller Map" + "

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 constraints).

") : + tr("Call Map" + "

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 constraints).

"); + + s += tr( "

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 " + "very 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 before drawing children. Note that " + "size proportions can get heavily wrong.

" + + "

This is a TreeMap 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. " + "Return activates the current item.

"); + + return s; +} + +void CallMapView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + ((CallMapBaseItem*)base())->setFunction(0); +} + +void CallMapView::addItemListMenu(QMenu* menu, TreeMapItem* item) +{ + QAction* a; + + QMenu* m = menu->addMenu(tr("Go To")); + int count = 0; + while (counttext(0); + a = m->addAction(GlobalConfig::shortenSymbol(name)); + a->setData(QVariant::fromValue( (void*)item )); + item = item->parent(); + count++; + } + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(mapItemTriggered(QAction*)) ); +} + +void CallMapView::mapItemTriggered(QAction* a) +{ + activatedSlot( (TreeMapItem*) a->data().value() ); +} + +QAction* CallMapView::addDrawingDepthAction(QMenu* m, + const QString& s, int d) +{ + QAction* a = m->addAction(s); + a->setData(d); + a->setCheckable(true); + a->setChecked(maxDrawingDepth() == d); + return a; +} + +void CallMapView::addDrawingDepthMenu(QMenu* menu, + TreeMapItem* i, const QString& name) +{ + QMenu* m = menu->addMenu(tr("Stop at Depth")); + addDrawingDepthAction(m, tr("No Depth Limit"), -1); + m->addSeparator(); + addDrawingDepthAction(m, tr("Depth 10"), 10); + addDrawingDepthAction(m, tr("Depth 15"), 15); + addDrawingDepthAction(m, tr("Depth 20"), 20); + if (i) { + m->addSeparator(); + addDrawingDepthAction(m, tr("Depth of '%1' (%2)") + .arg(name).arg(i->depth()), + i->depth()); + } + int maxDepth = maxDrawingDepth(); + if (maxDepth>0) { + m->addSeparator(); + addDrawingDepthAction(m, tr("Decrement Depth (to %1)").arg(maxDepth-1), + maxDepth-1); + addDrawingDepthAction(m, tr("Increment Depth (to %1)").arg(maxDepth+1), + maxDepth+1); + } + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(drawingDepthTriggered(QAction*)) ); +} + +void CallMapView::drawingDepthTriggered(QAction* a) +{ + setMaxDrawingDepth(a->data().toInt()); +} + +QAction* CallMapView::addStopFunctionAction(QMenu* m, + const QString& s, + const QString& v) +{ + QAction* a = m->addAction(s); + a->setData(v); + a->setCheckable(true); + a->setChecked(fieldStop(0) == v); + return a; +} + +void CallMapView::addStopFunctionMenu(QMenu* menu, TreeMapItem* item) +{ + QMenu* m = menu->addMenu(tr("Stop at Function")); + addStopFunctionAction(m, tr("No Function Limit"), QString()); + + bool foundStopName = false; + QAction* a; + if (item) { + m->addSeparator(); + int count = 0; + while (counttext(0)); + a = addStopFunctionAction(m, name, item->text(0)); + if (a->isChecked()) foundStopName = true; + item = item->parent(); + count++; + } + } + if (!foundStopName && !fieldStop(0).isEmpty()) { + m->addSeparator(); + QString name = GlobalConfig::shortenSymbol(fieldStop(0)); + addStopFunctionAction(m, name, fieldStop(0)); + } + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(stopFunctionTriggered(QAction*)) ); +} + +void CallMapView::stopFunctionTriggered(QAction* a) +{ + setFieldStop(0, a->data().toString()); +} + +QAction* CallMapView::addAreaLimitAction(QMenu* m, + const QString& s, int v) +{ + QAction* a = m->addAction(s); + a->setData(v); + a->setCheckable(true); + a->setChecked(minimalArea() == v); + return a; +} + +void CallMapView::addAreaLimitMenu(QMenu* menu, TreeMapItem* i, + const QString& name) +{ + QMenu* m = menu->addMenu(tr("Stop at Area")); + addAreaLimitAction(m, tr("No Area Limit"), -1); + m->addSeparator(); + addAreaLimitAction(m, tr("100 Pixels"), 100); + addAreaLimitAction(m, tr("200 Pixels"), 200); + addAreaLimitAction(m, tr("500 Pixels"), 500); + addAreaLimitAction(m, tr("1000 Pixels"), 1000); + + int currentArea = 0; + if (i) { + currentArea = i->width() * i->height(); + m->addSeparator(); + addAreaLimitAction(m, tr("Area of '%1' (%2)") + .arg(name).arg(currentArea), currentArea); + } + int mArea = minimalArea(); + if (mArea>0) { + m->addSeparator(); + addAreaLimitAction(m, tr("Double Area Limit (to %1)") + .arg(mArea*2), mArea*2); + addAreaLimitAction(m, tr("Half Area Limit (to %1)") + .arg(mArea/2), mArea/2); + } + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(areaLimitTriggered(QAction*)) ); +} + +void CallMapView::areaLimitTriggered(QAction* a) +{ + setMinimalArea(a->data().toInt()); +} + +QAction* CallMapView::addBorderWidthAction(QMenu* m, const QString& s, int v) +{ + QAction* a = m->addAction(s); + a->setData(v); + a->setCheckable(true); + a->setChecked(borderWidth() == v); + return a; +} + +void CallMapView::borderWidthTriggered(QAction* a) +{ + setBorderWidth(a->data().toInt()); +} + +void CallMapView::context(TreeMapItem* i,const QPoint & p) +{ + if (!i) return; + + QMenu popup; + QAction* a; + + QString shortCurrentName; + if (i) { + shortCurrentName = GlobalConfig::shortenSymbol(i->text(0)); + } + + if (i) { + addItemListMenu(&popup, i); + popup.addSeparator(); + } + addGoMenu(&popup); + popup.addSeparator(); + addDrawingDepthMenu(&popup, i, shortCurrentName); + addStopFunctionMenu(&popup, i); + addAreaLimitMenu(&popup, i, shortCurrentName); + popup.addSeparator(); + + QMenu* vpopup = popup.addMenu(tr("Visualization")); + QMenu* spopup = vpopup->addMenu(tr("Split Direction")); + addSplitDirectionItems(spopup); + + QAction* skipBorderAction = vpopup->addAction(tr("Skip Incorrect Borders")); + skipBorderAction->setEnabled(!_showCallers); + skipBorderAction->setCheckable(true); + skipBorderAction->setChecked(skipIncorrectBorder()); + + QMenu* bpopup = vpopup->addMenu(tr("Border Width")); + a = addBorderWidthAction(bpopup, tr("Border 0"), 0); + a->setEnabled(!_showCallers); + addBorderWidthAction(bpopup, tr("Border 1"), 1); + addBorderWidthAction(bpopup, tr("Border 2"), 2); + addBorderWidthAction(bpopup, tr("Border 3"), 3); + connect(bpopup, SIGNAL(triggered(QAction*)), + this, SLOT(borderWidthTriggered(QAction*)) ); + vpopup->addSeparator(); + + QAction* drawNamesAction = vpopup->addAction(tr("Draw Symbol Names")); + drawNamesAction->setCheckable(true); + QAction* drawCostAction = vpopup->addAction(tr("Draw Cost")); + drawCostAction->setCheckable(true); + QAction* drawLocationAction = vpopup->addAction(tr("Draw Location")); + drawLocationAction->setCheckable(true); + QAction* drawCallsAction = vpopup->addAction(tr("Draw Calls")); + drawCallsAction->setCheckable(true); + vpopup->addSeparator(); + + QAction* ignorePropAction = vpopup->addAction(tr("Ignore Proportions")); + ignorePropAction->setCheckable(true); + QAction* allowRotationAction = vpopup->addAction(tr("Allow Rotation")); + allowRotationAction->setCheckable(true); + if (!fieldVisible(0) && + !fieldVisible(1) && + !fieldVisible(2) && + !fieldVisible(3)) { + ignorePropAction->setEnabled(false); + allowRotationAction->setEnabled(false); + } + else { + drawNamesAction->setChecked(fieldVisible(0)); + drawCostAction->setChecked(fieldVisible(1)); + drawLocationAction->setChecked(fieldVisible(2)); + drawCallsAction->setChecked(fieldVisible(3)); + ignorePropAction->setChecked(fieldForced(0)); + allowRotationAction->setChecked(allowRotation()); + } + + QAction* drawShadingAction = vpopup->addAction(tr("Shading")); + drawShadingAction->setCheckable(true); + drawShadingAction->setChecked(isShadingEnabled()); + + a = popup.exec(mapToGlobal(p)); + if (a == drawNamesAction) + setFieldVisible(0, !fieldVisible(0)); + else if (a == drawCostAction) + setFieldVisible(1, !fieldVisible(1)); + else if (a == drawLocationAction) + setFieldVisible(2, !fieldVisible(2)); + else if (a == drawCallsAction) + setFieldVisible(3, !fieldVisible(3)); + else if (a == ignorePropAction) { + bool newSetting = !fieldForced(0); + setFieldForced(0, newSetting); + setFieldForced(1, newSetting); + setFieldForced(2, newSetting); + setFieldForced(3, newSetting); + } + else if (a == allowRotationAction) + setAllowRotation(!allowRotation()); + else if (a == drawShadingAction) + setShadingEnabled(!isShadingEnabled()); + else if (a == skipBorderAction) + setSkipIncorrectBorder(!skipIncorrectBorder()); +} + +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 = tr("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); + } +} + +CostItem* CallMapView::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CallMapView::doUpdate(int changeType, bool) +{ + if (changeType == eventType2Changed) return; + + // if there is a selected item, always draw marking... + if (changeType & selectedItemChanged) { + TraceFunction* f = 0; + + if (_selectedItem) { + switch(_selectedItem->type()) { + case ProfileContext::Function: + case ProfileContext::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 ProfileContext::Function: + case ProfileContext::FunctionCycle: + f = (TraceFunction*)_activeItem; + break; + default: + break; + } + } + ((CallMapBaseItem*)base())->setFunction(f); + } + else if ( ((changeType & partsChanged) && GlobalConfig::showCycles()) || + (changeType & dataChanged) || + (changeType & configChanged)) { + /* regenerates the treemap because traceitems were added/removed */ + base()->refresh(); + } + else if ((changeType & partsChanged) || + (changeType & eventTypeChanged)) { + /* 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 palette().color( QPalette::Button ); + + return GlobalGUIConfig::functionColor(_groupType, f); +} + + +QString CallMapView::tipString(TreeMapItem* i) const +{ + QString tip, itemTip; + int count = 0; + + //qDebug("CallMapView::tipString for '%s'", i->text(0).toAscii()); + + // first, SubPartItem's + while (i && counttext(0)); + + if (!i->text(1).isEmpty()) + itemTip += " (" + i->text(1) + ')'; + + if (!tip.isEmpty()) tip += '\n'; + + tip += itemTip; + i = i->parent(); + count++; + } + if (count == GlobalConfig::maxSymbolCount()) tip += "\n..."; + + return tip; +} + + +ProfileCostArray* CallMapView::totalCost() +{ + TraceFunction* f = ((CallMapBaseItem*)base())->function(); + if (!f) return 0; + + return GlobalConfig::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 QObject::tr("(no function)"); + + return _f->prettyName(); + } + + if (!_f) return QString(); + + if (textNo == 2) return _f->prettyLocation(); + if (textNo == 3) return _f->calledCount().pretty(); + if (textNo != 1) return QString(); + + EventType* ct = ((CallMapView*)widget())->eventType(); + ProfileCostArray* t = ((CallMapView*)widget())->totalCost(); + + if (GlobalConfig::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', GlobalConfig::percentPrecision()); + } + return _f->inclusive()->prettySubCost(ct); +} + +QPixmap CallMapBaseItem::pixmap(int i) const +{ + if ((i != 1) || !_f) return QPixmap(); + + EventType* ct = ((CallMapView*)widget())->eventType(); + ProfileCostArray* 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; + + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + 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->eventType()); +} + + +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", + qPrintable(text(0))); + + setSorting(-1); + if (w->showCallers()) { + foreach(TraceCall* call, _f->callers()) { + // do not show calls inside of a cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + addItem(new CallMapCallerItem(1.0, call)); + } + + setSum(0); + } + else { + foreach(TraceCall* call, _f->callings()) { + // do not 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->eventType())); + } + 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 association: 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 QObject::tr("(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(); + + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + + SubCost val = SubCost(_factor * _c->subCost(ct)); + if (GlobalConfig::showPercentage()) { + // percentage relative to function cost + ProfileCostArray* t = ((CallMapView*)widget())->totalCost(); + double p = 100.0 * _factor * _c->subCost(ct) / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', GlobalConfig::percentPrecision()); + } + return val.pretty(); +} + +QPixmap CallMapCallingItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + EventType* ct = ((CallMapView*)widget())->eventType(); + ProfileCostArray* t = ((CallMapView*)widget())->totalCost(); + + // colored level meter with frame + return costPixmap( ct, _c, t->subCost(ct) / _factor, true); +} + + +double CallMapCallingItem::value() const +{ + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + 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)", + qPrintable(path(0).join("/"))); + + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + + // 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, qPrintable(_c->called()->prettyName())); + v = s; + } + double newFactor = _factor * v / s; + +#if 0 + qDebug("CallingItem: Subitems of %s => %s, factor %f * %d/%d => %f", + qPrintable(_c->caller()->prettyName()), + qPrintable(_c->called()->prettyName()), + _factor, v, s, newFactor); +#endif + setSorting(-1); + foreach(TraceCall* call, _c->called()->callings()) { + // do not 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 QObject::tr("(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(); + + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + + SubCost val = SubCost(_factor * _c->subCost(ct)); + if (GlobalConfig::showPercentage()) { + ProfileCostArray* t = ((CallMapView*)widget())->totalCost(); + double p = 100.0 * _factor * _c->subCost(ct) / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', GlobalConfig::percentPrecision()); + } + return val.pretty(); +} + + +QPixmap CallMapCallerItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + EventType* ct = ((CallMapView*)widget())->eventType(); + ProfileCostArray* t = ((CallMapView*)widget())->totalCost(); + + // colored level meter with frame + return costPixmap( ct, _c, t->subCost(ct) / _factor, true ); +} + + +double CallMapCallerItem::value() const +{ + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + 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().toAscii()); + + EventType* ct; + ct = ((CallMapView*)widget())->eventType(); + + 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", + qPrintable(_c->caller()->prettyName()), + qPrintable(_c->called()->prettyName()), + _factor, v, s, newFactor); +#endif + setSorting(-1); + + foreach(TraceCall* call, _c->caller()->callers()) { + // do not 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::restoreOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + setSplitMode(g->value("SplitMode", QString(DEFAULT_SPLITMODE)).toString()); + + setFieldVisible(0, g->value("DrawName", DEFAULT_DRAWNAME).toBool()); + setFieldVisible(1, g->value("DrawCost", DEFAULT_DRAWCOST).toBool()); + setFieldVisible(2, g->value("DrawLocation", DEFAULT_DRAWLOCATION).toBool()); + setFieldVisible(3, g->value("DrawCalls", DEFAULT_DRAWCALLS).toBool()); + + bool enable = g->value("ForceStrings", DEFAULT_FORCESTRINGS).toBool(); + setFieldForced(0, enable); + setFieldForced(1, enable); + setFieldForced(2, enable); + setFieldForced(3, enable); + + setAllowRotation(g->value("AllowRotation", DEFAULT_ROTATION).toBool()); + setShadingEnabled(g->value("Shading", DEFAULT_SHADING).toBool()); + setFieldStop(0, g->value("StopName", QString(DEFAULT_STOPNAME)).toString()); + setMaxDrawingDepth(g->value("MaxDepth", DEFAULT_MAXDEPTH).toInt()); + setMinimalArea(g->value("MaxArea", DEFAULT_MAXAREA).toInt()); + + delete g; +} + +void CallMapView::saveOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + g->setValue("SplitMode", splitModeString(), QString(DEFAULT_SPLITMODE)); + g->setValue("DrawName", fieldVisible(0), DEFAULT_DRAWNAME); + g->setValue("DrawCost", fieldVisible(1), DEFAULT_DRAWCOST); + g->setValue("DrawLocation", fieldVisible(2), DEFAULT_DRAWLOCATION); + g->setValue("DrawCalls", fieldVisible(3), DEFAULT_DRAWCALLS); + // when option for all text (0-3) + g->setValue("ForceStrings", fieldForced(0), DEFAULT_FORCESTRINGS); + + g->setValue("AllowRotation", allowRotation(), DEFAULT_ROTATION); + g->setValue("Shading", isShadingEnabled(), DEFAULT_SHADING); + + g->setValue("StopName", fieldStop(0), QString(DEFAULT_STOPNAME)); + g->setValue("MaxDepth", maxDrawingDepth(), DEFAULT_MAXDEPTH); + g->setValue("MaxArea", minimalArea(), DEFAULT_MAXAREA); + + delete g; +} + +#include "moc_callmapview.cpp" diff --git a/kcachegrind/libviews/callmapview.h b/kcachegrind/libviews/callmapview.h new file mode 100644 index 00000000..0b4ad9b6 --- /dev/null +++ b/kcachegrind/libviews/callmapview.h @@ -0,0 +1,149 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Call Map View + */ + +#ifndef CALLMAPVIEW_H +#define CALLMAPVIEW_H + +#include + +#include "treemap.h" +#include "tracedata.h" +#include "traceitemview.h" + +class QAction; +class QMenu; + +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 restoreOptions(const QString& prefix, const QString& postfix); + void saveOptions(const QString& prefix, const QString& postfix); + + bool showCallers() const { return _showCallers; } + ProfileCostArray* totalCost(); + QString tipString(TreeMapItem*) const; + QColor groupColor(TraceFunction*) const; + +private slots: + void context(TreeMapItem*,const QPoint &); + void selectedSlot(TreeMapItem*, bool); + void activatedSlot(TreeMapItem*); + void mapItemTriggered(QAction*); + void drawingDepthTriggered(QAction*); + void stopFunctionTriggered(QAction*); + void areaLimitTriggered(QAction*); + void borderWidthTriggered(QAction*); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + + // context menu builders + void addItemListMenu(QMenu*,TreeMapItem*); + QAction* addDrawingDepthAction(QMenu*, const QString&, int); + void addDrawingDepthMenu(QMenu*, TreeMapItem*, const QString&); + QAction* addStopFunctionAction(QMenu*, const QString&, const QString&); + void addStopFunctionMenu(QMenu*, TreeMapItem*); + QAction* addAreaLimitAction(QMenu*, const QString&, int); + void addAreaLimitMenu(QMenu*, TreeMapItem*, const QString&); + QAction* addBorderWidthAction(QMenu*, const QString&, 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/libviews/callview.cpp b/kcachegrind/libviews/callview.cpp new file mode 100644 index 00000000..710c29cf --- /dev/null +++ b/kcachegrind/libviews/callview.cpp @@ -0,0 +1,301 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2009 Josef Weidendorfer + + 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. +*/ + +/* + * Call Views + */ + + +#include "callview.h" + +#include +#include +#include +#include +#include + +#include "globalconfig.h" +#include "callitem.h" + + +// +// CallView +// + + +CallView::CallView(bool showCallers, TraceItemView* parentView, QWidget* parent) + : QTreeWidget(parent), TraceItemView(parentView) +{ + _showCallers = showCallers; + + QStringList headerLabels; + headerLabels << tr( "Cost" ) + << tr( "Cost per call" ) + << tr( "Cost 2" ) + << tr( "Cost 2 per call" ) + << tr( "Count" ) + << ((_showCallers) ? tr( "Caller" ) : tr( "Callee" )); + setHeaderLabels(headerLabels); + + // forbid scaling icon pixmaps to smaller size + setIconSize(QSize(99,99)); + setAllColumnsShowFocus(true); + setRootIsDecorated(false); + setUniformRowHeights(true); + // sorting will be enabled after refresh() + sortByColumn(0, Qt::DescendingOrder); + setMinimumHeight(50); + + this->setWhatsThis( whatsThis() ); + + connect( this, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + SLOT( selectedSlot(QTreeWidgetItem*,QTreeWidgetItem*) ) ); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &) ), + SLOT(context(const QPoint &))); + + connect(this, + SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(activatedSlot(QTreeWidgetItem*,int))); + + connect(header(), SIGNAL(sectionClicked(int)), + this, SLOT(headerClicked(int))); +} + +QString CallView::whatsThis() const +{ + return _showCallers ? + tr( "List of direct Callers" + "

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.

" + "

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.

" + "

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.

") : + tr( "List of direct Callees" + "

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.

" + "

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.

"); +} + + +void CallView::context(const QPoint & p) +{ + QMenu popup; + + // p is in local coordinates + int col = columnAt(p.x()); + QTreeWidgetItem* i = itemAt(p); + TraceCall* c = i ? ((CallItem*) i)->call() : 0; + TraceFunction *f = 0, *cycle = 0; + + QAction* activateFunctionAction = 0; + QAction* activateCycleAction = 0; + if (c) { + QString name = _showCallers ? c->callerName(true) : c->calledName(true); + f = _showCallers ? c->caller(true) : c->called(true); + cycle = f->cycle(); + + QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(name)); + activateFunctionAction = popup.addAction(menuText); + + if (cycle) { + name = GlobalConfig::shortenSymbol(cycle->prettyName()); + QString menuText = tr("Go to '%1'").arg(name); + activateCycleAction = popup.addAction(menuText); + } + + popup.addSeparator(); + } + + // add menu items to select event type if column displays cost for a type + if (col < 4) { + addEventTypeMenu(&popup); + popup.addSeparator(); + } + addGoMenu(&popup); + + QAction* a = popup.exec(mapToGlobal(p + QPoint(0,header()->height()))); + if (a == activateFunctionAction) + TraceItemView::activated(f); + else if (a == activateCycleAction) + TraceItemView::activated(cycle); +} + +void CallView::selectedSlot(QTreeWidgetItem * i, QTreeWidgetItem *) +{ + if (!i) return; + TraceCall* c = ((CallItem*) i)->call(); + // Should we skip cycles here? + CostItem* f = _showCallers ? c->caller(false) : c->called(false); + + _selectedItem = f; + selected(f); +} + +void CallView::activatedSlot(QTreeWidgetItem* i,int) +{ + if (!i) return; + + TraceCall* c = ((CallItem*) i)->call(); + // skip cycles: use the context menu to get to the cycle... + CostItem* f = _showCallers ? c->caller(true) : c->called(true); + + TraceItemView::activated(f); +} + +void CallView::headerClicked(int col) +{ + // name columns should be sortable in both ways + if (col == 5) return; + + // all others only descending + sortByColumn(col, Qt::DescendingOrder); +} + +void CallView::keyPressEvent(QKeyEvent* event) +{ + QTreeWidgetItem *item = currentItem(); + if (item && ((event->key() == Qt::Key_Return) || + (event->key() == Qt::Key_Space))) + { + TraceCall* c = ((CallItem*) item)->call(); + CostItem* f = _showCallers ? c->caller(false) : c->called(false); + + TraceItemView::activated(f); + } + QTreeView::keyPressEvent(event); +} + +CostItem* CallView::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CallView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + CallItem* ci = (CallItem*) currentItem(); + TraceCall* c; + CostItem* ti; + if (ci) { + c = ci->call(); + ti = _showCallers ? c->caller() : c->called(); + if (ti == _selectedItem) return; + } + + QTreeWidgetItem *item = 0; + for (int i=0; icall(); + ti = _showCallers ? c->caller() : c->called(); + if (ti == _selectedItem) { + scrollToItem(item); + setCurrentItem(item); + break; + } + } + if (!item && ci) clearSelection(); + return; + } + + if (changeType == groupTypeChanged) { + QTreeWidgetItem *item; + for (int i=0; iupdateGroup(); + } + return; + } + + refresh(); +} + +void CallView::refresh() +{ + clear(); + setColumnWidth(1, _eventType2 ? 50:0); + + if (_eventType) { + headerItem()->setText(0, _eventType->name()); + headerItem()->setText(1, tr("%1 per call").arg(_eventType->name())); + } + if (_eventType2) { + headerItem()->setText(2, _eventType2->name()); + headerItem()->setText(3, tr("%1 per call").arg(_eventType2->name())); + } + + if (!_data || !_activeItem) return; + + TraceFunction* f = activeFunction(); + if (!f) return; + + // In the call lists, we skip cycles to show the real call relations + TraceCallList l = _showCallers ? f->callers(true) : f->callings(true); + + QList items; + foreach(TraceCall* call, l) + if (call->subCost(_eventType)>0) + items.append(new CallItem(this, 0, call)); + + // when inserting, switch off sorting for performance reason + setSortingEnabled(false); + addTopLevelItems(items); + setSortingEnabled(true); + // enabling sorting switches on the indicator, but we want it off + header()->setSortIndicatorShown(false); + // resize to content now (section size still can be interactively changed) + header()->resizeSections(QHeaderView::ResizeToContents); + + if (!_eventType2) { + setColumnWidth(2, 0); + setColumnWidth(3, 0); + } +} + +#include "moc_callview.cpp" diff --git a/kcachegrind/libviews/callview.h b/kcachegrind/libviews/callview.h new file mode 100644 index 00000000..b1b3a05e --- /dev/null +++ b/kcachegrind/libviews/callview.h @@ -0,0 +1,59 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2009 Josef Weidendorfer + + 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. +*/ + +/* + * Call Views + */ + +#ifndef CALLVIEW_H +#define CALLVIEW_H + +#include +#include "tracedata.h" +#include "traceitemview.h" + +class CallView: public QTreeWidget, public TraceItemView +{ + Q_OBJECT + +public: + CallView(bool showCallers, TraceItemView* parentView, + QWidget* parent=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + bool showCallers() const { return _showCallers; } + +protected slots: + void context(const QPoint &); + void selectedSlot(QTreeWidgetItem*, QTreeWidgetItem*); + void activatedSlot(QTreeWidgetItem*, int); + void headerClicked(int); + +protected: + void keyPressEvent(QKeyEvent* event); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); + + bool _showCallers; +}; + +#endif diff --git a/kcachegrind/libviews/costlistitem.cpp b/kcachegrind/libviews/costlistitem.cpp new file mode 100644 index 00000000..a44204bf --- /dev/null +++ b/kcachegrind/libviews/costlistitem.cpp @@ -0,0 +1,130 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2011 Josef Weidendorfer + + 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. +*/ + +#include "costlistitem.h" + +#include + +#include + +#include "listutils.h" +#include "coverage.h" +#include "globalguiconfig.h" + + +// CostListItem + + +CostListItem::CostListItem(QTreeWidget* parent, TraceCostItem* costItem, + EventType* et, int size) + :QTreeWidgetItem(parent) +{ + _groupSize = size; + _skipped = 0; + _costItem = costItem; + setEventType(et); + + setTextAlignment(0, Qt::AlignRight); + + if (costItem) { + updateName(); + setIcon(1, colorPixmap(10, 10, + GlobalGUIConfig::groupColor(_costItem))); + } +} + +CostListItem::CostListItem(QTreeWidget* parent, int skipped, + TraceCostItem* costItem, EventType* et) + :QTreeWidgetItem(parent) +{ + _skipped = skipped; + _costItem = costItem; + setEventType(et); + + setTextAlignment(0, Qt::AlignRight); + + setText(1, QObject::tr("(%n item(s) skipped)", "", _skipped)); +} + +void CostListItem::setEventType(EventType* et) +{ + _eventType = et; + 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(_eventType); + if (total == 0.0) { + setText(0, QString("---")); + setIcon(0, QPixmap()); + return; + } + + _pure = _costItem->subCost(_eventType); + double pure = 100.0 * _pure / total; + QString str; + if (GlobalConfig::showPercentage()) + str = QString("%1").arg(pure, 0, 'f', GlobalConfig::percentPrecision()); + else + str = _costItem->prettySubCost(_eventType); + + if (_skipped) { + // special handling for skip entries... + setText(0, QString("< %1").arg(str)); + return; + } + + setText(0, str); + setIcon(0, costPixmap(_eventType, _costItem, total, false)); +} + +bool CostListItem::operator< ( const QTreeWidgetItem & other ) const +{ + const CostListItem* fi1 = this; + const CostListItem* fi2 = (CostListItem*) &other; + int col = treeWidget()->sortColumn(); + + // a skip entry is always sorted last + if (fi1->_skipped) return true; + if (fi2->_skipped) return false; + + if (col==0) + return (fi1->_pure < fi2->_pure); + + return QTreeWidgetItem::operator <(other); +} diff --git a/kcachegrind/libviews/costlistitem.h b/kcachegrind/libviews/costlistitem.h new file mode 100644 index 00000000..cdaf0e20 --- /dev/null +++ b/kcachegrind/libviews/costlistitem.h @@ -0,0 +1,53 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#ifndef COSTLISTITEM_H +#define COSTLISTITEM_H + +#include + +#include "tracedata.h" + +class CostListItem: public QTreeWidgetItem +{ +public: + CostListItem(QTreeWidget* parent, TraceCostItem* cost, + EventType* et, int size = -1); + // entry with multiple skipped items + CostListItem(QTreeWidget* parent, int skipped, TraceCostItem* cost, + EventType* et); + + bool operator< ( const QTreeWidgetItem & other ) const; + TraceCostItem* costItem() { return (_skipped) ? 0 : _costItem; } + void setEventType(EventType* et); + void update(); + void setSize(int s); + +private: + void updateName(); + + SubCost _pure; + EventType* _eventType; + 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/libviews/coverageitem.cpp b/kcachegrind/libviews/coverageitem.cpp new file mode 100644 index 00000000..7f3b3097 --- /dev/null +++ b/kcachegrind/libviews/coverageitem.cpp @@ -0,0 +1,338 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of coverage view. + */ + +#include "coverageitem.h" + + +#include "globalguiconfig.h" +#include "listutils.h" +#include "coverage.h" + + +// CallerCoverageItem + + +CallerCoverageItem::CallerCoverageItem(QTreeWidget* parent, Coverage* c, + TraceFunction* base, + EventType* ct, + ProfileContext::Type gt) + : QTreeWidgetItem(parent) +{ + _skipped = 0; + _coverage = c; + _function = c->function(); + _base = base; + _groupType = ProfileContext::InvalidType; + + setText(3, _function->prettyNameWithLocation()); + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + setCostType(ct); + setGroupType(gt); +} + +CallerCoverageItem::CallerCoverageItem(QTreeWidget* parent, int skipped, Coverage* c, + TraceFunction* base, + EventType* ct, + ProfileContext::Type gt) + : QTreeWidgetItem(parent) +{ + _skipped = skipped; + _coverage = c; + _function = c->function(); + _base = base; + _groupType = ProfileContext::InvalidType; + + setText(3, QObject::tr("(%n function(s) skipped)", "", _skipped)); + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + setCostType(ct); + setGroupType(gt); +} + +void CallerCoverageItem::setGroupType(ProfileContext::Type gt) +{ + if (_skipped) return; + if (_groupType == gt) return; + _groupType = gt; + + QColor c = GlobalGUIConfig::functionColor(_groupType, _function); + setIcon(3, colorPixmap(10, 10, c)); +} + +void CallerCoverageItem::setCostType(EventType* ct) +{ + _costType = ct; + update(); +} + +void CallerCoverageItem::update() +{ + if (!_coverage) { + setText(0, QString()); + setText(1, QString()); + return; + } + + _pSum = 100.0 * _coverage->inclusive(); + SubCost realSum = _base->inclusive()->subCost(_costType); + _sum = SubCost(realSum * _coverage->inclusive()); + QString str; + if (GlobalConfig::showPercentage()) + str = QString("%1").arg(_pSum, 0, 'f', GlobalConfig::percentPrecision()); + else + str = _sum.pretty(); + + if (_skipped) { + setText(0, QString("< %1").arg(str)); + return; + } + + setText(0, str); + setIcon(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); +} + + +bool CallerCoverageItem::operator<( const QTreeWidgetItem & other ) const +{ + const CallerCoverageItem* ci1 = this; + const CallerCoverageItem* ci2 = (CallerCoverageItem*) &other; + int col = treeWidget()->sortColumn(); + + // a skip entry is always sorted last + if (ci1->_skipped) return true; + if (ci2->_skipped) return false; + + if (col==0) { + if (ci1->_pSum < ci2->_pSum) return true; + if (ci1->_pSum > ci2->_pSum) return false; + + // for same percentage (e.g. all 100%), use distance info + return ci1->_distance < ci2->_distance; + } + + if (col==1) { + return ci1->_distance < ci2->_distance; + } + + if (col==2) { + return ci1->_cc < ci2->_cc; + } + + return QTreeWidgetItem::operator <(other); +} + + + +// CalleeCoverageItem + +CalleeCoverageItem::CalleeCoverageItem(QTreeWidget* parent, Coverage* c, + TraceFunction* base, + EventType* ct, + ProfileContext::Type gt) + : QTreeWidgetItem(parent) +{ + _skipped = 0; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = ProfileContext::InvalidType; + + if ( _function ) + setText(4, _function->prettyNameWithLocation()); + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + setTextAlignment(3, Qt::AlignRight); + + setCostType(ct); + setGroupType(gt); +} + +CalleeCoverageItem::CalleeCoverageItem(QTreeWidget* parent, int skipped, Coverage* c, + TraceFunction* base, + EventType* ct, + ProfileContext::Type gt) + : QTreeWidgetItem(parent) +{ + _skipped = skipped; + _coverage = c; + _function = c ? c->function() : 0; + _base = base; + _groupType = ProfileContext::InvalidType; + + setText(4, QObject::tr("(%n function(s) skipped)", "", _skipped)); + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + setTextAlignment(3, Qt::AlignRight); + + setCostType(ct); + setGroupType(gt); +} + +void CalleeCoverageItem::setGroupType(ProfileContext::Type gt) +{ + if (_skipped) return; + if (_groupType == gt) return; + _groupType = gt; + + QColor c = GlobalGUIConfig::functionColor(_groupType, _function); + setIcon(4, colorPixmap(10, 10, c)); +} + +void CalleeCoverageItem::setCostType(EventType* ct) +{ + _costType = ct; + update(); +} + +void CalleeCoverageItem::update() +{ + if (!_coverage) { + setText(0, QString()); + setText(1, QString()); + setText(2, QString()); + 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 (GlobalConfig::showPercentage()) + str = QString("%1").arg(_pSum, 0, 'f', GlobalConfig::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 (GlobalConfig::showPercentage()) { + setText(1, QString("%1") + .arg(_pSelf, 0, 'f', GlobalConfig::percentPrecision())); + } + else { + setText(1, _self.pretty()); + } + + setIcon(0, partitionPixmap(25, 10, _coverage->inclusiveHistogram(), 0, + Coverage::maxHistogramDepth, false)); + setIcon(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); +} + + +bool CalleeCoverageItem::operator<( const QTreeWidgetItem & other ) const +{ + CalleeCoverageItem* ci = (CalleeCoverageItem*) &other; + int col = treeWidget()->sortColumn(); + // a skip entry is always sorted last + if (_skipped) return true; + if (ci->_skipped) return false; + + if (col==0) { + if (_pSum < ci->_pSum) return true; + if (_pSum > ci->_pSum) return false; + + // for same percentage (e.g. all 100%), use distance info + return _distance < ci->_distance; + + } + + if (col==1) { + if (_pSelf < ci->_pSelf) return true; + if (_pSelf > ci->_pSelf) return false; + + // for same percentage (e.g. all 100%), use distance info + return _distance < ci->_distance; + } + + if (col==2) { + // we want to sort the distance in contra direction to costs + return _distance < ci->_distance; + } + + if (col==3) { + return _cc < ci->_cc; + } + return QTreeWidgetItem::operator <(other); +} + + diff --git a/kcachegrind/libviews/coverageitem.h b/kcachegrind/libviews/coverageitem.h new file mode 100644 index 00000000..700f7047 --- /dev/null +++ b/kcachegrind/libviews/coverageitem.h @@ -0,0 +1,86 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of coverage view. + */ + +#ifndef COVERAGEITEM_H +#define COVERAGEITEM_H + +#include +#include "tracedata.h" + +class Coverage; + +class CallerCoverageItem: public QTreeWidgetItem +{ +public: + CallerCoverageItem(QTreeWidget* parent, Coverage* c, + TraceFunction* base, + EventType* ct, ProfileContext::Type gt); + CallerCoverageItem(QTreeWidget* parent, int skipped, Coverage* c, + TraceFunction* base, + EventType* ct, ProfileContext::Type gt); + + bool operator< ( const QTreeWidgetItem & other ) const; + TraceFunction* function() { return (_skipped) ? 0 : _function; } + void setCostType(EventType* ct); + void setGroupType(ProfileContext::Type); + void update(); + +private: + float _pSum; + SubCost _sum; + EventType* _costType; + ProfileContext::Type _groupType; + SubCost _cc; + int _distance, _skipped; + TraceFunction *_function, *_base; + Coverage* _coverage; +}; + + +class CalleeCoverageItem: public QTreeWidgetItem +{ +public: + CalleeCoverageItem(QTreeWidget* parent, Coverage* c, + TraceFunction* base, + EventType* ct, ProfileContext::Type gt); + CalleeCoverageItem(QTreeWidget* parent, int skipped, Coverage* c, + TraceFunction* base, + EventType* ct, ProfileContext::Type gt); + + bool operator< ( const QTreeWidgetItem & other ) const; + TraceFunction* function() { return (_skipped) ? 0 : _function; } + void setCostType(EventType* ct); + void setGroupType(ProfileContext::Type); + void update(); + +private: + float _pSum, _pSelf; + SubCost _sum, _self; + EventType* _costType; + ProfileContext::Type _groupType; + SubCost _cc; + int _distance, _skipped; + TraceFunction *_function, *_base; + Coverage* _coverage; +}; + +#endif diff --git a/kcachegrind/libviews/coverageview.cpp b/kcachegrind/libviews/coverageview.cpp new file mode 100644 index 00000000..16a2e6b7 --- /dev/null +++ b/kcachegrind/libviews/coverageview.cpp @@ -0,0 +1,369 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Coverage Views + */ + + +#include "coverageview.h" + +#include +#include +#include +#include + +#include "globalconfig.h" +#include "coverageitem.h" +#include "coverage.h" + + +// +// CoverageView +// + + +CoverageView::CoverageView(bool showCallers, TraceItemView* parentView, QWidget* parent) + : QTreeWidget(parent), TraceItemView(parentView) +{ + _showCallers = showCallers; + + QStringList labels; + labels << tr( "Incl." ); + if (_showCallers) { + setColumnCount(4); + labels << tr( "Distance" ), + labels << tr( "Called" ), + labels << tr( "Caller" ); + } + else { + setColumnCount(5); + labels << tr( "Self" ), + labels << tr( "Distance" ), + labels << tr( "Calling" ), + labels << tr( "Callee" ); + } + setHeaderLabels(labels); + + // forbid scaling icon pixmaps to smaller size + setIconSize(QSize(99,99)); + setAllColumnsShowFocus(true); + setRootIsDecorated(false); + setUniformRowHeights(true); + // sorting will be enabled after refresh() + sortByColumn(0, Qt::DescendingOrder); + setMinimumHeight(50); + + this->setWhatsThis( whatsThis() ); + + connect( this, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + SLOT( selectedSlot(QTreeWidgetItem*,QTreeWidgetItem*) ) ); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &) ), + SLOT(context(const QPoint &))); + + connect(this, + SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(activatedSlot(QTreeWidgetItem*,int))); + + connect(header(), SIGNAL(sectionClicked(int)), + this, SLOT(headerClicked(int))); +} + +QString CoverageView::whatsThis() const +{ + return _showCallers ? + tr( "List of all Callers" + "

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 Distance (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).

" + + "

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.

" + + "

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.

" + + "

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.

") : + + tr( "List of all Callees" + "

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 Distance (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).

" + + "

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.

" + + "

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.

" + + "

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.

"); +} + +void CoverageView::context(const QPoint & p) +{ + int c = columnAt(p.x()); + QTreeWidgetItem* i = itemAt(p); + QMenu popup; + + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + QAction* activateFunctionAction = 0; + if (f) { + QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(f->prettyName())); + activateFunctionAction = popup.addAction(menuText); + popup.addSeparator(); + } + + if ((c == 0) || (!_showCallers && c == 1)) { + addEventTypeMenu(&popup, false); + popup.addSeparator(); + } + addGoMenu(&popup); + + QAction* a = popup.exec(mapToGlobal(p + QPoint(0,header()->height()))); + if (a == activateFunctionAction) + TraceItemView::activated(f); +} + +void CoverageView::selectedSlot(QTreeWidgetItem* i, QTreeWidgetItem*) +{ + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + if (f) { + _selectedItem = f; + selected(f); + } +} + +void CoverageView::activatedSlot(QTreeWidgetItem* i, int) +{ + TraceFunction* f = 0; + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + + if (f) TraceItemView::activated(f); +} + +void CoverageView::headerClicked(int col) +{ + // distance and name columns should be sortable in both ways + if (_showCallers) { + if ((col == 1) || (col==3)) return; + } + else { + if ((col == 2) || (col==4)) return; + } + + // all others only descending + sortByColumn(col, Qt::DescendingOrder); +} + +void CoverageView::keyPressEvent(QKeyEvent* event) +{ + QTreeWidgetItem *item = currentItem(); + if (item && ((event->key() == Qt::Key_Return) || + (event->key() == Qt::Key_Space))) + { + TraceFunction* f; + f = _showCallers ? + ((CallerCoverageItem*)item)->function() : + ((CalleeCoverageItem*)item)->function(); + TraceItemView::activated(f); + } + QTreeView::keyPressEvent(event); +} + +CostItem* CoverageView::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +void CoverageView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + TraceFunction* f = 0; + QTreeWidgetItem* i = currentItem(); + if (i) { + f = _showCallers ? + ((CallerCoverageItem*)i)->function() : + ((CalleeCoverageItem*)i)->function(); + } + if (f == _selectedItem) return; + + QTreeWidgetItem *item; + for (int i=0; itopLevelItem(i); + f = _showCallers ? + ((CallerCoverageItem*)item)->function() : + ((CalleeCoverageItem*)item)->function(); + if (f == _selectedItem) { + scrollToItem(item); + setCurrentItem(item); + break; + } + } + return; + } + + if (changeType == groupTypeChanged) { + QTreeWidgetItem *item; + for (int i=0; isetGroupType(_groupType); + else + ((CalleeCoverageItem*)item)->setGroupType(_groupType); + } + return; + } + + refresh(); +} + +void CoverageView::refresh() +{ + clear(); + + if (!_data || !_activeItem) return; + + ProfileContext::Type t = _activeItem->type(); + TraceFunction* f = 0; + if (t == ProfileContext::Function) f = (TraceFunction*) _activeItem; + if (t == ProfileContext::FunctionCycle) f = (TraceFunction*) _activeItem; + if (!f) return; + + + + _hc.clear(GlobalConfig::maxListCount()); + SubCost realSum = f->inclusive()->subCost(_eventType); + + TraceFunctionList l; + if (_showCallers) + l = Coverage::coverage(f, Coverage::Caller, _eventType); + else + l = Coverage::coverage(f, Coverage::Called, _eventType); + + foreach(TraceFunction* f2, l) { + Coverage* c = (Coverage*) f2->association(Coverage::Rtti); + if (c && (c->inclusive()>0.0)) + _hc.addCost(f2, SubCost(realSum * c->inclusive())); + } + + QList items; + QTreeWidgetItem* item; + TraceFunction* ff; + for(int i=0;i<_hc.realCount();i++) { + ff = (TraceFunction*) _hc[i]; + Coverage* c = (Coverage*) ff->association(Coverage::Rtti); + if (_showCallers) + item = new CallerCoverageItem(0, c, f, _eventType, _groupType); + else + item = new CalleeCoverageItem(0, c, f, _eventType, _groupType); + items.append(item); + } + if (_hc.hasMore()) { + // a placeholder for all the functions skipped ... + ff = (TraceFunction*) _hc[_hc.maxSize()-1]; + Coverage* c = (Coverage*) ff->association(Coverage::Rtti); + if (_showCallers) + item = new CallerCoverageItem(0, _hc.count() - _hc.maxSize(), + c, f, _eventType, _groupType); + else + item = new CalleeCoverageItem(0, _hc.count() - _hc.maxSize(), + c, f, _eventType, _groupType); + items.append(item); + } + + // when inserting, switch off sorting for performance reason + setSortingEnabled(false); + addTopLevelItems(items); + setSortingEnabled(true); + // enabling sorting switches on the indicator, but we want it off + header()->setSortIndicatorShown(false); + // resize to content now (section size still can be interactively changed) + header()->resizeSections(QHeaderView::ResizeToContents); +} + +#include "moc_coverageview.cpp" diff --git a/kcachegrind/libviews/coverageview.h b/kcachegrind/libviews/coverageview.h new file mode 100644 index 00000000..da862d32 --- /dev/null +++ b/kcachegrind/libviews/coverageview.h @@ -0,0 +1,61 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2009 Josef Weidendorfer + + 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. +*/ + +/* + * Coverage Views + */ + +#ifndef COVERAGEVIEW_H +#define COVERAGEVIEW_H + +#include + +#include "tracedata.h" +#include "traceitemview.h" +#include "listutils.h" + +class CoverageView: public QTreeWidget, public TraceItemView +{ + Q_OBJECT + +public: + CoverageView(bool showCallers, TraceItemView* parentView, + QWidget* parent = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + +protected slots: + void context(const QPoint &); + void selectedSlot(QTreeWidgetItem*, QTreeWidgetItem*); + void activatedSlot(QTreeWidgetItem*, int); + void headerClicked(int); + +protected: + void keyPressEvent(QKeyEvent* event); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); + + HighestCostList _hc; + bool _showCallers; +}; + +#endif diff --git a/kcachegrind/libviews/eventtypeitem.cpp b/kcachegrind/libviews/eventtypeitem.cpp new file mode 100644 index 00000000..5bb97ce6 --- /dev/null +++ b/kcachegrind/libviews/eventtypeitem.cpp @@ -0,0 +1,156 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of event type view. + */ + +#include "eventtypeitem.h" + +#include + +#include "globalconfig.h" +#include "listutils.h" + + +// EventTypeItem + + +EventTypeItem::EventTypeItem(TraceCostItem* costItem, + EventType* ct, ProfileContext::Type gt) +{ + _costItem = costItem; + _eventType = ct; + _groupType = gt; + + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + setTextAlignment(3, Qt::AlignRight); + setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + if (ct) { + setText(0, ct->longName()); + setText(3, ct->name()); + setText(5, ct->parsedFormula()); + QString formula = ct->formula(); + if (!ct->isReal()) { + setText(4, "="); + // we have a virtual type: allow editing + // FIXME: How to enable this only for columns 0,3,5 ?! + setFlags(flags() | Qt::ItemIsEditable); + } + } + else { + setText(0, QObject::tr("Unknown Type")); + } + update(); +} + +void EventTypeItem::setGroupType(ProfileContext::Type gt) +{ + if (_groupType == gt) return; + + _groupType = gt; + update(); +} + +void EventTypeItem::update() +{ + TraceData* d = _costItem ? _costItem->data() : 0; + double total = d ? ((double)d->subCost(_eventType)) : 0.0; + + if (total == 0.0) { + setText(1, "-"); + setIcon(1, QIcon()); + setText(2, "-"); + setIcon(2, QIcon()); + return; + } + + TraceFunction* f = (_costItem && _costItem->type()==ProfileContext::Function) ? + (TraceFunction*)_costItem : 0; + + ProfileCostArray* selfTotalCost = f ? f->data() : d; + if (f && GlobalConfig::showExpanded()) { + ProfileCostArray* parent = 0; + switch(_groupType) { + case ProfileContext::Object: parent = f->object(); break; + case ProfileContext::Class: parent = f->cls(); break; + case ProfileContext::File: parent = f->file(); break; + case ProfileContext::FunctionCycle: parent = f->cycle(); break; + default: break; + } + if (parent) selfTotalCost = parent; + } + if (_costItem && _costItem->type()==ProfileContext::FunctionCycle) { + f = (TraceFunction*)_costItem; + selfTotalCost = f->data(); + } + + double selfTotal = selfTotalCost->subCost(_eventType); + + // for all cost items there is a self cost + _pure = _costItem ? _costItem->subCost(_eventType) : SubCost(0); + double pure = 100.0 * _pure / selfTotal; + if (GlobalConfig::showPercentage()) { + setText(2, QString("%1") + .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); + } + else if (_costItem) + setText(2, _costItem->prettySubCost(_eventType)); + + setIcon(2, QIcon(costPixmap(_eventType, _costItem, selfTotal, false))); + + if (!f) { + setText(1, "-"); + setIcon(1, QIcon()); + return; + } + + _sum = f->inclusive()->subCost(_eventType); + double sum = 100.0 * _sum / total; + if (GlobalConfig::showPercentage()) { + setText(1, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + } + else + setText(1, _sum.pretty()); + + setIcon(1, QIcon(costPixmap(_eventType, f->inclusive(), total, false))); +} + +bool EventTypeItem::operator<(const QTreeWidgetItem &other) const +{ + int col = treeWidget()->sortColumn(); + EventTypeItem* o = (EventTypeItem*) &other; + if (col==0) + return _sum < o->_sum; + if (col==1) + return _pure < o->_pure; + + return QTreeWidgetItem::operator<(other); +} + +QVariant EventTypeItem::data(int column, int role) const +{ + if ((column == 5) && (role == Qt::EditRole)) + return QVariant(_eventType->formula()); + return QTreeWidgetItem::data(column, role); +} + + diff --git a/kcachegrind/libviews/eventtypeitem.h b/kcachegrind/libviews/eventtypeitem.h new file mode 100644 index 00000000..75c2e417 --- /dev/null +++ b/kcachegrind/libviews/eventtypeitem.h @@ -0,0 +1,51 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of event type view. + */ + +#ifndef EVENTTYPEITEM_H +#define EVENTTYPEITEM_H + +#include + +#include "tracedata.h" + +class EventTypeItem: public QTreeWidgetItem +{ +public: + EventTypeItem(TraceCostItem* costItem, + EventType* ct, ProfileContext::Type gt); + + bool operator<(const QTreeWidgetItem &other) const; + void setGroupType(ProfileContext::Type); + TraceCostItem* costItem() { return _costItem; } + EventType* eventType() { return _eventType; } + void update(); + + QVariant data(int column, int role) const; + +private: + SubCost _sum, _pure; + EventType* _eventType; + TraceCostItem* _costItem; + ProfileContext::Type _groupType; +}; + +#endif // EVENTTYPEITEM_H diff --git a/kcachegrind/libviews/eventtypeview.cpp b/kcachegrind/libviews/eventtypeview.cpp new file mode 100644 index 00000000..87f821ee --- /dev/null +++ b/kcachegrind/libviews/eventtypeview.cpp @@ -0,0 +1,338 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Event Type View + */ + + +#include "eventtypeview.h" + +#include +#include +#include + +#include "eventtypeitem.h" +#include "toplevelbase.h" + + +// +// EventTypeView +// + +EventTypeView::EventTypeView(TraceItemView* parentView, + QWidget* parent, const char* name) + : QTreeWidget(parent), TraceItemView(parentView) +{ + setObjectName(name); + // forbid scaling icon pixmaps to smaller size + setIconSize(QSize(99,99)); + setColumnCount(6); + QStringList labels; + labels << tr( "Event Type" ) + << tr( "Incl." ) + << tr( "Self" ) + << tr( "Short" ) + << QString() + << tr( "Formula" ); + setHeaderLabels(labels); + // reduce minimum width for '=' column + header()->setMinimumSectionSize(10); + + setRootIsDecorated(false); + setSortingEnabled(false); + setAllColumnsShowFocus(true); + setMinimumHeight(50); + + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &)), + SLOT(context(const QPoint &))); + + // FIXME: Endless jumping among 2 types possible! + connect( this, + SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + SLOT(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)) ); + + connect(this, + SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(itemDoubleClicked(QTreeWidgetItem*,int))); + + connect(this, + SIGNAL(itemChanged(QTreeWidgetItem*,int)), + SLOT(itemChanged(QTreeWidgetItem*,int))); + + setWhatsThis( whatsThis() ); +} + +QString EventTypeView::whatsThis() const +{ + return tr( "Cost Types List" + "

This list shows all cost types available " + "and what the self/inclusive cost of the " + "current selected function is for that cost type.

" + "

By choosing a cost type from the list, " + "you change the cost type of costs shown " + "all over KCachegrind to be the selected one.

"); +} + + +void EventTypeView::context(const QPoint & p) +{ + QMenu popup; + + QTreeWidgetItem* i = itemAt(p); + EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; + + QAction* selectType2Action = 0; + QAction* hideType2Action = 0; + if (ct) + selectType2Action = popup.addAction(tr("Set as Secondary Event Type")); + if (_eventType2) + hideType2Action = popup.addAction(tr("Hide Secondary Event Type")); + if (!popup.isEmpty()) + popup.addSeparator(); + + QAction* editLongNameAction = 0; + QAction* editShortNameAction = 0; + QAction* editFormulaAction = 0; + QAction* removeTypeAction = 0; + if (ct && !ct->isReal()) { + editLongNameAction = popup.addAction(tr("Edit Long Name")); + editShortNameAction = popup.addAction(tr("Edit Short Name")); + editFormulaAction = popup.addAction(tr("Edit Formula")); + removeTypeAction = popup.addAction(tr("Remove")); + popup.addSeparator(); + } + + addGoMenu(&popup); + + QAction* newTypeAction = 0; + if( _data) { + popup.addSeparator(); + newTypeAction = popup.addAction(tr("New Event Type...")); + } + + QAction* a = popup.exec(viewport()->mapToGlobal(p)); + if (a == hideType2Action) selectedEventType2(0); + else if (a == selectType2Action) selectedEventType2(ct); + else if (a == editLongNameAction) editItem(i, 0); + else if (a == editShortNameAction) editItem(i, 3); + else if (a == editFormulaAction) editItem(i, 5); + else if (a == removeTypeAction) { + + // search for a previous type + EventType* prev = 0, *ct = 0; + EventTypeSet* m = _data->eventTypes(); + for (int i=0;irealCount();i++) { + ct = m->realType(i); + if (ct) prev = ct; + } + for (int i=0;iderivedCount();i++) { + ct = m->derivedType(i); + if (ct == _eventType) break; + if (ct) prev = ct; + } + + if (_data->eventTypes()->remove(ct)) { + // select previous cost type + selectedEventType(prev); + if (_eventType2 == ct) + selectedEventType2(prev); + refresh(); + } + } + else if (a == newTypeAction) { + int i = 1; + while(1) { + if (!EventType::knownDerivedType(tr("New%1").arg(i))) + break; + i++; + } + // add same new cost type to this set and to known types + QString shortName = tr("New%1").arg(i); + QString longName = tr("New Event Type %1").arg(i); + EventType* et; + et = new EventType(shortName, longName); + et->setFormula(QString()); // event is derived + EventType::add(et); + // EventType::add() took ownership, need new object + et = new EventType(shortName, longName); + et->setFormula(QString()); // event is derived + _data->eventTypes()->add(et); + refresh(); + } +} + + +void EventTypeView::currentItemChanged(QTreeWidgetItem* i, QTreeWidgetItem*) +{ + EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; + if (ct) + selectedEventType(ct); +} + +void EventTypeView::itemDoubleClicked(QTreeWidgetItem* i, int) +{ + EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; + if (ct) + selectedEventType2(ct); +} + +CostItem* EventTypeView::canShow(CostItem* i) +{ + if (!i) return 0; + + switch(i->type()) { + case ProfileContext::Object: + case ProfileContext::Class: + case ProfileContext::File: + case ProfileContext::Call: + case ProfileContext::FunctionCycle: + case ProfileContext::Function: + break; + default: + return 0; + } + return i; +} + +void EventTypeView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) return; + + if (changeType == eventType2Changed) return; + + if (changeType == groupTypeChanged) { + for(int i = 0; i < topLevelItemCount(); i++) + ((EventTypeItem*)topLevelItem(i))->setGroupType(_groupType); + + return; + } + + if (changeType == eventTypeChanged) { + for(int i = 0; i < topLevelItemCount(); i++) { + EventTypeItem* item = (EventTypeItem*)topLevelItem(i); + if ( item->eventType() == _eventType) { + setCurrentItem(item); + scrollToItem(item); + break; + } + } + + return; + } + + if (changeType == partsChanged) { + for(int i = 0; i < topLevelItemCount(); i++) + ((EventTypeItem*)topLevelItem(i))->update(); + + resizeColumnToContents(1); + resizeColumnToContents(2); + return; + } + + + refresh(); +} + +void EventTypeView::refresh() +{ + clear(); + setColumnWidth(1, 50); + setColumnWidth(2, 50); + + if (!_data || !_activeItem) return; + switch(_activeItem->type()) { + case ProfileContext::Object: + case ProfileContext::Class: + case ProfileContext::File: + case ProfileContext::FunctionCycle: + case ProfileContext::Function: + break; + default: + return; + } + TraceCostItem* c = (TraceCostItem*) _activeItem; + + EventType* ct =0; + QTreeWidgetItem* item = 0; + QTreeWidgetItem* selected = 0; + QList items; + QString sumStr, pureStr; + + EventTypeSet* m = _data->eventTypes(); + for (int i=0; irealCount();i++) { + ct = m->realType(i); + item = new EventTypeItem(c, ct, _groupType); + if (ct == _eventType) selected = item; + items.append(item); + } + for (int i=0; iderivedCount();i++) { + ct = m->derivedType(i); + if (!ct) continue; + item = new EventTypeItem(c, ct, _groupType); + if (ct == _eventType) selected = item; + items.append(item); + } + insertTopLevelItems(0,items); + + if (selected) { + setCurrentItem(selected); + scrollToItem(selected); + } + + for(int c = 0; c<6; c++) + resizeColumnToContents(c); +} + +void EventTypeView::itemChanged(QTreeWidgetItem* item, int c) +{ + EventType* ct = item ? ((EventTypeItem*) item)->eventType() : 0; + if (!ct || ct->isReal()) return; + + // search for matching known Type + int knownCount = EventType::knownTypeCount(); + EventType* known = 0; + for (int i=0; iname() == ct->name()) break; + } + + QString t = item->text(c); + 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 "moc_eventtypeview.cpp" diff --git a/kcachegrind/libviews/eventtypeview.h b/kcachegrind/libviews/eventtypeview.h new file mode 100644 index 00000000..558f5fc7 --- /dev/null +++ b/kcachegrind/libviews/eventtypeview.h @@ -0,0 +1,54 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Event Type View + */ + +#ifndef EVENTTYPEVIEW_H +#define EVENTTYPEVIEW_H + +#include + +#include "tracedata.h" +#include "traceitemview.h" + +class EventTypeView: public QTreeWidget, public TraceItemView +{ + Q_OBJECT + +public: + explicit EventTypeView(TraceItemView* parentView, + QWidget* parent = 0, const char* name = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + +private slots: + void context(const QPoint&); + void currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); + void itemDoubleClicked(QTreeWidgetItem*, int); + void itemChanged(QTreeWidgetItem*, int); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); +}; + +#endif diff --git a/kcachegrind/libviews/functionlistmodel.cpp b/kcachegrind/libviews/functionlistmodel.cpp new file mode 100644 index 00000000..193f588e --- /dev/null +++ b/kcachegrind/libviews/functionlistmodel.cpp @@ -0,0 +1,474 @@ +/* This file is part of KCachegrind. + Copyright (C) 2010 Josef Weidendorfer + + 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. +*/ + +#include "functionlistmodel.h" + +#include "globalguiconfig.h" +#include "listutils.h" + +FunctionListModel::FunctionListModel() + : QAbstractItemModel(0) +{ + _maxCount = 300; + _sortColumn = 0; + _sortOrder = Qt::DescendingOrder; + + _headerData + << tr("Incl.") + << tr("Self") + << tr("Called") + << tr("Function") + << tr("Location"); + + _max0 = _max1 = _max2 = 0; +} + +FunctionListModel::~FunctionListModel() +{} + +int FunctionListModel::columnCount(const QModelIndex& parent) const +{ + return (parent.isValid()) ? 0 : 5; +} + +int FunctionListModel::rowCount(const QModelIndex& parent ) const +{ + if (parent.isValid()) return 0; + + int rowCount = _topList.count(); + // add one more row if functions are skipped + if (_topList.count() < _filteredList.count()) rowCount++; + return rowCount; +} + +TraceFunction* FunctionListModel::function(const QModelIndex& index) +{ + if (!index.isValid()) return 0; + + return (TraceFunction*) index.internalPointer(); +} + +QVariant FunctionListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) return QVariant(); + + // the skipped items entry + if ( (_topList.count() < _filteredList.count()) && + (index.row() == _topList.count()) ) { + if( (role != Qt::DisplayRole) || (index.column() != 3)) + return QVariant(); + + return tr("(%1 function(s) skipped)").arg(_filteredList.count() - _topList.count()); + } + + TraceFunction *f = (TraceFunction*) index.internalPointer(); + Q_ASSERT(f != 0); + switch(role) { + case Qt::TextAlignmentRole: + return (index.column()<3) ? Qt::AlignRight : Qt::AlignLeft; + + case Qt::DecorationRole: + switch (index.column()) { + case 0: + return getInclPixmap(f); + case 1: + return getSelfPixmap(f); + case 3: + return getNamePixmap(f); + default: + break; + } + break; + + case Qt::DisplayRole: + switch (index.column()) { + case 0: + return getInclCost(f); + case 1: + return getSelfCost(f); + case 2: + return getCallCount(f); + case 3: + return getName(f); + case 4: + return getLocation(f); + default: + break; + } + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags FunctionListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant FunctionListModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole)) + return QVariant(); + + return _headerData.value(section); +} + +QModelIndex FunctionListModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) return QModelIndex(); + + //the skipped items entry + if ( (_topList.count() < _list.count()) && (row == _topList.count()) ) + return createIndex(row, column); + + return createIndex(row, column, (void*)_topList[row]); +} + +QModelIndex FunctionListModel::indexForFunction(TraceFunction *f, bool add) +{ + if (!f) return QModelIndex(); + + int row = _topList.indexOf(f); + if (row<0) { + // we only add a function from _list matching the filter + if ( !add || + !_filteredList.contains(f) ) return QModelIndex(); + + // find insertion point with current list order + FunctionLessThan lessThan(_sortColumn, _sortOrder, _eventType); + QList::iterator insertPos; + insertPos = qLowerBound(_topList.begin(), _topList.end(), + f, lessThan); + row = insertPos - _topList.begin(); + beginInsertRows(QModelIndex(), row, row); + _topList.insert(row, f); + endInsertRows(); + } + + return createIndex(row, 0, (void*)f); +} + +QModelIndex FunctionListModel::parent(const QModelIndex& /*index*/ ) const +{ + /* only toplevel items */ + return QModelIndex(); +} + +void FunctionListModel::sort(int column, Qt::SortOrder order) +{ + _sortColumn = column; + _sortOrder = order; + computeTopList(); +} + +void FunctionListModel::setFilter(QString filterString) +{ + if (_filterString == filterString) return; + _filterString = filterString; + + _filter = QRegExp(_filterString, Qt::CaseInsensitive, QRegExp::Wildcard); + computeFilteredList(); + computeTopList(); +} + +void FunctionListModel::setEventType(EventType* et) +{ + _eventType = et; + // needed to recalculate max value entries + computeFilteredList(); + computeTopList(); +} + +void FunctionListModel::setMaxCount(int c) +{ + if (_maxCount == c) return; + _maxCount = c; + computeTopList(); +} + +void FunctionListModel::resetModelData(TraceData *data, + TraceCostItem *group, QString filterString, + EventType * eventType) +{ + _eventType = eventType; + + if (!group) { + _list.clear(); + _groupType = ProfileContext::Function; + if (data) { + TraceFunctionMap::iterator i = data->functionMap().begin(); + while (i != data->functionMap().end()) { + _list.append(&(i.value())); + ++i; + } + foreach(TraceFunction* f, data->functionCycles()) + _list.append(f); + } + } + else { + _groupType = group->type(); + switch(_groupType) { + case ProfileContext::Object: + { + TraceObject* o = dynamic_cast(group); + Q_ASSERT(o != 0); + _list = o->functions(); + } + break; + + case ProfileContext::Class: + { + TraceClass* c = dynamic_cast(group); + Q_ASSERT(c != 0); + _list = c->functions(); + } + break; + + case ProfileContext::File: + { + TraceFile* f = dynamic_cast(group); + Q_ASSERT(f != 0); + _list = f->functions(); + } + break; + + case ProfileContext::FunctionCycle: + { + TraceFunctionCycle* c = dynamic_cast(group); + Q_ASSERT(c != 0); + _list = c->members(); + } + break; + + default: + _list.clear(); + break; + } + } + + _filterString = filterString; + _filter = QRegExp(_filterString, Qt::CaseInsensitive, QRegExp::Wildcard); + + computeFilteredList(); + computeTopList(); +} + +void FunctionListModel::computeFilteredList() +{ + FunctionLessThan lessThan0(0, Qt::AscendingOrder, _eventType); + FunctionLessThan lessThan1(1, Qt::AscendingOrder, _eventType); + FunctionLessThan lessThan2(2, Qt::AscendingOrder, _eventType); + + // reset max functions + _max0 = 0; + _max1 = 0; + _max2 = 0; + + _filteredList.clear(); + int index = 0; + foreach(TraceFunction* f, _list) { + if (!_filterString.isEmpty()) + if (_filter.indexIn(f->name()) == -1) continue; + + _filteredList.append(f); + if (!_max0 || lessThan0(_max0, f)) { _max0 = f; } + if (!_max1 || lessThan1(_max1, f)) { _max1 = f; } + if (!_max2 || lessThan2(_max2, f)) { _max2 = f; } + index++; + } +} + +void FunctionListModel::computeTopList() +{ + beginResetModel(); + _topList.clear(); + if (_filteredList.isEmpty()) { + endResetModel(); + return; + } + + FunctionLessThan lessThan(_sortColumn, _sortOrder, _eventType); + qStableSort(_filteredList.begin(), _filteredList.end(), lessThan); + + foreach(TraceFunction* f, _filteredList) { + _topList.append(f); + if (_topList.count() >= _maxCount) break; + } + + // append max entries + QList maxList; + if (_max0 && !_topList.contains(_max0)) maxList.append(_max0); + if (_max1 && !_topList.contains(_max1)) maxList.append(_max1); + if (_max2 && !_topList.contains(_max2)) maxList.append(_max2); + qSort(maxList.begin(), maxList.end(), lessThan); + _topList.append(maxList); + + endResetModel(); +} + +QString FunctionListModel::getName(TraceFunction *f) const +{ + return f->prettyName(); +} + +QPixmap FunctionListModel::getNamePixmap(TraceFunction *f) const +{ + QColor c = GlobalGUIConfig::functionColor(_groupType, f); + return colorPixmap(10, 10, c); +} + +QString FunctionListModel::getLocation(TraceFunction *f) const +{ + return f->prettyLocation(); +} + +QString FunctionListModel::getSelfCost(TraceFunction *f) const +{ + ProfileCostArray* selfCost = f->data(); + if (GlobalConfig::showExpanded()) { + switch(_groupType) { + case ProfileContext::Object: selfCost = f->object(); break; + case ProfileContext::Class: selfCost = f->cls(); break; + case ProfileContext::File: selfCost = f->file(); break; + default: break; + } + } + double selfTotal = selfCost->subCost(_eventType); + if (selfTotal == 0.0) + return "-"; + + // self + SubCost pure = f->subCost(_eventType); + double self = 100.0 * pure / selfTotal; + if (GlobalConfig::showPercentage()) + return QString("%1") + .arg(self, 0, 'f', GlobalConfig::percentPrecision()); + else + return f->prettySubCost(_eventType); +} + +QPixmap FunctionListModel::getSelfPixmap(TraceFunction *f) const +{ + ProfileCostArray* selfCost = f->data(); + if (GlobalConfig::showExpanded()) { + switch(_groupType) { + case ProfileContext::Object: selfCost = f->object(); break; + case ProfileContext::Class: selfCost = f->cls(); break; + case ProfileContext::File: selfCost = f->file(); break; + default: break; + } + } + double selfTotal = selfCost->subCost(_eventType); + if (selfTotal == 0.0) + return QPixmap(); + + return costPixmap(_eventType, f, selfTotal, false); +} + +QString FunctionListModel::getInclCost(TraceFunction *f) const +{ + double inclTotal = f->data()->subCost(_eventType); + if (inclTotal == 0.0) + return "-"; + + SubCost sum = f->inclusive()->subCost(_eventType); + double incl = 100.0 * sum / inclTotal; + if (GlobalConfig::showPercentage()) + return QString("%1") + .arg(incl, 0, 'f', GlobalConfig::percentPrecision()); + else + return f->inclusive()->prettySubCost(_eventType); +} + +QPixmap FunctionListModel::getInclPixmap(TraceFunction *f) const +{ + double inclTotal = f->data()->subCost(_eventType); + if (inclTotal == 0.0) + return QPixmap(); + + return costPixmap(_eventType, f->inclusive(), inclTotal, false); +} + + +QString FunctionListModel::getCallCount(TraceFunction *f) const +{ + QString str; + if (f->calledCount() > 0) + str = f->prettyCalledCount(); + else { + if (f == f->cycle()) + str = QString("-"); + else + str = QString("(0)"); + } + return str; +} + +// +// FunctionListModel::FunctionLessThan +// +bool FunctionListModel::FunctionLessThan::operator()(TraceFunction *left, + TraceFunction *right) +{ + TraceFunction* f1 = left; + TraceFunction* f2 = right; + + // descending: swap arguments + if (_order == Qt::DescendingOrder) { + TraceFunction* temp = f1; + f1 = f2; + f2 = temp; + } + + switch(_column) { + case 0: + { + SubCost sum1 = f1->inclusive()->subCost(_eventType); + SubCost sum2 = f2->inclusive()->subCost(_eventType); + return sum1 < sum2; + } + + case 1: + { + SubCost pure1 = f1->subCost(_eventType); + SubCost pure2 = f2->subCost(_eventType); + return pure1 < pure2; + } + + case 2: + return f1->calledCount() < f2->calledCount(); + + case 3: + return f1->name() < f2->name(); + + case 4: + return f1->object()->name() < f2->object()->name(); + } + + return false; +} + + +#include "moc_functionlistmodel.cpp" diff --git a/kcachegrind/libviews/functionlistmodel.h b/kcachegrind/libviews/functionlistmodel.h new file mode 100644 index 00000000..7ee5bfe5 --- /dev/null +++ b/kcachegrind/libviews/functionlistmodel.h @@ -0,0 +1,115 @@ +/* This file is part of KCachegrind. + Copyright (C) 2010 Josef Weidendorfer + + 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. +*/ + +#ifndef FUNCTIONLISTMODEL_H +#define FUNCTIONLISTMODEL_H + +#include +#include +#include +#include + +#include "tracedata.h" +#include "subcost.h" + + +class FunctionListModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + FunctionListModel(); + ~FunctionListModel(); + + /* Data to show: all functions from + * - which are part of function group + * - whose name contains + */ + void resetModelData(TraceData* data, TraceCostItem* group, QString filter, + EventType* eventType); + + // reimplemented from QAbstractItemModel + QVariant data(const QModelIndex&, int) const; + Qt::ItemFlags flags(const QModelIndex&) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column = 0, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + void sort(int column, Qt::SortOrder order); + + void setFilter(QString filter); + void setEventType(EventType*); + void setMaxCount(int); + + TraceFunction* function(const QModelIndex &index); + // get index of an entry showing a function, optionally adding it if needed + QModelIndex indexForFunction(TraceFunction *f, bool add = false); + + class FunctionLessThan + { + public: + FunctionLessThan(int column, Qt::SortOrder order, EventType* et) + { _column = column; _order = order; _eventType = et; } + + bool operator()(TraceFunction *left, TraceFunction *right); + + private: + int _column; + Qt::SortOrder _order; + EventType* _eventType; + }; + +private: + QString getName(TraceFunction *f) const; + QPixmap getNamePixmap(TraceFunction *f) const; + QString getInclCost(TraceFunction *f) const; + QPixmap getInclPixmap(TraceFunction *f) const; + QString getSelfCost(TraceFunction *f) const; + QPixmap getSelfPixmap(TraceFunction *f) const; + QString getCallCount(TraceFunction *f) const; + QString getLocation(TraceFunction *f) const; + QString getSkippedCost(TraceFunction *f, QPixmap *pixmap) const; + + // compute the list of candidates to show, ignoring order + void computeFilteredList(); + // computes entries to show from candidates using current order + void computeTopList(); + + QList _headerData; + EventType *_eventType; + ProfileContext::Type _groupType; + int _maxCount; + + QList _list; + QList _filteredList; + QList _topList; + + // functions with max values at col.0/1/2 from candidate list: + // these are always shown to have same column widths when resorting + TraceFunction *_max0, *_max1, *_max2; + + int _sortColumn; + Qt::SortOrder _sortOrder; + QRegExp _filter; + QString _filterString; +}; + +#endif diff --git a/kcachegrind/libviews/functionselection.cpp b/kcachegrind/libviews/functionselection.cpp new file mode 100644 index 00000000..def07c05 --- /dev/null +++ b/kcachegrind/libviews/functionselection.cpp @@ -0,0 +1,888 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * For function selection, to be put into a QDockWindow + */ + +#include "functionselection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "traceitemview.h" +#include "stackbrowser.h" +#include "costlistitem.h" +#include "globalconfig.h" +#include "functionlistmodel.h" + + +// custom item delegate for function list +AutoToolTipDelegate::AutoToolTipDelegate(QObject* parent) + : QStyledItemDelegate(parent) +{} + +AutoToolTipDelegate::~AutoToolTipDelegate() +{} + +bool AutoToolTipDelegate::helpEvent(QHelpEvent* e, QAbstractItemView* view, + const QStyleOptionViewItem& option, + const QModelIndex& index) +{ + if (!e || !view) + return false; + + if ( e->type() != QEvent::ToolTip ) + return QStyledItemDelegate::helpEvent(e, view, option, index); + + QRect rect = view->visualRect(index); + QSize size = sizeHint(option, index); + if ( rect.width() < size.width() ) { + QVariant tooltip = index.data(Qt::DisplayRole); + if ( tooltip.canConvert() ) { + QToolTip::showText(e->globalPos(), tooltip.toString(), view ); + return true; + } + } + + if ( !QStyledItemDelegate::helpEvent( e, view, option, index ) ) + QToolTip::hideText(); + + return true; +} + + + + +// +// FunctionSelection +// + +FunctionSelection::FunctionSelection( TopLevelBase* top, + QWidget* parent) + : QWidget(parent), TraceItemView(0, top) +{ + _group = 0; + _inSetGroup = false; + _inSetFunction = false; + _functionListSortOrder = Qt::DescendingOrder; + + setTitle(tr("Function Profile")); + + // first row with search label and group type combo + QHBoxLayout* hboxLayout = new QHBoxLayout(); + hboxLayout->setSpacing(6); + hboxLayout->setMargin(0); + + searchLabel = new QLabel(this); + searchLabel->setText(tr("&Search:")); + searchLabel->setWordWrap(false); + hboxLayout->addWidget(searchLabel); + + searchEdit = new QLineEdit(this); + searchLabel->setBuddy(searchEdit); + hboxLayout->addWidget(searchEdit); + + groupBox = new QComboBox(this); + hboxLayout->addWidget(groupBox); + + // vertical layout: first row, group list, function list + QVBoxLayout* vboxLayout = new QVBoxLayout(this); + vboxLayout->setSpacing(6); + vboxLayout->setMargin(3); + vboxLayout->addLayout(hboxLayout); + + groupList = new QTreeWidget(this); + QStringList groupHeader; + groupHeader << tr("Self") << tr("Group"); + groupList->setHeaderLabels(groupHeader); + +#if QT_VERSION >= 0x050000 + groupList->header()->setSectionsClickable(true); +#else + groupList->header()->setClickable(true); +#endif + groupList->header()->setSortIndicatorShown(false); + groupList->header()->stretchLastSection(); + groupList->setIconSize(QSize(99,99)); + groupList->setMaximumHeight(150); + groupList->setRootIsDecorated(false); + groupList->setUniformRowHeights(true); + groupList->sortByColumn(0, Qt::AscendingOrder); + vboxLayout->addWidget(groupList); + + functionListModel = new FunctionListModel(); + functionListModel->setMaxCount(GlobalConfig::maxListCount()); + + functionList = new QTreeView(this); + functionList->setRootIsDecorated(false); + functionList->setAllColumnsShowFocus(true); + functionList->setAutoScroll(false); + functionList->setContextMenuPolicy(Qt::CustomContextMenu); + functionList->setUniformRowHeights(true); +#if QT_VERSION >= 0x050000 + functionList->header()->setSectionsClickable(true); + functionList->header()->setSectionResizeMode(QHeaderView::Interactive); +#else + functionList->header()->setClickable(true); + functionList->header()->setResizeMode(QHeaderView::Interactive); +#endif + functionList->header()->setSortIndicatorShown(false); + functionList->header()->setSortIndicator(0, Qt::DescendingOrder); + // for columns 3 and 4 (all others get resized) + functionList->header()->setDefaultSectionSize(200); + functionList->setModel(functionListModel); + functionList->setItemDelegate(new AutoToolTipDelegate(functionList)); + vboxLayout->addWidget(functionList); + + // order has to match mapping in groupTypeSelected() + QStringList args; + args << tr("(No Grouping)") + << ProfileContext::i18nTypeName(ProfileContext::Object) + << ProfileContext::i18nTypeName(ProfileContext::File) + << ProfileContext::i18nTypeName(ProfileContext::Class) + << ProfileContext::i18nTypeName(ProfileContext::FunctionCycle); + groupBox->addItems(args); + connect(groupBox, SIGNAL(activated(int)), + this, 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); + + // single click release activation + connect(functionList, SIGNAL(clicked(QModelIndex)), + this, SLOT(functionActivated(QModelIndex))); + connect(functionList, SIGNAL(activated(QModelIndex)), + this, SLOT(functionActivated(QModelIndex))); + connect(functionList, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(functionContext(const QPoint &))); + connect(functionList->header(), SIGNAL(sectionClicked(int)), + this, SLOT(functionHeaderClicked(int))); + + connect(groupList, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, SLOT( groupSelected(QTreeWidgetItem*,QTreeWidgetItem*) ) ); + connect(groupList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + this, SLOT(groupDoubleClicked(QTreeWidgetItem*,int))); + + groupList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(groupList, + SIGNAL(customContextMenuRequested(const QPoint &) ), + this, SLOT(groupContext(const QPoint &))); + connect(groupList->header(), + SIGNAL(sectionClicked(int)), this, SLOT(groupHeaderClicked(int))); + + // start hidden + groupList->hide(); + + setWhatsThis(whatsThis()); +} + +QString FunctionSelection::whatsThis() const +{ + return tr( + // xgettext: no-c-format + "The Flat Profile" + "

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.

" + "

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.

"); +} + +void FunctionSelection::setData(TraceData* d) +{ + TraceItemView::setData(d); + + _group = 0; + _groupSize.clear(); + _hc.clear(GlobalConfig::maxListCount()); + groupList->clear(); + functionListModel->resetModelData(d, 0, QString(), 0); +} + +void FunctionSelection::searchReturnPressed() +{ + query(searchEdit->text()); + + QTreeWidgetItem* item; + if (_groupType != ProfileContext::Function) { + // if current group not matching, select first matching group + item = groupList->currentItem(); + if (!item || item->isHidden()) { + int i; + item = 0; + for (i=0; itopLevelItemCount(); i++) { + item = groupList->topLevelItem(i); + if (!item->isHidden()) break; + } + if (!item) return; + + setGroup(((CostListItem*)item)->costItem()); + return; + } + } + // activate top function in functionList + selectTopFunction(); + functionList->setFocus(); +} + +// 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.setSingleShot(true); + _searchTimer.start(ms); +} + +void FunctionSelection::queryDelayed() +{ + query(_searchDelayed); +} + +void FunctionSelection::functionContext(const QPoint & p) +{ + QMenu popup; + TraceFunction* f = 0; + + QAction* activateFunctionAction = 0; + QModelIndex i = functionList->indexAt(p); + if (i.isValid()) { + f = functionListModel->function(i); + if (f) { + QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(f->prettyName())); + activateFunctionAction = popup.addAction(menuText); + popup.addSeparator(); + } + if ((i.column() == 0) || (i.column() == 1)) { + addEventTypeMenu(&popup,false); + popup.addSeparator(); + } + } + + addGroupMenu(&popup); + popup.addSeparator(); + addGoMenu(&popup); + + QPoint pDiff = QPoint(0, functionList->header()->height()); + QAction* a = popup.exec(functionList->mapToGlobal(p + pDiff)); + if (a == activateFunctionAction) + activated(f); +} + +void FunctionSelection::groupContext(const QPoint & p) +{ + QMenu popup; + + int c = groupList->columnAt(p.x()); + if (c == 0) { + addEventTypeMenu(&popup,false); + popup.addSeparator(); + } + addGroupMenu(&popup); + popup.addSeparator(); + addGoMenu(&popup); + + QPoint headerSize = QPoint(0, groupList->header()->height()); + popup.exec(groupList->mapToGlobal(p + headerSize)); +} + +void FunctionSelection::addGroupAction(QMenu* m, + ProfileContext::Type v, + const QString& s) +{ + QAction* a; + if (s.isEmpty()) + a = m->addAction(ProfileContext::i18nTypeName(v)); + else + a = m->addAction(s); + a->setData((int)v); + a->setCheckable(true); + a->setChecked(_groupType == v); +} + +void FunctionSelection::addGroupMenu(QMenu* menu) +{ + QMenu* m = menu->addMenu(tr("Grouping")); + + if (_groupType != ProfileContext::Function) { + addGroupAction(m, ProfileContext::Function, tr("No Grouping")); + m->addSeparator(); + } + addGroupAction(m, ProfileContext::Object); + addGroupAction(m, ProfileContext::File); + addGroupAction(m, ProfileContext::Class); + addGroupAction(m, ProfileContext::FunctionCycle); + + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(groupTypeSelected(QAction*))); +} + + +void FunctionSelection::groupTypeSelected(QAction* a) +{ + selectedGroupType( (ProfileContext::Type) a->data().toInt() ); +} + +void FunctionSelection::groupTypeSelected(int cg) +{ + switch(cg) { + case 0: selectedGroupType( ProfileContext::Function ); break; + case 1: selectedGroupType( ProfileContext::Object ); break; + case 2: selectedGroupType( ProfileContext::File ); break; + case 3: selectedGroupType( ProfileContext::Class ); break; + case 4: selectedGroupType( ProfileContext::FunctionCycle ); break; + default: break; + } +} + + +CostItem* FunctionSelection::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + break; + + case ProfileContext::Instr: + i = ((TraceInstr*)i)->function(); + break; + + case ProfileContext::Line: + i = ((TraceLine*)i)->functionSource()->function(); + break; + + default: + i = 0; + break; + } + return i; +} + + +void FunctionSelection::selectFunction(TraceFunction* f, + bool ensureVisible) +{ + QModelIndex i = functionListModel->indexForFunction(f, true); + if (!i.isValid()) return; + + if (ensureVisible) + functionList->scrollTo(i, QAbstractItemView::EnsureVisible); + + _inSetFunction = true; + QModelIndex last = functionListModel->index(i.row(), 4); + QItemSelection s(i, last); + functionList->selectionModel()->select(s, QItemSelectionModel::ClearAndSelect); + _inSetFunction = false; +} + +void FunctionSelection::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) return; + + // we do not show cost 2 at all... + if (changeType == eventType2Changed) return; + + if (changeType == eventTypeChanged) { + int i; + +#if QT_VERSION >= 0x050000 + groupList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); +#else + groupList->header()->setResizeMode(0, QHeaderView::ResizeToContents); +#endif + // need to disable sorting! Otherwise each change of shown cost + // reorders list and changes order returned by topLevelItem() + groupList->setSortingEnabled(false); + for (i=0; itopLevelItemCount(); i++) { + CostListItem* item = (CostListItem*) groupList->topLevelItem(i); + item->setEventType(_eventType); + } +#if QT_VERSION >= 0x050000 + groupList->header()->setSectionResizeMode(0, QHeaderView::Interactive); +#else + groupList->header()->setResizeMode(0, QHeaderView::Interactive); +#endif + groupList->setSortingEnabled(true); + groupList->header()->setSortIndicatorShown(false); + + functionListModel->setEventType(_eventType); + // previous line resets the model: reselect active item + selectFunction(dynamic_cast(_activeItem)); + setCostColumnWidths(); + return; + } + + if (changeType == activeItemChanged) { + if (_activeItem ==0) { + functionList->clearSelection(); + return; + } + switch(_activeItem->type()) { + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + setGroup((TraceCostItem*)_activeItem); + return; + default: break; + } + + // active item is a function + TraceFunction* f = (TraceFunction*) _activeItem; + + // if already current, nothing to do + QModelIndex i = functionList->currentIndex(); + if (functionListModel->function(i) == f) { + return; + } + + // reset search (as not activated from this view) + query(QString::null); //krazy:exclude=nullstrassign for old broken gcc + + // select cost item group of function + switch(_groupType) { + case ProfileContext::Object: setGroup(f->object()); break; + case ProfileContext::Class: setGroup(f->cls()); break; + case ProfileContext::File: setGroup(f->file()); break; + case ProfileContext::FunctionCycle: setGroup(f->cycle()); break; + default: + break; + } + + selectFunction(f); + return; + } + + if (changeType & groupTypeChanged) { + if (_activeItem && (_activeItem->type() == ProfileContext::Function)) { + TraceFunction* f = (TraceFunction*) _activeItem; + + // select cost item group of function + switch(_groupType) { + case ProfileContext::Object: _group = f->object(); break; + case ProfileContext::Class: _group = f->cls(); break; + case ProfileContext::File: _group = f->file(); break; + case ProfileContext::FunctionCycle: _group = f->cycle(); break; + default: + _group = 0; + break; + } + } + + int id; + switch(_groupType) { + case ProfileContext::Object: id = 1; break; + case ProfileContext::File: id = 2; break; + case ProfileContext::Class: id = 3; break; + case ProfileContext::FunctionCycle: id = 4; break; + default: id = 0; break; + } + groupBox->setCurrentIndex(id); + + if (_groupType == ProfileContext::Function) + groupList->hide(); + else + groupList->show(); + } + + // reset searchEdit + _searchString = QString(); + query(QString::null); //krazy:exclude=nullstrassign for old broken gcc + + 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; + + QTreeWidgetItem* item = 0; + int i; + for (i=0; i < groupList->topLevelItemCount(); i++) { + item = groupList->topLevelItem(i); + if (((CostListItem*)item)->costItem() == g) + break; + } + + if (item) { + groupList->scrollToItem(item); + // prohibit signalling of a group selection + _inSetGroup = true; + _group = 0; + groupList->setCurrentItem(item); + _inSetGroup = false; + } + else + groupList->clearSelection(); +} + + +void FunctionSelection::refresh() +{ + groupList->clear(); + + // make cost columns as small as possible: + // the new functions make them as wide as needed + groupList->setColumnWidth(0, 50); + groupList->headerItem()->setText(1, ProfileContext::i18nTypeName(_groupType)); + + functionListModel->setMaxCount(GlobalConfig::maxListCount()); + + if (!_data || _data->parts().count()==0) { + functionListModel->resetModelData(0, 0, QString(), 0); + selectTopFunction(); + return; + } + + + TraceObjectMap::Iterator oit; + TraceClassMap::Iterator cit; + TraceFileMap::Iterator fit; + + // Fill up group list. + // Always show group of current function, even if cost below low limit. + // + + _hc.clear(GlobalConfig::maxListCount()); + + switch(_groupType) { + case ProfileContext::Object: + + for ( oit = _data->objectMap().begin(); + oit != _data->objectMap().end(); ++oit ) + _hc.addCost(&(*oit), (*oit).subCost(_eventType)); + break; + + case ProfileContext::Class: + + for ( cit = _data->classMap().begin(); + cit != _data->classMap().end(); ++cit ) + _hc.addCost(&(*cit), (*cit).subCost(_eventType)); + break; + + case ProfileContext::File: + + for ( fit = _data->fileMap().begin(); + fit != _data->fileMap().end(); ++fit ) + _hc.addCost(&(*fit), (*fit).subCost(_eventType)); + break; + + case ProfileContext::FunctionCycle: + { + // add all cycles + foreach(TraceCostItem *group, _data->functionCycles()) + _hc.addCost(group, group->subCost(_eventType)); + } + + break; + + default: + { + _group = 0; + functionListModel->resetModelData(_data, _group, _searchString, _eventType); + selectFunction(dynamic_cast(_activeItem)); + setCostColumnWidths(); + return; + } + } + + // update group from _activeItem if possible + if (_activeItem && (_activeItem->type() == _groupType)) + _group = (TraceCostItem*) _activeItem; + + QTreeWidgetItem *item = 0, *activeItem = 0; + QList items; + + // we always put group of active item in list, even if + // it would be skipped because of small costs + if (_group) { + activeItem = new CostListItem(groupList, _group, _eventType); + items.append(activeItem); + } + + for(int i=0; i<_hc.realCount(); i++) { + TraceCostItem *group = (TraceCostItem*)_hc[i]; + // do not put group of active item twice into list + if (group == _group) continue; + item = new CostListItem(groupList, group, _eventType); + items.append(item); + } + if (_hc.hasMore()) { + // a placeholder for all the cost items skipped ... + item = new CostListItem(groupList, _hc.count() - _hc.maxSize(), + (TraceCostItem*)_hc[_hc.maxSize()-1], _eventType); + items.append(item); + } + +#if QT_VERSION >= 0x050000 + groupList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); +#else + groupList->header()->setResizeMode(0, QHeaderView::ResizeToContents); +#endif + groupList->setSortingEnabled(false); + groupList->addTopLevelItems(items); + groupList->setSortingEnabled(true); + // always reset to cost sorting + groupList->sortByColumn(0, Qt::DescendingOrder); + groupList->header()->setSortIndicatorShown(false); +#if QT_VERSION >= 0x050000 + groupList->header()->setSectionResizeMode(0, QHeaderView::Interactive); +#else + groupList->header()->setResizeMode(0, QHeaderView::Interactive); +#endif + + if (activeItem) { + groupList->scrollToItem(activeItem); + _inSetGroup = true; + _group = 0; + groupList->setCurrentItem(activeItem); + _inSetGroup = false; + } + else + groupList->clearSelection(); +} + + +void FunctionSelection::groupSelected(QTreeWidgetItem* i, QTreeWidgetItem*) +{ + if (!i) return; + if (!_data) return; + + TraceCostItem* g = ((CostListItem*) i)->costItem(); + if (!g) return; + if (g == _group) return; + _group = g; + + functionListModel->resetModelData(_data, g, _searchString, _eventType); + selectFunction(dynamic_cast(_activeItem)); + setCostColumnWidths(); + + // Do not emit signal if cost item was changed programatically + if (!_inSetGroup) { + if (_topLevel) + _topLevel->setGroupDelayed(g); + } +} + +void FunctionSelection::groupDoubleClicked(QTreeWidgetItem* i, int) +{ + 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); +} + +void FunctionSelection::groupHeaderClicked(int col) +{ + groupList->sortByColumn(col, Qt::DescendingOrder); +} + +TraceCostItem* FunctionSelection::group(QString s) +{ + int i; + for (i=0; itopLevelItemCount(); i++) { + CostListItem* item = (CostListItem*) groupList->topLevelItem(i); + if (item->costItem()->name() == s) + return item->costItem(); + } + + return 0; +} + + +void FunctionSelection::functionActivated(const QModelIndex& i) +{ + if (!_data) return; + + TraceFunction* f = functionListModel->function(i); + if (!f) return; + + if (!_inSetFunction) + activated(f); +} + +void FunctionSelection::updateGroupSizes(bool hideEmpty) +{ + int i; + for (i=0; itopLevelItemCount(); i++) { + CostListItem* item = (CostListItem*) groupList->topLevelItem(i); + int size = (_groupSize.contains(item->costItem())) ? + _groupSize[item->costItem()] : -1; + item->setSize(size); + item->setHidden(hideEmpty && (size<0)); + } +} + +void FunctionSelection::query(QString query) +{ + if(!_data) + return; + 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, Qt::CaseInsensitive, QRegExp::Wildcard); + _groupSize.clear(); + + TraceFunction* f = 0; + TraceFunctionList list2; + + _hc.clear(GlobalConfig::maxListCount()); + + TraceFunctionMap::Iterator it; + for ( it = _data->functionMap().begin(); + it != _data->functionMap().end(); ++it ) { + f = &(*it); + if (re.indexIn(f->prettyName())>=0) { + if (_group) { + if (_groupType==ProfileContext::Object) { + if (_groupSize.contains(f->object())) + _groupSize[f->object()]++; + else + _groupSize[f->object()] = 1; + if (f->object() != _group) continue; + } + else if (_groupType==ProfileContext::Class) { + if (_groupSize.contains(f->cls())) + _groupSize[f->cls()]++; + else + _groupSize[f->cls()] = 1; + if (f->cls() != _group) continue; + } + else if (_groupType==ProfileContext::File) { + if (_groupSize.contains(f->file())) + _groupSize[f->file()]++; + else + _groupSize[f->file()] = 1; + if (f->file() != _group) continue; + } + else if (_groupType==ProfileContext::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(_eventType)); + } + } + updateGroupSizes(true); + + functionListModel->resetModelData(_data, _group, _searchString, _eventType); + selectFunction(dynamic_cast(_activeItem)); + setCostColumnWidths(); +} + +bool FunctionSelection::selectTopFunction() +{ + QModelIndex i = functionListModel->index(0,0); + TraceFunction* f = functionListModel->function(i); + + // pre-select before activation to not trigger a refresh of this view + _activeItem = f; + selectFunction(f); + + functionActivated(i); + + return (f!=0); +} + +void FunctionSelection::setCostColumnWidths() +{ + functionList->resizeColumnToContents(1); + + if (_eventType && (_eventType->subCost(_data->callMax())>0) ) { + functionList->resizeColumnToContents(0); + functionList->resizeColumnToContents(2); + } + else { + functionList->header()->resizeSection(0, 0); + functionList->header()->resizeSection(2, 0); + } +} + +void FunctionSelection::functionHeaderClicked(int col) +{ + if ((_functionListSortOrder== Qt::AscendingOrder) || (col<3)) + _functionListSortOrder = Qt::DescendingOrder; + else + _functionListSortOrder = Qt::AscendingOrder; + + functionList->sortByColumn(col, _functionListSortOrder); + selectFunction(dynamic_cast(_activeItem), false); + setCostColumnWidths(); +} + + + +#include "moc_functionselection.cpp" diff --git a/kcachegrind/libviews/functionselection.h b/kcachegrind/libviews/functionselection.h new file mode 100644 index 00000000..0cfc2c9e --- /dev/null +++ b/kcachegrind/libviews/functionselection.h @@ -0,0 +1,130 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * For function selection, to be put into a QDockWindow + */ + +#ifndef FUNCTIONSELECTION_H +#define FUNCTIONSELECTION_H + +#include +#include +#include +#include + +#include "tracedata.h" +#include "traceitemview.h" +#include "listutils.h" +#include "toplevelbase.h" + +class QAction; +class QMenu; +class QLabel; +class QComboBox; +class QLineEdit; +class QTreeView; +class QTreeWidget; +class QTreeWidgetItem; +class FunctionListModel; + +class FunctionSelection: public QWidget, public TraceItemView +{ + Q_OBJECT + +public: + explicit FunctionSelection(TopLevelBase*, QWidget* parent = 0); + + TraceCostItem* group() { return _group; } + TraceCostItem* group(QString); + void setGroup(TraceCostItem*); + void query(QString); + bool selectTopFunction(); + + QWidget* widget() { return this; } + QString whatsThis() const; + void setData(TraceData*); + + void addGroupMenu(QMenu*); + +public slots: + void searchReturnPressed(); + void searchChanged(const QString&); + void queryDelayed(); + + void groupTypeSelected(QAction*); + void groupTypeSelected(int); + void groupDoubleClicked(QTreeWidgetItem*, int); + void groupSelected(QTreeWidgetItem*, QTreeWidgetItem*); + void groupContext(const QPoint &); + void groupHeaderClicked(int); + + void functionActivated(const QModelIndex&); + void functionContext(const QPoint &); + void functionHeaderClicked(int); + +private: + CostItem* canShow(CostItem* i); + void doUpdate(int, bool); + void selectFunction(); + void refresh(); + void setCostColumnWidths(); + void updateGroupSizes(bool hideEmpty); + void addGroupAction(QMenu*, ProfileContext::Type, + const QString& s = QString()); + void selectFunction(TraceFunction* f, bool ensureVisible = true); + + TraceCostItem* _group; + + QString _searchString, _searchDelayed; + QTimer _searchTimer; + QMap _groupSize; + + HighestCostList _hc; + // when setting a + bool _inSetGroup, _inSetFunction; + + QLabel *searchLabel; + QLineEdit *searchEdit; + QComboBox *groupBox; + QTreeWidget *groupList; + QTreeView *functionList; + FunctionListModel* functionListModel; + + Qt::SortOrder _functionListSortOrder; +}; + + +/* Custom item delegate for function list: + * show tooltip for function name if truncated. + * (thanks to http://www.mimec.org/node/337) + */ +class AutoToolTipDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + AutoToolTipDelegate(QObject* parent = 0); + ~AutoToolTipDelegate(); + +public slots: + bool helpEvent( QHelpEvent* e, QAbstractItemView* view, + const QStyleOptionViewItem& option, + const QModelIndex& index ); +}; + +#endif diff --git a/kcachegrind/libviews/globalguiconfig.cpp b/kcachegrind/libviews/globalguiconfig.cpp new file mode 100644 index 00000000..8c4931be --- /dev/null +++ b/kcachegrind/libviews/globalguiconfig.cpp @@ -0,0 +1,244 @@ +/* This file is part of KCachegrind. + Copyright (C) 2010 Josef Weidendorfer + + 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. +*/ + +/* + * Global configuration for GUI components of KCachegrind + */ + +#include "globalguiconfig.h" + +#include "config.h" + + +// +// ConfigColorSettings +// + +ConfigColorSetting::ConfigColorSetting(QString n) +{ + _name = n; + reset(); +} + +ConfigColorSetting::ConfigColorSetting(QString n, QColor c) +{ + _name = n; + setColor(c); +} + +void ConfigColorSetting::setColor(const QColor& c) +{ + _color = c; + _automatic = (c == colorForName(_name)); +} + +QColor ConfigColorSetting::colorForName(QString n) +{ + int h = 0, s = 100; + foreach(const QChar c, n) { + h = (h * 37 + s* c.unicode()) % 256; + s = (s * 17 + h* c.unicode()) % 192; + } + + return QColor::fromHsv(h, 64+s, 192); +} + +QColor ConfigColorSetting::autoColor() const +{ + return colorForName(_name); +} + +void ConfigColorSetting::reset() +{ + _automatic = true; + _color = colorForName(_name); +} + + +// +// GlobalGUIConfig +// + +GlobalGUIConfig::GlobalGUIConfig() + : GlobalConfig() +{ +} + +GlobalGUIConfig::~GlobalGUIConfig() +{ + qDeleteAll(_colors); + _colors.clear(); +} + +GlobalGUIConfig* GlobalGUIConfig::config() +{ + GlobalGUIConfig* gc; + + if (_config == 0) { + gc = new GlobalGUIConfig(); + _config = gc; + } + else { + gc = dynamic_cast(_config); + if (gc == 0) + qFatal("Internal error: config object is not a GlobalGUIConfig."); + } + + return gc; +} + +void GlobalGUIConfig::saveOptions() +{ + // color options + ConfigGroup* colorConfig = ConfigStorage::group("CostColors"); + int count = 1; + foreach(ConfigColorSetting* cs, _colors) { + if ( !cs->_automatic ) { + colorConfig->setValue( QString("Name%1").arg(count), + cs->_name); + colorConfig->setValue( QString("Color%1").arg(count), + cs->_color); + count++; + } + } + colorConfig->setValue("Count", count-1); + delete colorConfig; + + GlobalConfig::saveOptions(); +} + +void GlobalGUIConfig::readOptions() +{ + int i, count; + + // color options + _colors.clear(); + // colors for default event types: + // red for LL/L2 misses + colorSetting("EventType-I2mr")->_color = QColor(240, 0, 0); + colorSetting("EventType-D2mr")->_color = QColor(180,40,40); + colorSetting("EventType-D2mw")->_color = QColor(120,80,80); + colorSetting("EventType-ILmr")->_color = QColor(240, 0, 0); + colorSetting("EventType-DLmr")->_color = QColor(180,40,40); + colorSetting("EventType-DLmw")->_color = QColor(120,80,80); + // green for L1 misses + colorSetting("EventType-I1mr")->_color = QColor(0, 240, 0); + colorSetting("EventType-D1mr")->_color = QColor(40,180,40); + colorSetting("EventType-D1mw")->_color = QColor(80,120,80); + // yellow for branches/mispredictions + colorSetting("EventType-Bc") ->_color = QColor(240,240, 0); + colorSetting("EventType-Bcm")->_color = QColor(200,200,30); + colorSetting("EventType-Bi") ->_color = QColor(160,160,60); + colorSetting("EventType-Bim")->_color = QColor(120,120,90); + // blue for normal accesses + colorSetting("EventType-Ir")->_color = QColor(0, 0, 240); + colorSetting("EventType-Dr")->_color = QColor(40,40,180); + colorSetting("EventType-Dw")->_color = QColor(80,80,120); + + ConfigGroup* colorConfig = ConfigStorage::group("CostColors"); + count = colorConfig->value("Count", 0).toInt(); + for (i=1;i<=count;i++) { + QString n = colorConfig->value(QString("Name%1").arg(i), + QString()).toString(); + QColor color = colorConfig->value(QString("Color%1").arg(i), + QColor(Qt::black)); + + if (n.isEmpty()) continue; + + ConfigColorSetting* cs = new ConfigColorSetting(n,color); + _colors.insert(n, cs); + } + delete colorConfig; + + GlobalConfig::readOptions(); +} + +ConfigColorSetting* GlobalGUIConfig::groupColorSetting(CostItem* cost) +{ + QString n; + + if (!cost) + return colorSetting(QString("default")); + + return groupColorSetting(cost->type(), cost->name()); +} + +ConfigColorSetting* GlobalGUIConfig::groupColorSetting(ProfileContext::Type t, + QString name) +{ + QString n = ProfileContext::typeName(t) + '-' + name; + return colorSetting(n); +} + +QColor GlobalGUIConfig::groupColor(CostItem* cost) +{ + return groupColorSetting(cost)->color(); +} + +QColor GlobalGUIConfig::eventTypeColor(EventType* t) +{ + QString n; + + if (!t) + n = QString("EventType-default"); + else + n = QString("EventType-%1").arg(t->name()); + + return colorSetting(n)->color(); +} + +QColor GlobalGUIConfig::functionColor(ProfileContext::Type gt, + TraceFunction* f) +{ + ProfileCostArray* group = f; + QString n; + + switch(gt) { + case ProfileContext::Object: group = f->object(); break; + case ProfileContext::Class: group = f->cls(); break; + case ProfileContext::File: group = f->file(); break; + default: + break; + } + + if (group != f) { + // first look for manual color of a function in a group + n = ProfileContext::typeName(group->type()) + + '-' + group->name() + + '-' + f->name(); + + ConfigColorSetting* cs = colorSetting(n, false); + if (cs) return cs->color(); + } + return groupColor(group); +} + +ConfigColorSetting* GlobalGUIConfig::colorSetting(const QString& n, + bool createNew) +{ + // predefined ? + GlobalGUIConfig* c = config(); + ConfigColorSetting* cs = c->_colors.value(n, 0); + if (cs || !createNew) return cs; + + cs = new ConfigColorSetting(n); + c->_colors.insert(n, cs); + + return cs; +} + diff --git a/kcachegrind/libviews/globalguiconfig.h b/kcachegrind/libviews/globalguiconfig.h new file mode 100644 index 00000000..b6d38ce1 --- /dev/null +++ b/kcachegrind/libviews/globalguiconfig.h @@ -0,0 +1,97 @@ +/* This file is part of KCachegrind. + Copyright (C) 2010 Josef Weidendorfer + + 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. +*/ + +/* + * Global configuration for GUI components of KCachegrind + */ + +#ifndef GLOBALGUICONFIG_H +#define GLOBALGUICONFIG_H + +#include +#include +#include + +#include "globalconfig.h" + +/** + * Color setting for a cost item + * Helper class for color settings in configuration. + * + * Objects can only be instantiated by the singleton GlobalConfig + * and therefore are unique; changes will be saved as configuration. + */ +class ConfigColorSetting +{ + friend class GlobalGUIConfig; + friend class ConfigDlg; + +public: + QColor color() const { return _color; } + bool automatic() const { return _automatic; } + + static QColor colorForName(QString); + // returns automatic color calculated from name, but does not change color + QColor autoColor() const; + + // explicitly set a color, switches off automatic mode + void setColor(const QColor&); + // reset to automatic mode + void reset(); + +private: + ConfigColorSetting(QString n); // color calculated from name + ConfigColorSetting(QString, QColor); // color set explicitly + + QString _name; + QColor _color; + bool _automatic; +}; + + +/** + * Extension of global configuration for GUI options. + * A singleton. + */ +class GlobalGUIConfig: public GlobalConfig +{ + friend class ConfigDlg; + +public: + GlobalGUIConfig(); + ~GlobalGUIConfig(); + + // gets the singleton instance + static GlobalGUIConfig* config(); + + void saveOptions(); + void readOptions(); + + // color for visualization of an object + static QColor functionColor(ProfileContext::Type gt, TraceFunction*); + static QColor groupColor(CostItem*); + static QColor eventTypeColor(EventType*); + static ConfigColorSetting* groupColorSetting(CostItem*); + static ConfigColorSetting* groupColorSetting(ProfileContext::Type, QString); + +protected: + static ConfigColorSetting* colorSetting(const QString&, bool createNew = true); + QHash _colors; +}; + +#endif // GLOBALGUICONFIG_H diff --git a/kcachegrind/libviews/instritem.cpp b/kcachegrind/libviews/instritem.cpp new file mode 100644 index 00000000..4387df14 --- /dev/null +++ b/kcachegrind/libviews/instritem.cpp @@ -0,0 +1,480 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011 Josef Weidendorfer + + 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. +*/ + +/* + * Items of instruction view. + */ + +#include "instritem.h" + +#include +#include +#include + +#include "globalguiconfig.h" +#include "listutils.h" +#include "instrview.h" + +// +// InstrItem +// + +// for messages +InstrItem::InstrItem(InstrView* iv, QTreeWidget* parent, + Addr addr, const QString& msg) + : QTreeWidgetItem(parent) +{ + _view = iv; + _addr = addr; + _instr = 0; + _instrCall = 0; + _instrJump = 0; + _inside = false; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + setText(0, addr.pretty()); + setText(6, msg); + + updateGroup(); + updateCost(); +} + +// for code lines +InstrItem::InstrItem(InstrView* iv, QTreeWidget* parent, + Addr addr, bool inside, + const QString& code, const QString& cmd, + const QString& args, TraceInstr* instr) + : QTreeWidgetItem(parent) +{ + _view = iv; + _addr = addr; + _instr = instr; + _instrCall = 0; + _instrJump = 0; + _inside = inside; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + 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, QTreeWidgetItem* parent, Addr addr, + TraceInstr* instr, TraceInstrCall* instrCall) + : QTreeWidgetItem(parent) +{ + _view = iv; + _addr = addr; + _instr = instr; + _instrCall = instrCall; + _instrJump = 0; + _inside = true; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + //qDebug("InstrItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().toAscii()); + + SubCost cc = _instrCall->callCount(); + QString callStr = " "; + if (cc==0) + callStr += QObject::tr("Active call to '%1'") + .arg(_instrCall->call()->calledName()); + else + callStr += QObject::tr("%n call(s) to '%2'", "", (uint64)cc) + .arg(_instrCall->call()->calledName()); + + TraceFunction* calledF = _instrCall->call()->called(); + calledF->addPrettyLocation(callStr); + + setText(6, callStr); + + updateGroup(); + updateCost(); +} + +// for jump lines +InstrItem::InstrItem(InstrView* iv, QTreeWidgetItem* parent, Addr addr, + TraceInstr* instr, TraceInstrJump* instrJump) + : QTreeWidgetItem(parent) +{ + _view = iv; + _addr = addr; + _inside = true; + _instr = instr; + _instrCall = 0; + _instrJump = instrJump; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().toAscii()); + + QString jStr; + if (_instrJump->isCondJump()) + jStr = QObject::tr("Jump %1 of %2 times to 0x%3") + .arg(_instrJump->followedCount().pretty()) + .arg(_instrJump->executedCount().pretty()) + .arg(_instrJump->instrTo()->addr().toString()); + else + jStr = QObject::tr("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 = GlobalGUIConfig::functionColor(_view->groupType(), f); + setIcon(6, colorPixmap(10, 10, c)); +} + +void InstrItem::updateCost() +{ + _pure = SubCost(0); + _pure2 = SubCost(0); + + if (!_instr) return; + if (_instrJump) return; + + ProfileCostArray* instrCost = _instrCall ? + (ProfileCostArray*)_instrCall : (ProfileCostArray*)_instr; + + // do not show any cost inside of cycles + if (_instrCall && + ((_instrCall->call()->inCycle()>0) || + (_instrCall->call()->isRecursion()))) { + QString str; + QPixmap p; + + QString icon = "edit-undo"; +#if 0 // TODO + KIconLoader* loader = KIconLoader::global(); + p= loader->loadIcon(icon, KIconLoader::Small, 0, + KIconLoader::DefaultState, QStringList(), 0, true); +#endif + if (p.isNull()) + str = QObject::tr("(cycle)"); + + setText(1, str); + setIcon(1, p); + setText(2, str); + setIcon(2, p); + return; + } + + ProfileCostArray* totalCost; + if (GlobalConfig::showExpanded()) + totalCost = _instr->function()->inclusive(); + else + totalCost = _instr->function()->data(); + + EventType *et = _view->eventType(); + _pure = et ? instrCost->subCost(et) : SubCost(0); + if (_pure == 0) { + setText(1, QString()); + setIcon(1, QPixmap()); + } + else { + double total = totalCost->subCost(et); + double pure = 100.0 * _pure / total; + + if (GlobalConfig::showPercentage()) + setText(1, QString("%1") + .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(1, _pure.pretty()); + + setIcon(1, costPixmap(et, instrCost, total, false)); + } + + EventType *ct2 = _view->eventType2(); + _pure2 = ct2 ? instrCost->subCost(ct2) : SubCost(0); + if (_pure2 == 0) { + setText(2, QString()); + setIcon(2, QPixmap()); + } + else { + double total = totalCost->subCost(ct2); + double pure = 100.0 * _pure2 / total; + + if (GlobalConfig::showPercentage()) + setText(2, QString("%1") + .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(2, _pure2.pretty()); + + setIcon(2, costPixmap(ct2, instrCost, total, false)); + } +} + +bool InstrItem::operator<( const QTreeWidgetItem & other ) const +{ + const InstrItem* ii1 = this; + const InstrItem* ii2 = (InstrItem*) &other; + int col = treeWidget()->sortColumn(); + + if (col==1) + return (ii1->_pure < ii2->_pure); + + if (col==2) + return (ii1->_pure2 < ii2->_pure2); + + if (col==0) { + if (ii1->_addr < ii2->_addr) return true; + if (ii1->_addr > ii2->_addr) return false; + + // Same address: code gets above calls/jumps + if (!ii1->_instrCall && !ii1->_instrJump) return true; + if (!ii2->_instrCall && !ii2->_instrJump) return false; + + // calls above jumps + if (ii1->_instrCall && !ii2->_instrCall) return true; + if (ii2->_instrCall && !ii1->_instrCall) return false; + + if (ii1->_instrCall && ii2->_instrCall) { + // Two calls: desending sort according costs + if (ii1->_pure < ii2->_pure) return true; + if (ii1->_pure > ii2->_pure) return false; + + // Two calls: sort according function names + TraceFunction* f1 = ii1->_instrCall->call()->called(); + TraceFunction* f2 = ii2->_instrCall->call()->called(); + return (f1->prettyName() < f2->prettyName()); + } + + // Two jumps: descending sort according target address + return (ii1->_instrJump->instrTo()->addr() < + ii2->_instrJump->instrTo()->addr()); + } + return QTreeWidgetItem::operator<(other); +} + +void InstrItem::setJumpArray(const QVector& a) +{ + _jump = a; +} + + +// +// InstrItemDelegate +// + +InstrItemDelegate::InstrItemDelegate(InstrView *parent) + : QItemDelegate(parent) +{ + _parent = parent; +} + +QSize InstrItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QSize sz = QItemDelegate::sizeHint(option, index); + + int c = index.column(); + if (c != 3) return sz; + + InstrView* iv = (InstrView*) _parent; + int levels = iv->arrowLevels(); + + if (levels == 0) + return QSize(0, sz.height()); + + // 10 pixels for the arrow, 1 pixel margin left and right + return QSize(10 + 6*levels + 2, sz.height()); +} + +void InstrItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + int column = index.column(); + InstrItem* item = static_cast(index.internalPointer()); + + QColor color; + if ( !item->inside() || ((column==1) || (column==2))) + color = option.palette.color( QPalette::Button ); + else if ((item->instrCall() || item->instrJump()) && column>2) + color = option.palette.color( QPalette::Midlight ); + if (color.isValid()) + _parent->model()->setData(index, color, Qt::BackgroundRole); + + if(column==3) + paintArrows(painter, option, index); + else + QItemDelegate::paint(painter, option, index); +} + +void InstrItemDelegate::paintArrows(QPainter *p, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QTreeWidget* tw = _parent; + if ( !tw ) return; + InstrView* iv = (InstrView*) tw; + InstrItem* item = static_cast(index.internalPointer()); + const QRect& rect = option.rect; + int height = rect.height(); + + p->save(); + drawBackground(p, option, index); + p->translate(rect.topLeft()); + + int marg = 1; + int yy = height/2, y1, y2; + QColor c; + + int start = -1, end = -1; + + TraceInstrJump* instrJump = item->instrJump(); + Addr addr = item->addr(); + TraceInstrCall* instrCall = item->instrCall(); + + // draw line borders, detect start/stop of a line + for(int i=0; i< item->jumpCount(); i++) { + TraceInstrJump* jump = item->jump(i); + if (jump == 0) continue; + + y1 = 0; + y2 = height; + if ((instrJump == jump) && + (jump->instrFrom()->addr() == addr)) { + + //qDebug() << "InstrItem " << addr.toString() << ": start " << i; + if (start<0) start = i; + if (jump->instrTo()->addr() <= addr) + y2 = yy; + else + y1 = yy; + } + else if (!instrJump && !instrCall && + (jump->instrTo()->addr() == addr)) { + + //qDebug() << "InstrItem " << addr.toString() << ": end " << i; + if (end<0) end = i; + if (jump->instrFrom()->addr() < addr) + y2 = yy; + else + y1 = yy; + } + + c = jump->isCondJump() ? Qt::red : Qt::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 = item->jump(start)->isCondJump() ? Qt::red : Qt::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 = item->jump(end)->isCondJump() ? Qt::red : Qt::blue; + x = marg + 6*end; + w = 6*(iv->arrowLevels() - end) + 10; + + QPolygon a; + a.putPoints(0, 8, 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, + x,y+h); + p->setBrush(c); + p->drawConvexPolygon(a); + + p->setPen(c.light()); + p->drawPolyline(a.constData(), 5); + p->setPen(c.dark()); + p->drawPolyline(a.constData() + 4, 2); + p->setPen(c.light()); + p->drawPolyline(a.constData() + 5, 2); + p->setPen(c.dark()); + p->drawPolyline(a.constData() + 6, 2); + } + + // draw inner vertical line for start/stop + // this overwrites borders of horizontal line + for(int i=0; i< item->jumpCount(); i++) { + TraceInstrJump* jump = item->jump(i); + if (jump == 0) continue; + + c = jump->isCondJump() ? Qt::red : Qt::blue; + + if (jump->instrFrom()->addr() == addr) { + bool drawUp = true; + if (jump->instrTo()->addr() == addr) + if (start<0) drawUp=false; + if (jump->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->instrTo()->addr() == addr) { + if (end<0) end = i; + if (jump->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); + } + } + p->restore(); +} + diff --git a/kcachegrind/libviews/instritem.h b/kcachegrind/libviews/instritem.h new file mode 100644 index 00000000..300c4a8d --- /dev/null +++ b/kcachegrind/libviews/instritem.h @@ -0,0 +1,103 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011 Josef Weidendorfer + + 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. +*/ + +/* + * Items of instruction view. + */ + +#ifndef INSTRITEM_H +#define INSTRITEM_H + + +#include +#include + +#include "tracedata.h" + +class InstrView; + +class InstrItem: public QTreeWidgetItem +{ + +public: + // for messages + InstrItem(InstrView* iv, QTreeWidget* parent, + Addr addr, const QString&); + + // for instruction lines + InstrItem(InstrView* iv, QTreeWidget* parent, + Addr addr, bool inside, + const QString&, const QString&, const QString&, + TraceInstr* instr); + + // for call instr + InstrItem(InstrView* iv, QTreeWidgetItem* parent, Addr addr, + TraceInstr* instr, TraceInstrCall* instrCall); + + // for jump lines + InstrItem(InstrView* iv, QTreeWidgetItem* parent, Addr addr, + TraceInstr* instr, TraceInstrJump* instrJump); + + Addr addr() const { return _addr; } + bool inside() const { return _inside; } + TraceInstr* instr() const { return _instr; } + TraceInstrCall* instrCall() const { return _instrCall; } + TraceInstrJump* instrJump() const { return _instrJump; } + TraceInstrJump* jump(int i) const { return _jump[i]; } + int jumpCount() const { return _jump.size(); } + bool operator< ( const QTreeWidgetItem & other ) const; + + void updateGroup(); + void updateCost(); + + // arrow lines + void setJumpArray(const QVector& a); + +private: + InstrView* _view; + SubCost _pure, _pure2; + Addr _addr; + TraceInstr* _instr; + TraceInstrJump* _instrJump; + TraceInstrCall* _instrCall; + bool _inside; + + QVector _jump; +}; + +// Delegate for drawing the arrows column + +class InstrItemDelegate : public QItemDelegate +{ +public: + InstrItemDelegate(InstrView *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex & index ) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +protected: + void paintArrows(QPainter *p, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + InstrView* _parent; +}; + + +#endif // INSTRITEM_H diff --git a/kcachegrind/libviews/instrview.cpp b/kcachegrind/libviews/instrview.cpp new file mode 100644 index 00000000..8bb94faf --- /dev/null +++ b/kcachegrind/libviews/instrview.cpp @@ -0,0 +1,1134 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011 Josef Weidendorfer + + 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. +*/ + +/* + * Instruction View + */ + +#include "instrview.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "globalconfig.h" +#include "instritem.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: * ":" * + 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 isHexDigit(char c) +{ + return (c >='0' && c <='9') || (c >='a' && c <='f'); +} + +/** + * Parses a line from objdump assembly output, returning false for + * a line without an assembly instruction. Otherwise, it sets the + * output parameters addr, code, mnemonic, operands. + */ +static bool parseLine(const char* buf, Addr& addr, + QString& code, QString& mnemonic, QString& operands) +{ + uint pos, start; + + // check for instruction line: * ":" * + + pos = 0; + while(buf[pos]==' ' || buf[pos]=='\t') pos++; + + int digits = addr.set(buf + pos); + pos += digits; + if ((digits==0) || (buf[pos] != ':')) return false; + + // further parsing of objdump output... + pos++; + while(buf[pos]==' ' || buf[pos]=='\t') pos++; + + // check for hex code, patterns "xx "* / "xxxx "* / "xxxxxxxx" + // (with "x" being a lower case hex digit) + start = pos; + while(1) { + if (! isHexDigit(buf[pos])) break; + if (! isHexDigit(buf[pos+1])) break; + if (buf[pos+2] == ' ') { + pos += 3; + continue; + } + if (! isHexDigit(buf[pos+2])) break; + if (! isHexDigit(buf[pos+3])) break; + if (buf[pos+4] == ' ') { + pos += 5; + continue; + } + if (! isHexDigit(buf[pos+4])) break; + if (! isHexDigit(buf[pos+5])) break; + if (! isHexDigit(buf[pos+6])) break; + if (! isHexDigit(buf[pos+7])) break; + if (buf[pos+8] != ' ') break; + pos += 9; + } + if (pos <= start) return false; + code = QString::fromLatin1(buf + start, pos - start - 1); + + // skip whitespace + while(buf[pos]==' ' || buf[pos]=='\t') pos++; + + // check for mnemonic + start = pos; + while(buf[pos] && buf[pos]!=' ' && buf[pos]!='\t') pos++; + mnemonic = QString::fromLatin1(buf + start, pos - start); + + // skip whitespace + while(buf[pos]==' '|| buf[pos]=='\t') pos++; + + // last part are the operands + int operandsLen = strlen(buf + pos); + + // ignore a newline at end + if ((operandsLen>0) && (buf[pos + operandsLen - 1] == '\n')) + operandsLen--; + + // maximal 50 chars + if (operandsLen > 50) + operands = QString::fromLatin1(buf + pos, 47) + QString("..."); + else + operands = QString::fromLatin1(buf+pos, operandsLen); + + if (0) qDebug("For 0x%s: Code '%s', Mnemonic '%s', Operands '%s'", + qPrintable(addr.toString()), qPrintable(code), + qPrintable(mnemonic), qPrintable(operands)); + + return true; +} + + + + +// +// InstrView +// + + +InstrView::InstrView(TraceItemView* parentView, + QWidget* parent) + : QTreeWidget(parent), TraceItemView(parentView) +{ + _showHexCode = DEFAULT_SHOWHEXCODE; + _lastHexCodeWidth = 50; + + _inSelectionUpdate = false; + _arrowLevels = 0; + + QStringList headerLabels; + headerLabels << tr( "#" ) + << tr( "Cost" ) + << tr( "Cost 2" ) + << "" + << tr( "Hex" ) + << "" // Mnenomic + << tr( "Assembly Instructions" ) + << tr( "Source Position" ); + setHeaderLabels(headerLabels); + setRootIsDecorated(false); + setAllColumnsShowFocus(true); + setUniformRowHeights(true); + // collapsing call/jump lines by double-click is confusing + setExpandsOnDoubleClick(false); + + // sorting will be enabled after refresh() + sortByColumn(0, Qt::AscendingOrder); + header()->setSortIndicatorShown(false); + setItemDelegate(new InstrItemDelegate(this)); + setWhatsThis( whatsThis() ); + + connect( this, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + SLOT( selectedSlot(QTreeWidgetItem*,QTreeWidgetItem*) ) ); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &) ), + SLOT(context(const QPoint &))); + + connect(this, + SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(activatedSlot(QTreeWidgetItem*,int))); + + connect(header(), SIGNAL(sectionClicked(int)), + this, SLOT(headerClicked(int))); + + this->setWhatsThis( whatsThis()); +} + + +QString InstrView::whatsThis() const +{ + return tr( "Annotated Machine Code" + "

The annotated machine code list shows the " + "assembly 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.

" + "

The machine code shown is generated with " + "the 'objdump' utility from the 'binutils' package.

" + "

Select a line with call information to " + "make the destination function of this call current.

"); +} + +void InstrView::context(const QPoint & p) +{ + QMenu popup; + int c = columnAt(p.x()); + QTreeWidgetItem* i = itemAt(p); + + 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; + + QAction* activateFunctionAction = 0; + QAction* activateInstrAction = 0; + if (f) { + QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(f->prettyName())); + activateFunctionAction = popup.addAction(menuText); + popup.addSeparator(); + } + else if (instr) { + QString menuText = tr("Go to Address %1").arg(instr->name()); + activateInstrAction = popup.addAction(menuText); + popup.addSeparator(); + } + + if ((c == 1) || (c == 2)) { + addEventTypeMenu(&popup); + popup.addSeparator(); + } + addGoMenu(&popup); + popup.addSeparator(); + + QAction* toggleHexAction = new QAction(tr("Hex Code"), &popup); + toggleHexAction->setCheckable(true); + toggleHexAction->setChecked(_showHexCode); + popup.addAction(toggleHexAction); + + QAction* a = popup.exec(mapToGlobal(p + QPoint(0,header()->height()))); + if (a == activateFunctionAction) + TraceItemView::activated(f); + else if (a == activateInstrAction) + TraceItemView::activated(instr); + else if (a == toggleHexAction) { + _showHexCode = !_showHexCode; + // remember width when hiding + if (!_showHexCode) + _lastHexCodeWidth = columnWidth(4); + setColumnWidths(); + } +} + + +void InstrView::selectedSlot(QTreeWidgetItem *i, QTreeWidgetItem *) +{ + 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(QTreeWidgetItem* i, int) +{ + if (!i) return; + TraceInstrCall* ic = ((InstrItem*) i)->instrCall(); + TraceInstrJump* ij = ((InstrItem*) i)->instrJump(); + + if (!ic && !ij) { + TraceInstr* instr = ((InstrItem*) i)->instr(); + if (instr) TraceItemView::activated(instr); + return; + } + + if (ic) { + TraceFunction* f = ic->call()->called(); + if (f) TraceItemView::activated(f); + } + else if (ij) { + TraceInstr* instr = ij->instrTo(); + if (instr) TraceItemView::activated(instr); + } +} + +void InstrView::keyPressEvent(QKeyEvent* event) +{ + QTreeWidgetItem *item = currentItem(); + if (item && ((event->key() == Qt::Key_Return) || + (event->key() == Qt::Key_Space))) + { + activatedSlot(item, 0); + } + QTreeView::keyPressEvent(event); +} + +CostItem* InstrView::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::Instr: + case ProfileContext::InstrJump: + case ProfileContext::Line: + return i; + + default: + break; + } + + return 0; +} + + +void InstrView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + QList items = selectedItems(); + InstrItem* ii = (items.count() > 0) ? (InstrItem*)items[0] : 0; + if (ii) { + if ((ii->instr() == _selectedItem) || + (ii->instr() && (ii->instr()->line() == _selectedItem))) return; + if (ii->instrCall() && + (ii->instrCall()->call()->called() == _selectedItem)) return; + } + + TraceInstrJump* ij = 0; + if (_selectedItem->type() == ProfileContext::InstrJump) + ij = (TraceInstrJump*) _selectedItem; + + QTreeWidgetItem *item, *item2; + for (int i=0; iinstr() == _selectedItem) || + (ii->instr() && (ii->instr()->line() == _selectedItem)) || + (ij && (ij->instrTo() == ii->instr())) ) { + scrollToItem(item); + _inSelectionUpdate = true; + setCurrentItem(item); + _inSelectionUpdate = false; + break; + } + item2 = 0; + for (int j=0; ichildCount(); j++) { + item2 = item->child(j); + ii = (InstrItem*)item2; + if (!ii->instrCall()) continue; + if (ii->instrCall()->call()->called() == _selectedItem) { + scrollToItem(item2); + _inSelectionUpdate = true; + setCurrentItem(item2); + _inSelectionUpdate = false; + break; + } + } + if (item2) break; + } + return; + } + + if (changeType == groupTypeChanged) { + // update group colors for call lines + QTreeWidgetItem *item, *item2; + for (int i=0; ichildCount(); i++) { + item2 = item->child(j); + ((InstrItem*)item2)->updateGroup(); + } + } + return; + } + + // On eventTypeChanged, we can not just change the costs shown in + // already existing items, as costs of 0 should make the line to not + // be shown at all. So we do a full refresh. + + refresh(); +} + +void InstrView::setColumnWidths() +{ +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(4, QHeaderView::Interactive); +#else + header()->setResizeMode(4, QHeaderView::Interactive); +#endif + if (_showHexCode) { + setColumnWidth(4, _lastHexCodeWidth); + } + else { + setColumnWidth(4, 0); + } +} + +// compare functions for jump arrow drawing + +// helper for compare +void getInstrJumpAddresses(const TraceInstrJump* ij, Addr& low, Addr& high) +{ + low = ij->instrFrom()->addr(); + high = ij->instrTo()->addr(); + + if (low > high) { + Addr t = low; + low = high; + high = t; + } +} + +// sort jumps according to lower instruction address +bool instrJumpLowLessThan(const TraceInstrJump* ij1, + const TraceInstrJump* ij2) +{ + Addr addr1Low, addr1High, addr2Low, addr2High; + + getInstrJumpAddresses(ij1, addr1Low, addr1High); + getInstrJumpAddresses(ij2, addr2Low, addr2High); + + if (addr1Low != addr2Low) return (addr1Low < addr2Low); + // jump ends come before jump starts + if (addr1Low == ij1->instrTo()->addr()) return true; + if (addr2Low == ij2->instrTo()->addr()) return false; + return (addr1High < addr2High); +} + +// sort jumps according to higher instruction address +bool instrJumpHighLessThan(const TraceInstrJump* ij1, + const TraceInstrJump* ij2) +{ + Addr addr1Low, addr1High, addr2Low, addr2High; + + getInstrJumpAddresses(ij1, addr1Low, addr1High); + getInstrJumpAddresses(ij2, addr2Low, addr2High); + + if (addr1High != addr2High) return (addr1High < addr2High); + // jump ends come before jump starts + if (addr1High == ij1->instrTo()->addr()) return true; + if (addr2High == ij2->instrTo()->addr()) return false; + return (addr1Low < addr2Low); +} + +void InstrView::refresh() +{ + int originalPosition = verticalScrollBar()->value(); + + clear(); + setColumnWidth(0, 20); + setColumnWidth(1, 50); + setColumnWidth(2, _eventType2 ? 50:0); + setColumnWidth(3, 0); // arrows, defaults to invisible + setColumnWidth(4, 0); // hex code column + setColumnWidth(5, 50); // command column + setColumnWidth(6, 250); // arg column + + // reset to automatic sizing to get column width +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(4, QHeaderView::ResizeToContents); +#else + header()->setResizeMode(4, QHeaderView::ResizeToContents); +#endif + + + if (_eventType) + headerItem()->setText(1, _eventType->name()); + if (_eventType2) + headerItem()->setText(2, _eventType2->name()); + + _arrowLevels = 0; + if (!_data || !_activeItem) return; + + ProfileContext::Type t = _activeItem->type(); + TraceFunction* f = 0; + if (t == ProfileContext::Function) f = (TraceFunction*) _activeItem; + if (t == ProfileContext::Instr) { + f = ((TraceInstr*)_activeItem)->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + if (t == ProfileContext::Line) { + f = ((TraceLine*)_activeItem)->functionSource()->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + + if (!f) return; + + // 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(_eventType)) break; + if (_eventType2 && (*it).hasCost(_eventType2)) break; + ++it; + } + } + if (!instrMap || (it == itEnd)) { + new InstrItem(this, this, 1, + tr("There is no instruction info in the profile data file.")); + new InstrItem(this, this, 2, + tr("Tip: For Callgrind, rerun with option")); + new InstrItem(this, this, 3, tr(" --dump-instr=yes")); + new InstrItem(this, this, 4, tr("To see (conditional) jumps, additionally specify")); + new InstrItem(this, this, 5, tr(" --collect-jumps=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(); + foreach(TraceInstrJump* ij, jlist) { + if (ij->executedCount()==0) continue; + _lowList.append(ij); + _highList.append(ij); + } + ++it; + while(it != itEnd) { + if ((*it).hasCost(_eventType)) break; + if (_eventType2 && (*it).hasCost(_eventType2)) break; + ++it; + } + if (it == itEnd) break; + } + qSort(_lowList.begin(), _lowList.end(), instrJumpLowLessThan); + qSort(_highList.begin(), _highList.end(), instrJumpHighLessThan); + _lowListIter = _lowList.begin(); // iterators to list start + _highListIter = _highList.begin(); + _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(_eventType)) break; + if (_eventType2 && (*it).hasCost(_eventType2)) 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 (!_eventType2) { +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(2, QHeaderView::Interactive); +#else + header()->setResizeMode(2, QHeaderView::Interactive); +#endif + setColumnWidth(2, 0); + } + + // reset to the original position - this is useful when the view + // is refreshed just because we change between relative/absolute + verticalScrollBar()->setValue(originalPosition); +} + +/* 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) +{ + Addr lowAddr, highAddr; + int iEnd = -1, iStart = -1; + + if (0) qDebug("updateJumpArray(addr 0x%s, jump to %s)", + qPrintable(addr.toString()), + ii->instrJump() + ? qPrintable(ii->instrJump()->instrTo()->name()) : "?" ); + + // check for new arrows starting from here downwards + while(_lowListIter != _lowList.end()) { + TraceInstrJump* ij= *_lowListIter; + 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, qPrintable(ij->name())); + _jump[iStart] = ij; + } + _lowListIter++; + } + + ii->setJumpArray(_jump); + + // check for active arrows ending here + while(_highListIter != _highList.end()) { + TraceInstrJump* ij= *_highListIter; + 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) { + qDebug() << "InstrView: no jump start for end at 0x" + << highAddr.toString() << " ?"; + iEnd = -1; + } + + if (0 && (iEnd>=0)) + qDebug(" end %d (%s to %s)", + iEnd, + qPrintable(_jump[iEnd]->instrFrom()->name()), + qPrintable(_jump[iEnd]->instrTo()->name())); + + if (0 && ij) qDebug("next end: %s to %s", + qPrintable(ij->instrFrom()->name()), + qPrintable(ij->instrTo()->name())); + + _highListIter++; + + if (highAddr > addr) + break; + else { + if (iEnd>=0) _jump[iEnd] = 0; + iEnd = -1; + } + } + if (iEnd>=0) _jump[iEnd] = 0; +} + + +bool InstrView::searchFile(QString& dir, TraceObject* o) +{ + QString filename = o->shortName(); + + if (QDir::isAbsolutePath(dir)) { + return QFile::exists(dir + '/' + filename); + } + + QFileInfo fi(dir, filename); + if (fi.exists()) { + dir = fi.absolutePath(); + return true; + } + + TracePart* firstPart = _data->parts().first(); + if (firstPart) { + QFileInfo partFile(firstPart->name()); + if (QFileInfo(partFile.absolutePath(), filename).exists()) { + dir = partFile.absolutePath(); + return true; + } + } + + return false; +} + +/** + * 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; + + // should not 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; + + QString dir = function->object()->directory(); + if (!searchFile(dir, function->object())) { + new InstrItem(this, this, 1, + tr("For annotated machine code, " + "the following object file is needed:")); + new InstrItem(this, this, 2, + QString(" '%1'").arg(function->object()->name())); + new InstrItem(this, this, 3, + tr("This file can not be found.")); + return false; + } + function->object()->setDirectory(dir); + + // call objdump synchronously + QString objfile = dir + '/' + function->object()->shortName(); + QStringList objdumpArgs = QStringList() + << "-C" << "-d" + << QString("--start-address=0x%1").arg(dumpStartAddr.toString()) + << QString("--stop-address=0x%1").arg(dumpEndAddr.toString()) + << objfile; + QString objdumpCmd = "objdump " + objdumpArgs.join(" "); + + if (1) qDebug("Running '%s'...", qPrintable(objdumpCmd)); + + // and run... + QProcess objdump; + objdump.start("objdump", objdumpArgs); + if (!objdump.waitForStarted() || + !objdump.waitForFinished()) { + + new InstrItem(this, this, 1, + tr("There is an error trying to execute the command")); + new InstrItem(this, this, 2, + QString(" '%1'").arg(objdumpCmd)); + new InstrItem(this, this, 3, + tr("Check that you have installed 'objdump'.")); + new InstrItem(this, this, 4, + tr("This utility can be found in the 'binutils' package.")); + return false; + } + + +#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; + + QList items; + while (1) { + + if (needObjAddr) { + needObjAddr = false; + + // read next objdump line + while (1) { + readBytes=objdump.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, qPrintable(objdumpCmd)); + } + else if ((readBytes>0) && (buf[readBytes-1] == '\n')) + buf[readBytes-1] = 0; + + objAddr = parseAddr(buf); + if ((objAddrdumpEndAddr)) + objAddr = 0; + if (objAddr != 0) break; + } + + if (0) qDebug() << "Got ObjAddr: 0x" << objAddr.toString(); + } + + // 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(_eventType)) break; + if (_eventType2 && (*it).hasCost(_eventType2)) break; + ++it; + } + costAddr = nextCostAddr; + nextCostAddr = (it == itEnd) ? Addr(0) : (*it).addr(); + + if (0) qDebug() << "Got nextCostAddr: 0x" << nextCostAddr.toString() + << ", costAddr 0x" << costAddr.toString(); + } + + // if we have no more address from objdump, stop + if (objAddr == 0) break; + + if ((nextCostAddr==0) || (costAddr == 0) || + (objAddr < nextCostAddr)) { + // next line is objAddr + + // this sets addr, code, cmd, args + bool isAssemblyInstr = parseLine(buf, addr, code, cmd, args); + Q_UNUSED(isAssemblyInstr); + assert(isAssemblyInstr && (objAddr == addr)); + + if (costAddr == objAddr) { + currInstr = &(*costIt); + needCostAddr = true; + } + else + currInstr = 0; + + needObjAddr = true; + + if (0) qDebug() << "Dump Obj Addr: 0x" << addr.toString() + << " [" << cmd << " " << args << "], cost (0x" + << costAddr.toString() << ", next 0x" + << nextCostAddr.toString() << ")"; + } + else { + addr = costAddr; + code = cmd = QString(); + args = tr("(No Instruction)"); + + currInstr = &(*costIt); + needCostAddr = true; + + noAssLines++; + if (0) qDebug() << "Dump Cost Addr: 0x" << addr.toString() + << " (no ass), objAddr 0x" << objAddr.toString(); + } + + // update inside + if (!inside) { + if (currInstr) inside = true; + } + else { + if (0) qDebug() << "Check if 0x" << addr.toString() << " is in ]0x" + << costAddr.toString() << ",0x" + << (nextCostAddr - 3*GlobalConfig::noCostInside()).toString() + << "[" << endl; + + // Suppose a average instruction len of 3 bytes + if ( (addr > costAddr) && + ((nextCostAddr==0) || + (addr < nextCostAddr - 3*GlobalConfig::noCostInside()) )) + inside = false; + } + + int context = GlobalConfig::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, 0, addr, inside, + code, cmd, args, currInstr); + items.append(ii); + + dumpedLines++; + if (0) qDebug() << "Dumped 0x" << addr.toString() << " " + << (inside ? "Inside " : "Outside") + << (currInstr ? "Cost" : ""); + + // 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(_eventType) > most) { + item = ii; + most = currInstr->subCost(_eventType); + } + + ii->setExpanded(true); + foreach(TraceInstrCall* ic, currInstr->instrCalls()) { + if ((ic->subCost(_eventType)==0) && + (ic->subCost(_eventType2)==0)) continue; + + if (ic->subCost(_eventType) > most) { + item = ii; + most = ic->subCost(_eventType); + } + + ii2 = new InstrItem(this, ii, addr, currInstr, ic); + + if (!selected && (ic->call()->called() == _selectedItem)) + selected = ii2; + } + + foreach(TraceInstrJump* ij, currInstr->instrJumps()) { + if (ij->executedCount()==0) continue; + + new InstrItem(this, ii, addr, currInstr, ij); + } + } + + // Resize columns with address/counts/opcode to contents +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(5, QHeaderView::ResizeToContents); +#else + header()->setResizeMode(0, QHeaderView::ResizeToContents); + header()->setResizeMode(1, QHeaderView::ResizeToContents); + header()->setResizeMode(2, QHeaderView::ResizeToContents); + header()->setResizeMode(5, QHeaderView::ResizeToContents); +#endif + + setSortingEnabled(false); + addTopLevelItems(items); + expandAll(); + setSortingEnabled(true); + // always reset to line number sort + sortByColumn(0, Qt::AscendingOrder); + header()->setSortIndicatorShown(false); + + // Reallow interactive column size change after resizing to content +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(0, QHeaderView::Interactive); + header()->setSectionResizeMode(1, QHeaderView::Interactive); + header()->setSectionResizeMode(2, QHeaderView::Interactive); + header()->setSectionResizeMode(5, QHeaderView::Interactive); +#else + header()->setResizeMode(0, QHeaderView::Interactive); + header()->setResizeMode(1, QHeaderView::Interactive); + header()->setResizeMode(2, QHeaderView::Interactive); + header()->setResizeMode(5, QHeaderView::Interactive); +#endif + + if (selected) item = selected; + if (item) first = item; + if (first) { + scrollToItem(first); + _inSelectionUpdate = true; + setCurrentItem(first); + _inSelectionUpdate = false; + } + + // for arrows: go down the list according to list sorting + QTreeWidgetItem *item1, *item2; + for (int i=0; iaddr(), ii, true, false); + + for (int j=0; jchildCount(); j++) { + item2 = item1->child(j); + ii2 = (InstrItem*)item2; + if (ii2->instrJump()) + updateJumpArray(ii->addr(), ii2, false, true); + else + ii2->setJumpArray(_jump); + } + } + + if (arrowLevels()) + setColumnWidth(3, 10 + 6*arrowLevels() + 2); + else + setColumnWidth(3, 0); + + if (noAssLines > 1) { + // trace cost not matching code + + new InstrItem(this, this, 1, + tr("There are %n cost line(s) without machine code.", "", noAssLines)); + new InstrItem(this, this, 2, + tr("This happens because the code of")); + new InstrItem(this, this, 3, QString(" %1").arg(objfile)); + new InstrItem(this, this, 4, + tr("does not seem to match the profile data file.")); + new InstrItem(this, this, 5, ""); + new InstrItem(this, this, 6, + tr("Are you using an old profile data file or is the above mentioned")); + new InstrItem(this, this, 7, + tr("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, + tr("There seems to be an error trying to execute the command")); + new InstrItem(this, this, 2, + QString(" '%1'").arg(objdumpCmd)); + new InstrItem(this, this, 3, + tr("Check that the ELF object used in the command exists.")); + new InstrItem(this, this, 4, + tr("Check that you have installed 'objdump'.")); + new InstrItem(this, this, 5, + tr("This utility can be found in the 'binutils' package.")); + return false; + } + + return true; +} + +void InstrView::headerClicked(int col) +{ + if (col == 0) { + sortByColumn(col, Qt::AscendingOrder); + } + //All others but Source Text column Descending + else if (col <4) { + sortByColumn(col, Qt::DescendingOrder); + } +} + +void InstrView::restoreOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + _showHexCode = g->value("ShowHexCode", DEFAULT_SHOWHEXCODE).toBool(); + delete g; +} + +void InstrView::saveOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + g->setValue("ShowHexCode", _showHexCode, DEFAULT_SHOWHEXCODE); + delete g; +} + +#include "moc_instrview.cpp" diff --git a/kcachegrind/libviews/instrview.h b/kcachegrind/libviews/instrview.h new file mode 100644 index 00000000..4f4e0c3e --- /dev/null +++ b/kcachegrind/libviews/instrview.h @@ -0,0 +1,85 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011 Josef Weidendorfer + + 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. +*/ + +/* + * Instruction View + */ + +#ifndef INSTRVIEW_H +#define INSTRVIEW_H + +#include + +#include "traceitemview.h" + +class InstrItem; + +class InstrView : public QTreeWidget, public TraceItemView +{ + friend class InstrItem; + + Q_OBJECT + +public: + explicit InstrView(TraceItemView* parentView, + QWidget* parent = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + int arrowLevels() { return _arrowLevels; } + + void restoreOptions(const QString& prefix, const QString& postfix); + void saveOptions(const QString& prefix, const QString& postfix); + +protected slots: + void context(const QPoint &); + void selectedSlot(QTreeWidgetItem*, QTreeWidgetItem*); + void activatedSlot(QTreeWidgetItem*,int); + void headerClicked(int); + +protected: + void keyPressEvent(QKeyEvent* event); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); + void setColumnWidths(); + bool searchFile(QString&, TraceObject*); + void fillInstr(); + void updateJumpArray(Addr,InstrItem*,bool,bool); + bool fillInstrRange(TraceFunction*, + TraceInstrMap::Iterator,TraceInstrMap::Iterator); + + bool _inSelectionUpdate; + + // arrows + int _arrowLevels; + // temporary needed on creation... + QVector _jump; + TraceInstrJumpList _lowList, _highList; + TraceInstrJumpList::iterator _lowListIter, _highListIter; + + // remember width of hex code column if hidden + int _lastHexCodeWidth; + + // widget options + bool _showHexCode; +}; + +#endif diff --git a/kcachegrind/libviews/libviews.pri b/kcachegrind/libviews/libviews.pri new file mode 100644 index 00000000..1557d4fc --- /dev/null +++ b/kcachegrind/libviews/libviews.pri @@ -0,0 +1,62 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +NHEADERS += \ + $$PWD/globalguiconfig.h \ + $$PWD/traceitemview.h \ + $$PWD/toplevelbase.h \ + $$PWD/partselection.h \ + $$PWD/functionlistmodel.h \ + $$PWD/functionselection.h \ + $$PWD/listutils.h \ + $$PWD/stackselection.h \ + $$PWD/multiview.h \ + $$PWD/tabview.h \ + $$PWD/callgraphview.h \ + $$PWD/treemap.h \ + $$PWD/callitem.h \ + $$PWD/callview.h \ + $$PWD/callmapview.h \ + $$PWD/costlistitem.h \ + $$PWD/coverageitem.h \ + $$PWD/coverageview.h \ + $$PWD/eventtypeitem.h \ + $$PWD/eventtypeview.h \ + $$PWD/instritem.h \ + $$PWD/instrview.h \ + $$PWD/partgraph.h \ + $$PWD/partlistitem.h \ + $$PWD/partview.h \ + $$PWD/sourceitem.h \ + $$PWD/sourceview.h \ + $$PWD/stackitem.h + +SOURCES += \ + $$PWD/globalguiconfig.cpp \ + $$PWD/callgraphview.cpp \ + $$PWD/callitem.cpp \ + $$PWD/callmapview.cpp \ + $$PWD/callview.cpp \ + $$PWD/costlistitem.cpp \ + $$PWD/coverageitem.cpp \ + $$PWD/coverageview.cpp \ + $$PWD/eventtypeitem.cpp \ + $$PWD/eventtypeview.cpp \ + $$PWD/functionlistmodel.cpp \ + $$PWD/functionselection.cpp \ + $$PWD/instritem.cpp \ + $$PWD/instrview.cpp \ + $$PWD/listutils.cpp \ + $$PWD/multiview.cpp \ + $$PWD/partgraph.cpp \ + $$PWD/partlistitem.cpp \ + $$PWD/partselection.cpp \ + $$PWD/partview.cpp \ + $$PWD/sourceitem.cpp \ + $$PWD/sourceview.cpp \ + $$PWD/stackitem.cpp \ + $$PWD/stackselection.cpp \ + $$PWD/tabview.cpp \ + $$PWD/toplevelbase.cpp \ + $$PWD/traceitemview.cpp \ + $$PWD/treemap.cpp diff --git a/kcachegrind/libviews/listutils.cpp b/kcachegrind/libviews/listutils.cpp new file mode 100644 index 00000000..a78b2b16 --- /dev/null +++ b/kcachegrind/libviews/listutils.cpp @@ -0,0 +1,241 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Some helper functions for QListViewItem derivates + */ + +#include "listutils.h" + +#include +#include + +#include "globalguiconfig.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, ih, iy1, iy2; + + // at max, draw 100% + if (percent > 100) percent = 100; + + // inner rectangle to fill with bar + if (framed) { + iw = w-2, ix1 = 1; + ih = h-2, iy1 = 1, iy2 = h-2; + } + else { + iw = w; ix1 = 0; + ih = h; iy1 = 0; iy2 = h-1; + } + + int filled = iw*percent/100+1; + if (!framed) 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-1, h-1); + + // inside + p.setPen(Qt::NoPen); + p.setBrush(c); + p.drawRect(ix1, iy1, filled-1,ih-1); + + // last right pix column + int lastY = ih-(filled*ih - iw*ih*percent/100); + int lastX1 = ix1+filled-2 + ((lastY>1) ? 1: 0); + int lastX2 = ix1+filled-2; + + // frame + p.setPen(c.light()); + p.drawLine(ix1, iy1, lastX1, iy1); + p.drawLine(ix1, iy1, ix1, iy2); + p.setPen(c.dark()); + p.drawLine(lastX1, iy1, lastX1, iy1+lastY); + p.drawLine(lastX2, iy1+lastY, lastX2, iy2); + p.drawLine(ix1+1, iy2, lastX2, iy2); + + return pix; +} + +inline QColor partitionColor(int d, int max) +{ + return QColor::fromHsv((720*d/max) % 360, 255-(128*d/max), 192); +} + + +QPixmap partitionPixmap(int w, int h, + double* hist, EventTypeSet* set, int maxIndex, bool framed) +{ + int lastPos = 0, nextPos; + double val=0.0, sum=0.0; + int d, dmin=maxIndex, dmax=0; + for (d = 0;d0.0) { + sum += hist[d]; + if (dmin>d) dmin = d; + if (dmaxrealType(d)); + else + c = 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, ih-1); + + // 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; + cLast = c; + d++; + } + + // right border (in last color) + if (x2>0) + p.drawLine(x2, iy1, x2, iy2); + + return pix; +} + + +QPixmap costPixmap(EventType* ct, ProfileCostArray* cost, + double total, bool framed) +{ + if (!ct) return QPixmap(); + + if (ct->isReal()) { + QColor color = GlobalGUIConfig::eventTypeColor(ct); + double p = 100.0 * cost->subCost(ct) / total; + return percentagePixmap(COSTPIX_WIDTH, 10, (int)(p+.5), color, framed); + } + + int maxIndex; + double h[MaxRealIndexValue]; + maxIndex = ct->histCost(cost, total, h); + + if (maxIndex ==0) return QPixmap(); + return partitionPixmap(COSTPIX_WIDTH, 10, h, ct->set(), maxIndex, framed); +} + + + + diff --git a/kcachegrind/libviews/listutils.h b/kcachegrind/libviews/listutils.h new file mode 100644 index 00000000..bd915e87 --- /dev/null +++ b/kcachegrind/libviews/listutils.h @@ -0,0 +1,43 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Some helper functions for QListViewItem derivates + */ + +#ifndef LISTUTILS_H +#define LISTUTILS_H + +#include +#include +#include + +#include "subcost.h" + +class EventType; +class EventTypeSet; + +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, EventTypeSet*, + int maxIndex, bool framed); +QPixmap costPixmap(EventType* ct, ProfileCostArray* cost, + double total, bool framed); + +#endif diff --git a/kcachegrind/libviews/multiview.cpp b/kcachegrind/libviews/multiview.cpp new file mode 100644 index 00000000..c9504c84 --- /dev/null +++ b/kcachegrind/libviews/multiview.cpp @@ -0,0 +1,239 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * 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 "multiview.h" + +#include + +#include "config.h" +#include "tabview.h" + + +// +// MultiView +// + +MultiView::MultiView(TopLevelBase* top, QWidget* parent) + : QSplitter(parent), TraceItemView(0, top) +{ + // default + setOrientation(Qt::Horizontal); + + appendView(); + _active = _views.first(); + _active->setActive(true); +} + +void MultiView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + foreach(TabView* tv, _views) + tv->setData(d); +} + +void MultiView::setChildCount(int n) +{ + while(n< _views.count()) removeView(); + while(n> _views.count()) appendView(); +} + +void MultiView::appendView() +{ + int n = _views.count()+1; + + TabView* tv = new TabView(this, this); + tv->setObjectName(QString("TabView-%1").arg(n)); + connect(tv, SIGNAL(tabActivated(TabView*)), + this, SLOT(tabActivated(TabView*)) ); + _views.append(tv); + tv->show(); + + // no need to waste time with update merging + tv->setMergeUpdates(false); + + // set same attributes as in active view + tv->set(0, _data, _eventType, _eventType2, + _groupType, _partList, _activeItem, 0); + + if (0) qDebug() << "MultiView::appendView, now " + << _views.count(); +} + +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.removeAll(last); + delete last; + + if (0) qDebug() << "MultiView::removeView, now " + << _views.count(); +} + + +void MultiView::tabActivated(TabView* newActiveTab) +{ + if (_active == newActiveTab) return; + + if (0) qDebug() << "MultiView::tabActivated " + << newActiveTab->objectName(); + + CostItem* 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, CostItem* i) +{ + if (0) qDebug() << "MultiView::selected " << i->name() + << ", sender " << sender->widget()->objectName(); + + // we react only on selection changes of the active TabView + if (sender != (TraceItemView*)_active) return; + + int idx = _views.indexOf(_active); + idx++; + if (idx == _views.count()) idx = 0; + TabView* next = _views.at(idx); + + // do not change item of active tab + if (next == _active) return; + + next->activate(i); +} + +void MultiView::activated(TraceItemView* sender, CostItem* i) +{ + if (0) qDebug() << "MultiView::activated " << i->name() + << ", sender " << sender->widget()->objectName(); + + // we react only on selection changes of the active TabView + if (sender != (TraceItemView*)_active) return; + + TraceItemView::activated(sender,i); +} + +void MultiView::doUpdate(int changeType, bool force) +{ + foreach(TabView* tv, _views) { + tv->set(changeType, _data, _eventType, _eventType2, + _groupType, _partList, + (tv == _active) ? _activeItem : tv->activeItem(), + tv->selectedItem()); + tv->notifyChange(changeType); + if (tv->isViewVisible()) + tv->updateView(force); + } +} + + +void MultiView::restoreLayout(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + int panelCount = g->value("Panels", 1).toInt();; + QString o = g->value("Orientation", QString("Vertical")).toString(); + QString active = g->value("ActivePanel", QString()).toString(); + + setChildCount(panelCount); + setOrientation( o == QString("Horizontal") ? + Qt::Horizontal : Qt::Vertical ); + if ( panelCount>1 ) { + QList sizes = toIntList(g->value("PanelSizes", QStringList()).toStringList()); + setSizes(sizes); + } + delete g; + + TabView* activeTV = 0; + foreach(TabView* tv, _views) { + if (tv->objectName() == active) activeTV=tv; + tv->restoreLayout( QString("%1-%2").arg(prefix).arg(tv->objectName()), + postfix); + } + + // activate panel after restoring + if (!activeTV) activeTV = _views.first(); + + if (_active == activeTV) + TraceItemView::activated(_active->activeItem()); + else + activeTV->setActive(true); +} + +void MultiView::saveLayout(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + g->setValue("Panels", childCount()); + g->setValue("Orientation", + QString( (orientation() == Qt::Horizontal) ? + "Horizontal" : "Vertical"), + QString("Vertical")); + + g->setValue("PanelSizes", toStringList(sizes())); + g->setValue("ActivePanel", + _active ? QString(_active->objectName()) : QString("none")); + delete g; + + foreach(TabView* tv, _views) + tv->saveLayout(QString("%1-%2").arg(prefix).arg(tv->objectName()), + postfix); +} + + + +void MultiView::restoreOptions(const QString& prefix, const QString& postfix) +{ + foreach(TabView* tv, _views) + tv->restoreOptions(QString("%1-%2").arg(prefix).arg(tv->objectName()), + postfix); +} + +void MultiView::saveOptions(const QString& prefix, const QString& postfix) +{ + foreach(TabView* tv, _views) + tv->saveOptions(QString("%1-%2").arg(prefix).arg(tv->objectName()), + postfix); +} + + +#include "moc_multiview.cpp" diff --git a/kcachegrind/libviews/multiview.h b/kcachegrind/libviews/multiview.h new file mode 100644 index 00000000..528c97f4 --- /dev/null +++ b/kcachegrind/libviews/multiview.h @@ -0,0 +1,70 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * 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 +#include + +#include "traceitemview.h" + +class TabView; + +class MultiView : public QSplitter, public TraceItemView +{ + Q_OBJECT + +public: + explicit MultiView(TopLevelBase* top, QWidget* parent = 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*, CostItem*); + void activated(TraceItemView*, CostItem*); + + void saveLayout(const QString& prefix, const QString& postfix); + void restoreLayout(const QString& prefix, const QString& postfix); + void saveOptions(const QString& prefix, const QString& postfix); + void restoreOptions(const QString& prefix, const QString& postfix); + +public slots: + void tabActivated(TabView*); + + private: + void doUpdate(int, bool); + + TabView* _active; + QList _views; +}; + +#endif diff --git a/kcachegrind/libviews/partgraph.cpp b/kcachegrind/libviews/partgraph.cpp new file mode 100644 index 00000000..ba11cab9 --- /dev/null +++ b/kcachegrind/libviews/partgraph.cpp @@ -0,0 +1,527 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003,2009 Josef Weidendorfer + + 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. +*/ + +/* + * TracePart as Nested Area + */ + +#include "partgraph.h" + +#include + +#include "globalguiconfig.h" +#include "listutils.h" + + +// PartAreaWidget + +PartAreaWidget::PartAreaWidget(QWidget* parent) + : TreeMapWidget(new BasePartItem(), parent) +{ + _data = 0; + _function = 0; + + _eventType = 0; + _groupType = ProfileContext::InvalidType; + _visualization = NoVisualization; + _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::setEventType(EventType* ct) +{ + _eventType = ct; + + // this resizes items + base()->redraw(); +} + +void PartAreaWidget::setVisualization(VisualizationMode m) +{ + _visualization = 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 + TreeMapItemList* l = base()->children(); + if (l) + foreach(TreeMapItem* i, *l) + i->refresh(); + + // but resize part areas + base()->redraw(); +} + + +void PartAreaWidget::setFunction(TraceFunction* f) +{ + _function = f; + + if (_visualization == PartAreaWidget::Inclusive) + refreshParts(); +} + +void PartAreaWidget::setGroupType(ProfileContext::Type gt) +{ + _groupType = gt; + + // rebuild hierarchy below parts. + // thus, selected parts stay selected + TreeMapItemList* l = base()->children(); + if (l) + foreach(TreeMapItem* i, *l) + i->refresh(); + + base()->redraw(); +} + +bool PartAreaWidget::isHidden(TracePart* part) const +{ + return _hiddenParts.contains(part); +} + +QColor PartAreaWidget::groupColor(TraceFunction* f) const +{ + if (!f) + return palette().color( QPalette::Button ); + + return GlobalGUIConfig::functionColor(_groupType, f); +} + +QString PartAreaWidget::tipString(TreeMapItem* i) const +{ + QString tip, itemTip; + int count = 0; + + //qDebug("PartAreaWidget::tipString for '%s'", i->name().toAscii()); + + // first, SubPartItem's + while (i && countrtti() == 3) { + itemTip = GlobalConfig::shortenSymbol(i->text(0)); + + 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 = QObject::tr("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.toAscii(), itemTip.toAscii()); + + 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().toAscii()); + + PartAreaWidget* w = (PartAreaWidget*) widget(); + foreach(TracePart* part, _data->parts()) + if (!w->isHidden(part)) + addItem(new PartItem(part)); +} + + return _children; +} + +QString BasePartItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_data) + return QObject::tr("(no trace)"); + + if (_data->parts().count() == 0) + return QObject::tr("(no part)"); + } + return QString(); +} + + +QColor BasePartItem::backColor() const +{ + return widget()->palette().base().color(); +} + +double BasePartItem::value() const +{ + if (!_data) return 0; + + PartAreaWidget* w = (PartAreaWidget*) widget(); + return (double)_data->subCost(w->eventType()); +} + + + + + +// 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(); + + EventType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + SubCost v; + + ct = w->eventType(); + v = _p->subCost(ct); + + if (GlobalConfig::showPercentage()) { + ProfileCostArray* t = _p->data()->totals(); + double p = 100.0 * v / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', GlobalConfig::percentPrecision()); + } + return v.pretty(); +} + + +QPixmap PartItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + + EventType* ct = ((PartAreaWidget*)widget())->eventType(); + return costPixmap( ct, _p, (double) (_p->data()->totals()->subCost(ct)), false ); +} + + +double PartItem::value() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + EventType* ct = w->eventType(); + if ((w->visualization() == 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->visualization() == PartAreaWidget::Inclusive) { + double s = value(); + //qDebug("PartItem::sum [part %s]: %d", _p->name().toAscii(), s); + return s; + } + return 0.0; +} + +TreeMapItemList* PartItem::children() +{ + if (initialized()) return _children; + + ProfileCostArray* c; +// qDebug("Create Part subitems (%s)", name().toAscii()); + + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualization() == 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 ProfileContext::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 ProfileContext::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 ProfileContext::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 ProfileContext::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(ProfileCostArray* c) +{ + _partCostItem = c; + _factor=1; +} + +QString SubPartItem::text(int textNo) const +{ + if (textNo == 0) { + if (!_partCostItem) + return QObject::tr("(unknown)"); + + return _partCostItem->dependant()->prettyName(); + } + + if (textNo != 1) + return QString(); + + EventType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + SubCost v; + + ct = w->eventType(); + if (w->visualization() == PartAreaWidget::Inclusive) + v = ((TracePartFunction*)_partCostItem)->inclusive()->subCost(ct); + else + v = _partCostItem->subCost(ct); + + if (GlobalConfig::showPercentage()) { + ProfileCostArray* t = GlobalConfig::showExpanded() ? + _partCostItem->part() : _partCostItem->part()->data()->totals(); + double p = 100.0 * v / t->subCost(ct); + return QString("%1 %") + .arg(p, 0, 'f', GlobalConfig::percentPrecision()); + } + return v.pretty(); +} + +QPixmap SubPartItem::pixmap(int i) const +{ + if (i != 1) return QPixmap(); + + // Cost pixmap + + PartAreaWidget* w = (PartAreaWidget*)widget(); + EventType* ct = w->eventType(); + ProfileCostArray* t = GlobalConfig::showExpanded() ? + _partCostItem->part() : _partCostItem->part()->data()->totals(); + ProfileCostArray* c; + if (w->visualization() == PartAreaWidget::Inclusive) + c = ((TracePartFunction*)_partCostItem)->inclusive(); + else + c = _partCostItem; + + return costPixmap( ct, c, (double) (t->subCost(ct)), false ); +} + +double SubPartItem::value() const +{ + EventType* ct; + PartAreaWidget* w = (PartAreaWidget*)widget(); + + ct = w->eventType(); + if (w->visualization() == PartAreaWidget::Inclusive) + return (double) + ((TracePartFunction*)_partCostItem)->inclusive()->subCost(ct); + + return (double) _partCostItem->subCost(ct); +} + +double SubPartItem::sum() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualization() == PartAreaWidget::Inclusive) { + double s = value(); + //qDebug("SubPartItem::sum [Cost %s]: %d", _cost->name().toAscii(), s); + return s; + } + return 0.0; +} + +TreeMapItemList* SubPartItem::children() +{ + if (!initialized()) { +// qDebug("Create Part sub-subitems (%s)", name().toAscii()); + + PartAreaWidget* w = (PartAreaWidget*)widget(); + + if (depth()-2 > w->callLevels()) + return _children; + + if (w->visualization() == PartAreaWidget::Inclusive) { + setSum(value()); + + TracePartCallList l; + l = ((TracePartFunction*)_partCostItem)->partCallings(); + foreach(TracePartCall* call, l) { + TraceFunction* called = call->call()->called(); + ProfileCostArray* partCalled = called->findDepFromPart(call->part()); + if (partCalled) + addItem(new SubPartItem(partCalled)); + } + } + } + + return _children; +} + + +QColor SubPartItem::backColor() const +{ + PartAreaWidget* w = (PartAreaWidget*)widget(); + if (w->visualization() == PartAreaWidget::Inclusive) + return w->groupColor((TraceFunction*)(_partCostItem->dependant())); + + return GlobalGUIConfig::groupColor(_partCostItem->dependant()); +} + + +#include "moc_partgraph.cpp" diff --git a/kcachegrind/libviews/partgraph.h b/kcachegrind/libviews/partgraph.h new file mode 100644 index 00000000..3728c849 --- /dev/null +++ b/kcachegrind/libviews/partgraph.h @@ -0,0 +1,133 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003,2009 Josef Weidendorfer + + 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. +*/ + +/* + * TracePart Graph + */ + +#ifndef PARTGRAPH_H +#define PARTGRAPH_H + +#include + +#include "treemap.h" +#include "tracedata.h" + +class PartAreaWidget: public TreeMapWidget +{ + Q_OBJECT + +public: + // Visualization inside of trace parts + enum VisualizationMode { NoVisualization, Partitioning, Inclusive }; + + PartAreaWidget(QWidget* parent=0); + + void setData(TraceData* d); + void setEventType(EventType* ct); + void setGroupType(ProfileContext::Type gt); + void setVisualization(VisualizationMode); + void setZoomFunction(bool zoomFunction); + void setCallLevels(int callLevels); + void setFunction(TraceFunction* f); + + EventType* eventType() const { return _eventType; } + ProfileContext::Type groupType() const { return _groupType; } + TraceFunction* function() const { return _function; } + VisualizationMode visualization() const { return _visualization; } + 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; + EventType* _eventType; + ProfileContext::Type _groupType; + TraceFunction* _function; + VisualizationMode _visualization; + 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(ProfileCostArray*); + int rtti() const { return 3; } + ProfileCostArray* 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: + ProfileCostArray* _partCostItem; + unsigned int _factor; +}; + + +#endif diff --git a/kcachegrind/libviews/partlistitem.cpp b/kcachegrind/libviews/partlistitem.cpp new file mode 100644 index 00000000..bfe41a18 --- /dev/null +++ b/kcachegrind/libviews/partlistitem.cpp @@ -0,0 +1,168 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011 Josef Weidendorfer + + 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. +*/ + +#include "partlistitem.h" + +#include + +#include + +#include "listutils.h" +#include "coverage.h" +#include "globalconfig.h" + + +// PartListItem + +PartListItem::PartListItem(QTreeWidget* parent, TraceCostItem* costItem, + EventType* et, ProfileContext::Type gt, + TracePart* part) + :QTreeWidgetItem(parent) +{ + _partCostItem = costItem->findDepFromPart(part); + _part = part; + _groupType = gt; + _eventType = et; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + setText(0, _part->prettyName()); + + if (_part->trigger().isEmpty()) + setText(4, QObject::tr("(none)")); + else + setText(4, _part->trigger()); + + update(); +} + +void PartListItem::setEventType(EventType* et) +{ + if (_eventType == et) return; + + _eventType = et; + update(); +} + +void PartListItem::setGroupType(ProfileContext::Type gt) +{ + if (_groupType == gt) return; + + _groupType = gt; + update(); +} + +void PartListItem::update() +{ + TracePartFunction* pf; + pf = !_partCostItem ? 0 : + (_partCostItem->type()==ProfileContext::PartFunction) ? + ((TracePartFunction*)_partCostItem) : 0; + + double total = _part->subCost(_eventType); + + ProfileCostArray* selfTotalCost = _part; + if (pf && GlobalConfig::showExpanded()) { + switch(_groupType) { + case ProfileContext::Object: selfTotalCost = pf->partObject(); break; + case ProfileContext::Class: selfTotalCost = pf->partClass(); break; + case ProfileContext::File: selfTotalCost = pf->partFile(); break; + default: break; + } + } + double selfTotal = selfTotalCost->subCost(_eventType); + + _pure = _partCostItem ? _partCostItem->subCost(_eventType) : SubCost(0); + _sum = pf ? pf->inclusive()->subCost(_eventType) : SubCost(0); + + if (selfTotal == 0 || !_partCostItem) { + setText(2, QString("-")); + setIcon(2, QPixmap()); + } + else { + double pure = 100.0 * _pure / selfTotal; + if (GlobalConfig::showPercentage()) { + setText(2, QString("%1") + .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); + } + else + setText(2, _partCostItem->prettySubCost(_eventType)); + + setIcon(2, costPixmap(_eventType, _partCostItem, selfTotal, false)); + } + + if (total == 0 || !pf) { + setText(1, QString("-")); + setIcon(1, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + if (GlobalConfig::showPercentage()) { + setText(1, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + } + else + setText(1, _sum.pretty()); + + setIcon(1, costPixmap(_eventType, pf->inclusive(), total, false)); + } + + if (!pf) { + setText(3, QString("-")); + _callCount = 0; + return; + } + + SubCost callCount; + QString str; + + callCount = 0; + foreach(TracePartCall* pc, pf->partCallers()) + callCount += pc->callCount(); + + if ((callCount == 0) && (pf->calledContexts()>0)) + str = QObject::tr("(active)"); + else + str = callCount.pretty(); + + _callCount = callCount; + setText(3, str); +} + +bool PartListItem::operator<(const QTreeWidgetItem& other) const +{ + const PartListItem* pi1 = this; + const PartListItem* pi2 = (PartListItem*) &other; + int col = treeWidget()->sortColumn(); + + if (col==0) + return (*(pi1->_part) < *(pi2->_part)); + + if (col==1) + return (pi1->_sum < pi2->_sum); + + if (col==2) + return (pi1->_pure < pi2->_pure); + + if (col==3) + return (pi1->_callCount < pi2->_callCount); + + return QTreeWidgetItem::operator <(other); +} diff --git a/kcachegrind/libviews/partlistitem.h b/kcachegrind/libviews/partlistitem.h new file mode 100644 index 00000000..14f2e875 --- /dev/null +++ b/kcachegrind/libviews/partlistitem.h @@ -0,0 +1,55 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +#ifndef PARTLISTITEM_H +#define PARTLISTITEM_H + +#include + +#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 QTreeWidgetItem +{ +public: + PartListItem(QTreeWidget* parent, TraceCostItem* costItem, + EventType* ct, ProfileContext::Type gt, TracePart* part); + + bool operator<(const QTreeWidgetItem& other) const; + ProfileCostArray* partCostItem() { return _partCostItem; } + void setEventType(EventType* ct); + void setGroupType(ProfileContext::Type); + TracePart* part() { return _part; } + void update(); + +private: + SubCost _sum, _pure; + SubCost _callCount; + EventType* _eventType; + ProfileCostArray* _partCostItem; + TracePart* _part; + ProfileContext::Type _groupType; +}; + +#endif // PARTLISTITEM_H diff --git a/kcachegrind/libviews/partselection.cpp b/kcachegrind/libviews/partselection.cpp new file mode 100644 index 00000000..02268ba7 --- /dev/null +++ b/kcachegrind/libviews/partselection.cpp @@ -0,0 +1,598 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * For part file selection, to be put into a QDockWindow + */ + +#include "partselection.h" + +#include +#include +#include +#include +#include + +#include "toplevelbase.h" +#include "partgraph.h" +#include "globalconfig.h" +#include "config.h" + +// +// PartSelection +// + +// defaults +#define DEFAULT_PARTITIONMODE "Inclusive" +#define DEFAULT_DIAGRAMMODE false +#define DEFAULT_DRAWFRAMES true +#define DEFAULT_SHOWINFO false +#define DEFAULT_FUNCTIONZOOM false +#define DEFAULT_CALLEELEVELS 1 +#define DEFAULT_DRAWNAME true +#define DEFAULT_DRAWCOST true +#define DEFAULT_FORCESTRINGS false +#define DEFAULT_ALLOWROTATION true + +PartSelection::PartSelection( TopLevelBase* top, + QWidget* parent) + : QWidget(parent), TraceItemView(0, top) +{ + _inSelectionUpdate = false; + + setWindowTitle(tr("Parts Overview")); + + QVBoxLayout* vboxLayout = new QVBoxLayout(this); + vboxLayout->setSpacing(6); + vboxLayout->setMargin(6); + + _partAreaWidget = new PartAreaWidget(this); + _partAreaWidget->setMinimumHeight(50); + _partAreaWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _partAreaWidget->setMaxSelectDepth(2); + _partAreaWidget->setSelectionMode(TreeMapWidget::Extended); + _partAreaWidget->setSplitMode(TreeMapItem::HAlternate); + _partAreaWidget->setVisibleWidth(2, true); + _partAreaWidget->setFieldType(0, tr("Name", "A thing's name")); + _partAreaWidget->setFieldType(1, tr("Cost" )); + vboxLayout->addWidget(_partAreaWidget); + + _rangeLabel = new QLabel(this); + _rangeLabel->setWordWrap(false); + _rangeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + vboxLayout->addWidget(_rangeLabel); + _rangeLabel->setText(tr("(no trace parts)")); + _showInfo = true; // label is currently shown + + _diagramMode = DEFAULT_DIAGRAMMODE; + _drawFrames = DEFAULT_DRAWFRAMES; + + // sets _showInfo + showInfo(DEFAULT_SHOWINFO); + + _partAreaWidget->setAllowRotation(DEFAULT_ALLOWROTATION); + + 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 &))); + + setWhatsThis(whatsThis()); +} + +QString PartSelection::whatsThis() const +{ + return tr( "The Parts Overview" + "

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." + "

" + "

The parts are further subdivided: there is a " + "partitioning and an callee split mode: " + "

  • 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.
  • " + "
  • 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.

"); +} + +void PartSelection::setData(TraceData* data) +{ + TraceItemView::setData(data); + _partAreaWidget->setData(data); +} + + +CostItem* PartSelection::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + return i; + default: + break; + } + return 0; +} + +/* Helper for doUpdate(), called on partsChanged event. + * This makes the graph selection the same to the parts in the list + */ +void PartSelection::selectParts(const TracePartList& list) +{ + _inSelectionUpdate = true; + + qDebug("Entering PartSelection::activePartsChangedSlot"); + + TreeMapItemList l = *_partAreaWidget->base()->children(); + // first deselect inactive, then select active (makes current active) + foreach(TreeMapItem* i, l) { + TracePart* part = ((PartItem*)i)->part(); + bool active = list.contains(part); + if (!active && _partAreaWidget->isSelected(i)) { +#if 0 + qDebug("PartSelection::selectParts: Part %s changed to unselected.", + ((PartItem*)i)->part()->shortName()); +#endif + + _partAreaWidget->setSelected(i, false); + } + } + foreach(TreeMapItem* i, l) { + TracePart* part = ((PartItem*)i)->part(); + bool active = list.contains(part); + if (active && !_partAreaWidget->isSelected(i)) { +#if 0 + qDebug("PartSelection::selectParts: Part %s changed to selected.", + ((PartItem*)i)->part()->shortName())); +#endif + _partAreaWidget->setSelected(i, true); + } + } + + _inSelectionUpdate = false; + + qDebug("Leaving PartSelection::activePartsChangedSlot"); +} + + +void PartSelection::doUpdate(int changeType, bool) +{ + if (changeType == eventType2Changed) return; + if (changeType == selectedItemChanged) return; + + if (changeType & eventTypeChanged) + _partAreaWidget->setEventType(_eventType); + + if (changeType & groupTypeChanged) + _partAreaWidget->setGroupType(_groupType); + + if (changeType & activeItemChanged) { + TraceFunction* f = 0; + + if (_activeItem) { + switch(_activeItem->type()) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + f = (TraceFunction*)_activeItem; + break; + default: + break; + } + } + + _inSelectionUpdate = true; + _partAreaWidget->setFunction(f); + _inSelectionUpdate = false; + } + + if (changeType & partsChanged) + selectParts(_partList); + + _partAreaWidget->redraw(); + fillInfo(); +} + + + +void PartSelection::currentChangedSlot(TreeMapItem* i, bool kbd) +{ + if (!i) return; + if (!kbd) return; + if (i->text(0).isEmpty()) return; + + if (_topLevel) { + QString str = i->text(0); + if (!i->text(1).isEmpty()) + str += " (" + i->text(1) + ')'; + QString msg = tr("Profile Part Overview: Current is '%1'").arg(str); + + _topLevel->showMessage(msg, 5000); + } + + if (_showInfo) fillInfo(); +} + + +void PartSelection::doubleClicked(TreeMapItem* i) +{ + if (!i || i->rtti() != 3) return; + + ProfileCostArray* c = ((SubPartItem*) i)->partCostItem(); + TraceCostItem* ci = 0; + + switch(c->type()) { + case ProfileContext::PartFunction: + { + TraceFunction* f = ((TracePartFunction*)c)->function(); + if (f) + activated(f); + } + return; + + case ProfileContext::PartObject: + ci = ((TracePartObject*)c)->object(); + break; + case ProfileContext::PartClass: + ci = ((TracePartClass*)c)->cls(); + break; + case ProfileContext::PartFile: + ci = ((TracePartFile*)c)->file(); + break; + default: + break; + } + + if (ci) + activated(ci); +} + + +void PartSelection::selectionChanged() +{ + if (_inSelectionUpdate) return; + + qDebug("PartSelection::selectionChanged"); + + bool something_changed = false; + bool nothingSelected = true; + + TracePartList pList; + TracePart* part; + + // if nothing is selected, activate all parts + TreeMapItemList* list = _partAreaWidget->base()->children(); + if (!list) return; + + foreach(TreeMapItem* i, *list) + if (_partAreaWidget->isSelected(i)) { + nothingSelected = false; + break; + } + + foreach(TreeMapItem* i, *list) { + 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."); + partsSelected(pList); + } +} + +void PartSelection::itemSelected() +{ + QAction* a = qobject_cast(sender()); + if (a) + doubleClicked( (TreeMapItem*) a->data().value() ); +} + +void PartSelection::contextMenuRequested(TreeMapItem* i, + const QPoint & p) +{ + if (!i) return; + + QMenu popup; + QAction* a; + + QString str; + TreeMapItem* s = 0; + + QAction* selectPartAction = 0; + QAction* selectAllPartsAction = 0; + QAction* hidePartsAction = 0; + QAction* showPartsAction = 0; + if (_data && (_data->parts().count()>1)) { + s = _partAreaWidget->possibleSelection(i); + if (!s->text(0).isEmpty()) { + if (_partAreaWidget->isSelected(s)) + str = tr("Deselect '%1'").arg(s->text(0)); + else + str = tr("Select '%1'").arg(s->text(0)); + + selectPartAction = popup.addAction(str); + } + + selectAllPartsAction = popup.addAction(tr("Select All Parts")); + QMenu* ppopup = popup.addMenu(tr("Visible Parts")); + hidePartsAction = ppopup->addAction(tr("Hide Selected Parts")); + showPartsAction = ppopup->addAction(tr("Show Hidden Parts")); + + popup.addSeparator(); + } + + addGoMenu(&popup); + + if (i->rtti() == 3) { + TreeMapItem* ni = i; + while (ni && ni->rtti() == 3) { + ProfileCostArray* c = ((SubPartItem*)ni)->partCostItem(); + if (c->type() == ProfileContext::PartFunction) + if ( ((TracePartFunction*)c)->function() == _selectedItem) break; + + str = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(ni->text(0))); + a = popup.addAction(str); + a->setData(QVariant::fromValue( (void*)ni )); + connect(a, SIGNAL(triggered()), this, SLOT(itemSelected())); + ni = ni->parent(); + } + } + popup.addSeparator(); + + QMenu* vpopup = popup.addMenu(tr("Visualization")); + QAction* showPartitioningAction = vpopup->addAction(tr("Partitioning Mode")); + showPartitioningAction->setCheckable(true); + QAction* zoomFunctionAction = vpopup->addAction(tr("Zoom Function")); + zoomFunctionAction->setCheckable(true); + QAction* directCallsAction = vpopup->addAction(tr("Show Direct Calls")); + QAction* incCallsAction = vpopup->addAction(tr("Increment Shown Call Levels")); + QAction* showDiagramAction = vpopup->addAction(tr("Diagram Mode")); + showDiagramAction->setCheckable(true); + if (_partAreaWidget->visualization() == PartAreaWidget::Partitioning) { + showPartitioningAction->setChecked(true); + zoomFunctionAction->setEnabled(false); + directCallsAction->setEnabled(false); + incCallsAction->setEnabled(false); + } + else { + zoomFunctionAction->setChecked(_partAreaWidget->zoomFunction()); + } + showDiagramAction->setChecked(_diagramMode); + + vpopup->addSeparator(); + + QAction* drawNamesAction = vpopup->addAction(tr("Draw Names")); + drawNamesAction->setCheckable(true); + QAction* drawCostsAction = vpopup->addAction(tr("Draw Costs")); + drawCostsAction->setCheckable(true); + QAction* ignorePropsAction = vpopup->addAction(tr("Ignore Proportions")); + ignorePropsAction->setCheckable(true); + QAction* allowRotationAction = vpopup->addAction(tr("Allow Rotation")); + allowRotationAction->setCheckable(true); + QAction* drawFramesAction = vpopup->addAction(tr("Draw Frames")); + drawFramesAction->setCheckable(true); + if (!_partAreaWidget->fieldVisible(0) && + !_partAreaWidget->fieldVisible(1)) { + ignorePropsAction->setEnabled(false); + allowRotationAction->setEnabled(false); + } + else { + drawNamesAction->setChecked(_partAreaWidget->fieldVisible(0)); + drawCostsAction->setChecked(_partAreaWidget->fieldVisible(1)); + ignorePropsAction->setChecked(_partAreaWidget->fieldForced(0)); + allowRotationAction->setChecked(_partAreaWidget->allowRotation()); + drawFramesAction->setChecked(_drawFrames); + } + QAction* showInfoAction = popup.addAction(_showInfo ? tr("Hide Info"):tr("Show Info")); + + a = popup.exec(_partAreaWidget->mapToGlobal(p)); + + if (a == selectPartAction) { + // select/deselect part under mouse + _partAreaWidget->setSelected(s, !_partAreaWidget->isSelected(s)); + } + else if (a == selectAllPartsAction) { + // select all parts + TreeMapItemList list = *_partAreaWidget->base()->children(); + _partAreaWidget->setRangeSelection(list.first(), list.last(), true); + } + else if (a == hidePartsAction) + emit partsHideSelected(); + else if (a == showPartsAction) + emit partsUnhideAll(); + else if (a == drawNamesAction) + _partAreaWidget->setFieldVisible(0, !_partAreaWidget->fieldVisible(0)); + else if (a == drawCostsAction) + _partAreaWidget->setFieldVisible(1, !_partAreaWidget->fieldVisible(1)); + else if (a == ignorePropsAction) { + _partAreaWidget->setFieldForced(0, !_partAreaWidget->fieldForced(0)); + _partAreaWidget->setFieldForced(1, !_partAreaWidget->fieldForced(0)); + } + else if (a == allowRotationAction) + _partAreaWidget->setAllowRotation(!_partAreaWidget->allowRotation()); + else if (a == drawFramesAction) { + _drawFrames = !_drawFrames; + _partAreaWidget->drawFrame(2,_drawFrames); + _partAreaWidget->drawFrame(3,_drawFrames); + } + else if (a == showInfoAction) + showInfo(!_showInfo); + else if (a == showPartitioningAction) + _partAreaWidget->setVisualization( + (_partAreaWidget->visualization() != PartAreaWidget::Partitioning) ? + PartAreaWidget::Partitioning : PartAreaWidget::Inclusive ); + else if (a == zoomFunctionAction) { + // zoom/unzoom function + _partAreaWidget->setZoomFunction(!_partAreaWidget->zoomFunction()); + } + else if (a == directCallsAction) + _partAreaWidget->setCallLevels(1); + else if (a == incCallsAction) { + int l = _partAreaWidget->callLevels()+1; + _partAreaWidget->setCallLevels(l); + } + else if (a == showDiagramAction) { + _diagramMode = !_diagramMode; + _partAreaWidget->setTransparent(2,_diagramMode); + } +} + +void PartSelection::hiddenPartsChangedSlot(const TracePartList& list) +{ + _partAreaWidget->changeHidden(list); +} + +void PartSelection::restoreOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + QString pmode = g->value("PartitionMode", + QString(DEFAULT_PARTITIONMODE)).toString(); + if (pmode == "Inclusive") + _partAreaWidget->setVisualization(PartAreaWidget::Inclusive); + else + _partAreaWidget->setVisualization(PartAreaWidget::Partitioning); + + _diagramMode = g->value("DiagramMode", DEFAULT_DIAGRAMMODE).toBool(); + _partAreaWidget->setTransparent(2,_diagramMode); + + _drawFrames = g->value("DrawFrames", DEFAULT_DRAWFRAMES).toBool(); + _partAreaWidget->drawFrame(2,_drawFrames); + _partAreaWidget->drawFrame(3,_drawFrames); + + showInfo(g->value("ShowInfo", DEFAULT_SHOWINFO).toBool()); + + bool enable = g->value("FunctionZoom", DEFAULT_FUNCTIONZOOM).toBool(); + _partAreaWidget->setZoomFunction(enable); + + int levels = g->value("CalleeLevels", DEFAULT_CALLEELEVELS).toInt(); + _partAreaWidget->setCallLevels(levels); + + enable = g->value("DrawName", DEFAULT_DRAWNAME).toBool(); + _partAreaWidget->setFieldVisible(0, enable); + + enable = g->value("DrawCost", DEFAULT_DRAWCOST).toBool(); + _partAreaWidget->setFieldVisible(1, enable); + + enable = g->value("ForceStrings", DEFAULT_FORCESTRINGS).toBool(); + _partAreaWidget->setFieldForced(0, enable); + _partAreaWidget->setFieldForced(1, enable); + + enable = g->value("AllowRotation", DEFAULT_ALLOWROTATION).toBool(); + _partAreaWidget->setAllowRotation(enable); + + delete g; +} + +void PartSelection::saveOptions(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + QString mode; + if (_partAreaWidget->visualization() == PartAreaWidget::Inclusive) + mode = "Inclusive"; + else + mode = "Partitioning"; + + g->setValue("PartitionMode", mode, QString(DEFAULT_PARTITIONMODE)); + g->setValue("DiagramMode", _diagramMode, DEFAULT_DIAGRAMMODE); + g->setValue("DrawFrames", _drawFrames, DEFAULT_DRAWFRAMES); + g->setValue("ShowInfo", _showInfo, DEFAULT_SHOWINFO); + + g->setValue("FunctionZoom", + _partAreaWidget->zoomFunction(), DEFAULT_FUNCTIONZOOM); + g->setValue("CalleeLevels", + _partAreaWidget->callLevels(), DEFAULT_CALLEELEVELS); + g->setValue("DrawName", + _partAreaWidget->fieldVisible(0), DEFAULT_DRAWNAME); + g->setValue("DrawCost", + _partAreaWidget->fieldVisible(1), DEFAULT_DRAWCOST); + g->setValue("ForceStrings", + _partAreaWidget->fieldForced(0), DEFAULT_FORCESTRINGS); + g->setValue("AllowRotation", + _partAreaWidget->allowRotation(), DEFAULT_ALLOWROTATION); + + delete g; +} + +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(tr("(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 "moc_partselection.cpp" diff --git a/kcachegrind/libviews/partselection.h b/kcachegrind/libviews/partselection.h new file mode 100644 index 00000000..fce3e6cd --- /dev/null +++ b/kcachegrind/libviews/partselection.h @@ -0,0 +1,86 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * PartSelection for KCachegrind + * For part file selection, to be put into a QDockWindow + */ + +#ifndef PARTSELECTION_H +#define PARTSELECTION_H + +#include +#include + +#include "tracedata.h" +#include "traceitemview.h" + +class QLabel; +class TraceData; +class TreeMapItem; +class PartAreaWidget; + +class PartSelection: public QWidget, public TraceItemView +{ + Q_OBJECT + +public: + explicit PartSelection(TopLevelBase*, QWidget* parent = 0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + void setData(TraceData*); + + PartAreaWidget* graph() { return _partAreaWidget; } + + void saveOptions(const QString& prefix, const QString& postfix); + void restoreOptions(const QString& prefix, const QString& postfix); + +signals: + void partsHideSelected(); + void partsUnhideAll(); + +public slots: + void selectionChanged(); + void doubleClicked(TreeMapItem*); + void itemSelected(); + void contextMenuRequested(TreeMapItem*, const QPoint &); + void currentChangedSlot(TreeMapItem*, bool); + + void hiddenPartsChangedSlot(const TracePartList& list); + void showInfo(bool); + +private: + // reimplementations of TraceItemView + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + + // helper for doUpdate + void selectParts(const TracePartList& list); + void fillInfo(); + + bool _showInfo; + bool _diagramMode; + bool _drawFrames; + bool _inSelectionUpdate; + + PartAreaWidget* _partAreaWidget; + QLabel* _rangeLabel; +}; + +#endif diff --git a/kcachegrind/libviews/partview.cpp b/kcachegrind/libviews/partview.cpp new file mode 100644 index 00000000..fe1eb5f6 --- /dev/null +++ b/kcachegrind/libviews/partview.cpp @@ -0,0 +1,259 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Part View + */ + + +#include "partview.h" + +#include +#include +#include +#include +#include + +#include "partlistitem.h" +#include "toplevelbase.h" + + +// +// PartView +// + + +PartView::PartView(TraceItemView* parentView, QWidget* parent) + : QTreeWidget(parent), TraceItemView(parentView) +{ + _inSelectionUpdate = false; + + QStringList headerLabels; + headerLabels << tr( "Profile Part" ) + << tr( "Incl." ) + << tr( "Self" ) + << tr( "Called" ) + << tr( "Comment" ); + setHeaderLabels(headerLabels); + + setAllColumnsShowFocus(true); + setRootIsDecorated(false); + setUniformRowHeights(true); + // sorting will be enabled after refresh() + sortByColumn(0, Qt::DescendingOrder); + setMinimumHeight(50); + setSelectionMode(QAbstractItemView::ExtendedSelection); + + connect( this, SIGNAL(itemSelectionChanged()), + SLOT( selectionChangedSlot() ) ); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &) ), + SLOT(context(const QPoint &))); + + connect(header(), SIGNAL(sectionClicked(int)), + this, SLOT(headerClicked(int))); + + setWhatsThis( whatsThis() ); +} + +QString PartView::whatsThis() const +{ + return tr( "Trace Part List" + "

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 of the part (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.

" + "

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.

" + "

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.

" + "

Note that the list is hidden if only one trace " + "part is loaded.

"); +} + + +void PartView::context(const QPoint & p) +{ + QMenu popup; + addGoMenu(&popup); + + // p is in local coordinates + popup.exec(mapToGlobal(p + QPoint(0,header()->height()))); +} + + +void PartView::selectionChangedSlot() +{ + if (_inSelectionUpdate) return; + + TracePartList l; + QList sItems = selectedItems(); + foreach(QTreeWidgetItem* item, sItems) + l.append( ((PartListItem*)item)->part() ); + + // nothing selected means all + if (l.isEmpty()) l = _data->parts(); + + partsSelected(l); +} + +void PartView::headerClicked(int col) +{ + // name columns should be sortable in both ways + if ((col == 0) || (col == 4)) return; + + // all others only descending + sortByColumn(col, Qt::DescendingOrder); +} + +CostItem* PartView::canShow(CostItem* i) +{ + if (!TraceItemView::data()) return 0; + if (TraceItemView::data()->parts().count()>1) return i; + return 0; +} + +void PartView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == eventType2Changed) return; + if (changeType == selectedItemChanged) return; + + if (changeType == groupTypeChanged) { + QTreeWidgetItem *item; + for (int i=0; isetGroupType(_groupType); + } + return; + } + + if (changeType == eventTypeChanged) { +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); +#else + header()->setResizeMode(1, QHeaderView::ResizeToContents); + header()->setResizeMode(2, QHeaderView::ResizeToContents); +#endif + // need to disable sorting! Otherwise each change of shown cost + // can reorders list and change order returned by topLevelItem() + setSortingEnabled(false); + for (int i=0; i< topLevelItemCount(); i++) { + PartListItem* item = (PartListItem*) topLevelItem(i); + item->setEventType(_eventType); + } +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(1, QHeaderView::Interactive); + header()->setSectionResizeMode(2, QHeaderView::Interactive); +#else + header()->setResizeMode(1, QHeaderView::Interactive); + header()->setResizeMode(2, QHeaderView::Interactive); +#endif + setSortingEnabled(true); + header()->setSortIndicatorShown(false); + + return; + } + + if (changeType == partsChanged) { + + TracePart* part; + + _inSelectionUpdate = true; + for (int i=0; i< topLevelItemCount(); i++) { + PartListItem* item = (PartListItem*) topLevelItem(i); + part = ((PartListItem*)item)->part(); + + if (_partList.contains(part)) { + item->setSelected(true); + scrollToItem(item); + } + else + item->setSelected(false); + } + _inSelectionUpdate = false; + + return; + } + + refresh(); +} + +void PartView::refresh() +{ + setColumnWidth(1, 50); + setColumnWidth(2, 50); + + if (!_data || !_activeItem) { + clear(); + return; + } + + ProfileContext::Type t = _activeItem->type(); + TraceFunction* f = 0; + if (t == ProfileContext::Function) f = (TraceFunction*) _activeItem; + if (!f) return; + + TracePartList hidden; + if (_topLevel) + hidden = _topLevel->hiddenParts(); + + _inSelectionUpdate = true; + clear(); + + QList items; + QTreeWidgetItem* item; + foreach(TracePart* part, _data->parts()) { + if (hidden.contains(part)) continue; + item = new PartListItem(0, f, _eventType, _groupType, part); + items.append(item); + } + setSortingEnabled(false); + addTopLevelItems(items); + setSortingEnabled(true); + header()->setSortIndicatorShown(false); + header()->resizeSections(QHeaderView::ResizeToContents); + + foreach(item, items) { + TracePart* part = ((PartListItem*)item)->part(); + if (hidden.contains(part)) continue; + if (part->isActive()) { + item->setSelected(true); + scrollToItem(item); + } + } + + _inSelectionUpdate = false; +} + +#include "moc_partview.cpp" diff --git a/kcachegrind/libviews/partview.h b/kcachegrind/libviews/partview.h new file mode 100644 index 00000000..746b405f --- /dev/null +++ b/kcachegrind/libviews/partview.h @@ -0,0 +1,55 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Part View + */ + +#ifndef PARTVIEW_H +#define PARTVIEW_H + +#include + +#include "tracedata.h" +#include "traceitemview.h" + +class PartView: public QTreeWidget, public TraceItemView +{ + Q_OBJECT + +public: + explicit PartView(TraceItemView* parentView, QWidget* parent=0); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + + void refresh(); + +private slots: + void context(const QPoint &); + void selectionChangedSlot(); + void headerClicked(int); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + + bool _inSelectionUpdate; +}; + +#endif diff --git a/kcachegrind/libviews/sourceitem.cpp b/kcachegrind/libviews/sourceitem.cpp new file mode 100644 index 00000000..2688fb7e --- /dev/null +++ b/kcachegrind/libviews/sourceitem.cpp @@ -0,0 +1,470 @@ +/* This file is part of KCachegrind. + Copyright (C) 2011 Josef Weidendorfer + + 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. +*/ + +/* + * Items of source view. + */ + +#include "sourceitem.h" + +#include +#include +#include +#include +#include + +#include "globalguiconfig.h" +#include "listutils.h" +#include "sourceview.h" + + +// +// SourceItem +// + +// for source lines +SourceItem::SourceItem(SourceView* sv, QTreeWidget* parent, + int fileno, unsigned int lineno, + bool inside, const QString& src, + TraceLine* line) + : QTreeWidgetItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = inside; + _line = line; + _lineCall = 0; + _lineJump = 0; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + 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, QTreeWidgetItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineCall* lineCall) + : QTreeWidgetItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = true; + _line = line; + _lineCall = lineCall; + _lineJump = 0; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().toAscii()); + + SubCost cc = _lineCall->callCount(); + QString callStr = " "; + if (cc==0) + callStr += QObject::tr("Active call to '%1'") + .arg(_lineCall->call()->calledName()); + else + callStr += QObject::tr("%n call(s) to '%2'", "", (uint64)cc) + .arg(_lineCall->call()->calledName()); + + TraceFunction* calledF = _lineCall->call()->called(); + calledF->addPrettyLocation(callStr); + + setText(4, callStr); + + updateGroup(); + updateCost(); +} + +// for jump lines +SourceItem::SourceItem(SourceView* sv, QTreeWidgetItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineJump* lineJump) + : QTreeWidgetItem(parent) +{ + _view = sv; + _lineno = lineno; + _fileno = fileno; + _inside = true; + _line = line; + _lineCall = 0; + _lineJump = lineJump; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + //qDebug("SourceItem: (file %d, line %d) Linecall to %s", + // fileno, lineno, _lineCall->call()->called()->prettyName().toAscii()); + + QString to; + if (_lineJump->lineTo()->functionSource() == _line->functionSource()) + to = _lineJump->lineTo()->name(); + else + to = _lineJump->lineTo()->prettyName(); + + QString jStr; + if (_lineJump->isCondJump()) + jStr = QObject::tr("Jump %1 of %2 times to %3") + .arg(_lineJump->followedCount().pretty()) + .arg(_lineJump->executedCount().pretty()) + .arg(to); + else + jStr = QObject::tr("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 = GlobalGUIConfig::functionColor(_view->groupType(), f); + setIcon(4, colorPixmap(10, 10, c)); +} + +void SourceItem::updateCost() +{ + _pure = SubCost(0); + _pure2 = SubCost(0); + + if (!_line) return; + if (_lineJump) return; + + ProfileCostArray* lineCost = _lineCall ? (ProfileCostArray*)_lineCall : (ProfileCostArray*)_line; + + // do not show any cost inside of cycles + if (_lineCall && + ((_lineCall->call()->inCycle()>0) || + (_lineCall->call()->isRecursion()))) { + QString str; + QPixmap p; + + QString icon = "edit-undo"; +#if 0 // TODO + KIconLoader* loader = KIconLoader::global(); + p= loader->loadIcon(icon, KIconLoader::Small, 0, + KIconLoader::DefaultState, QStringList(), 0, true); +#endif + if (p.isNull()) + str = QObject::tr("(cycle)"); + + setText(1, str); + setIcon(1, p); + setText(2, str); + setIcon(2, p); + return; + } + + ProfileCostArray* totalCost; + if (GlobalConfig::showExpanded()) + totalCost = _line->functionSource()->function()->inclusive(); + else + totalCost = _line->functionSource()->function()->data(); + + EventType* ct = _view->eventType(); + _pure = ct ? lineCost->subCost(ct) : SubCost(0); + if (_pure == 0) { + setText(1, QString()); + setIcon(1, QPixmap()); + } + else { + double total = totalCost->subCost(ct); + double pure = 100.0 * _pure / total; + + if (GlobalConfig::showPercentage()) + setText(1, QString("%1") + .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(1, _pure.pretty()); + + setIcon(1, costPixmap(ct, lineCost, total, false)); + } + + EventType* ct2 = _view->eventType2(); + _pure2 = ct2 ? lineCost->subCost(ct2) : SubCost(0); + if (_pure2 == 0) { + setText(2, QString()); + setIcon(2, QPixmap()); + } + else { + double total = totalCost->subCost(ct2); + double pure2 = 100.0 * _pure2 / total; + + if (GlobalConfig::showPercentage()) + setText(2, QString("%1") + .arg(pure2, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(2, _pure2.pretty()); + + setIcon(2, costPixmap(ct2, lineCost, total, false)); + } +} + + +bool SourceItem::operator < ( const QTreeWidgetItem & other ) const +{ + const SourceItem* si1 = this; + const SourceItem* si2 = (SourceItem*) &other; + int col = treeWidget()->sortColumn(); + + if (col==1) { + return (si1->_pure < si2->_pure); + } + if (col==2) { + return (si1->_pure2 < si2->_pure2); + } + if (col==0) { + // Sort file numbers + if (si1->_fileno < si2->_fileno) return true; + if (si1->_fileno > si2->_fileno) return false; + + // Sort line numbers + if (si1->_lineno < si2->_lineno) return true; + if (si1->_lineno > si2->_lineno) return false; + + // Same line: code gets above calls/jumps + if (!si1->_lineCall && !si1->_lineJump) return true; + if (!si2->_lineCall && !si2->_lineJump) return false; + + // calls above jumps + if (si1->_lineCall && !si2->_lineCall) return true; + if (si2->_lineCall && !si1->_lineCall) return false; + + if (si1->_lineCall && si2->_lineCall) { + // Two calls: desending sort according costs + if (si1->_pure < si2->_pure) return true; + if (si1->_pure > si2->_pure) return false; + + // Two calls: sort according function names + TraceFunction* f1 = si1->_lineCall->call()->called(); + TraceFunction* f2 = si2->_lineCall->call()->called(); + if (f1->prettyName() > f2->prettyName()) return false; + return true; + } + + // Two jumps: descending sort according target line + return (si1->_lineJump->lineTo()->lineno() < + si2->_lineJump->lineTo()->lineno()); + } + return QTreeWidgetItem::operator <(other); +} + +void SourceItem::setJumpArray(const QVector& a) +{ + _jump = a; +} + + + +// +// SourceItemDelegate +// + +SourceItemDelegate::SourceItemDelegate(SourceView *parent) + : QItemDelegate(parent) +{ + _parent = parent; +} + +QSize SourceItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QSize sz = QItemDelegate::sizeHint(option, index); + + int c = index.column(); + if (c != 3) return sz; + + SourceView* sv = (SourceView*) _parent; + int levels = sv->arrowLevels(); + + if (levels == 0) + return QSize(0, sz.height()); + + // 10 pixels for the arrow, 1 pixel margin left and right + return QSize(10 + 6*levels + 2, sz.height()); +} + +void SourceItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + int column = index.column(); + SourceItem* item = static_cast(index.internalPointer()); + + QColor color; + if ( !item->inside() || ((column==1) || (column==2))) + color = option.palette.color( QPalette::Button ); + else if ((item->lineCall() || item->lineJump()) && column>2) + color = option.palette.color( QPalette::Midlight ); + if (color.isValid()) + _parent->model()->setData(index, color, Qt::BackgroundRole); + + if(column==3) + paintArrows(painter, option, index); + else + QItemDelegate::paint(painter, option, index); +} + +void SourceItemDelegate::paintArrows(QPainter *p, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QTreeWidget *lv = _parent; + if ( !lv ) return; + SourceView* sv = (SourceView*) lv; + SourceItem* item = static_cast(index.internalPointer()); + const QRect& rect = option.rect; + int height = rect.height(); + + p->save(); + drawBackground(p, option, index); + p->translate(rect.topLeft()); + + int marg = 1; + int yy = height/2, y1, y2; + QColor c; + + int start = -1, end = -1; + + TraceLineJump* lineJump = item->lineJump(); + uint lineno = item->lineno(); + TraceLineCall* lineCall = item->lineCall(); + + // draw line borders, detect start/stop of a line + for(int i=0; i< item->jumpCount(); i++) { + TraceLineJump* jump = item->jump(i); + if (jump == 0) continue; + + y1 = 0; + y2 = height; + if (lineJump && + (lineJump->lineTo() == jump->lineTo()) && + (jump->lineFrom()->lineno() == lineno)) { + + if (start<0) start = i; + if (lineJump == jump) { + if (jump->lineTo()->lineno() <= lineno) + y2 = yy; + else + y1 = yy; + } + } + else if (!lineJump && !lineCall && + (jump->lineTo()->lineno() == lineno)) { + if (end<0) end = i; + if (jump->lineFrom()->lineno() < lineno) + y2 = yy; + else + y1 = yy; + } + + c = jump->isCondJump() ? Qt::red : Qt::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 = item->jump(start)->isCondJump() ? Qt::red : Qt::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 = item->jump(end)->isCondJump() ? Qt::red : Qt::blue; + x = marg + 6*end; + w = 6*(sv->arrowLevels() - end) + 10; + + QPolygon a; + a.putPoints(0, 8, 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, + x,y+h); + p->setBrush(c); + p->drawConvexPolygon(a); + + p->setPen(c.light()); + p->drawPolyline(a.constData(), 5); + p->setPen(c.dark()); + p->drawPolyline(a.constData() + 4, 2); + p->setPen(c.light()); + p->drawPolyline(a.constData() + 5, 2); + p->setPen(c.dark()); + p->drawPolyline(a.constData() + 6, 2); + } + + // draw inner vertical line for start/stop + // this overwrites borders of horizontal line + for(int i=0;i< item->jumpCount();i++) { + TraceLineJump* jump = item->jump(i); + if (jump == 0) continue; + + c = jump->isCondJump() ? Qt::red : Qt::blue; + + if (jump->lineFrom()->lineno() == lineno) { + bool drawUp = true; + if (jump->lineTo()->lineno() == lineno) + if (start<0) drawUp = false; + if (jump->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->lineTo()->lineno() == lineno) { + if (end<0) end = i; + if (jump->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); + } + } + p->restore(); +} diff --git a/kcachegrind/libviews/sourceitem.h b/kcachegrind/libviews/sourceitem.h new file mode 100644 index 00000000..b15071aa --- /dev/null +++ b/kcachegrind/libviews/sourceitem.h @@ -0,0 +1,102 @@ +/* This file is part of KCachegrind. + Copyright (C) 2011 Josef Weidendorfer + + 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. +*/ + +/* + * Items of source view. + */ + +#ifndef SOURCEITEM_H +#define SOURCEITEM_H + +#include +#include + +#include "tracedata.h" + +class SourceView; + +class SourceItem: public QTreeWidgetItem +{ +public: + // for source lines + SourceItem(SourceView* sv, QTreeWidget* parent, + int fileno, unsigned int lineno, + bool inside, const QString& src, + TraceLine* line = 0); + + // for call lines + SourceItem(SourceView* sv, QTreeWidgetItem* parent, + int fileno, unsigned int lineno, + TraceLine* line, TraceLineCall* lineCall); + + // for jump lines + SourceItem(SourceView* sv, QTreeWidgetItem* 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; } + TraceLineJump* jump(int i) const { return _jump[i]; } + int jumpCount() const { return _jump.size(); } + bool operator< ( const QTreeWidgetItem & other ) const; + + void updateGroup(); + void updateCost(); + + // arrow lines + void setJumpArray(const QVector& a); + + QVector _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; +}; + + +// Delegate for drawing the arrows column + +class SourceItemDelegate : public QItemDelegate +{ +public: + SourceItemDelegate(SourceView *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex & index ) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +protected: + void paintArrows(QPainter *p, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + SourceView* _parent; +}; + + +#endif // SOURCEITEM_H diff --git a/kcachegrind/libviews/sourceview.cpp b/kcachegrind/libviews/sourceview.cpp new file mode 100644 index 00000000..c448e4c8 --- /dev/null +++ b/kcachegrind/libviews/sourceview.cpp @@ -0,0 +1,947 @@ +/* This file is part of KCachegrind. + Copyright (C) 2011 Josef Weidendorfer + + 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. +*/ + +/* + * Source View + */ + +#include "sourceview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "globalconfig.h" +#include "sourceitem.h" + + + +// +// SourceView +// + + +SourceView::SourceView(TraceItemView* parentView, + QWidget* parent) + : QTreeWidget(parent), TraceItemView(parentView) +{ + _inSelectionUpdate = false; + + _arrowLevels = 0; + + + setColumnCount(5); + setRootIsDecorated(false); + setAllColumnsShowFocus(true); + setUniformRowHeights(true); + // collapsing call/jump lines by double-click is confusing + setExpandsOnDoubleClick(false); + + QStringList headerLabels; + headerLabels << tr( "#" ) + << tr( "Cost" ) + << tr( "Cost 2" ) + << "" + << tr( "Source (unknown)"); + setHeaderLabels(headerLabels); + + // sorting will be enabled after refresh() + sortByColumn(0, Qt::AscendingOrder); + header()->setSortIndicatorShown(false); + this->setItemDelegate(new SourceItemDelegate(this)); + this->setWhatsThis( whatsThis()); + + connect( this, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + SLOT( selectedSlot(QTreeWidgetItem*,QTreeWidgetItem*) ) ); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect( this, + SIGNAL(customContextMenuRequested(const QPoint &) ), + SLOT(context(const QPoint &))); + + connect(this, + SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(activatedSlot(QTreeWidgetItem*,int))); + + connect(header(), SIGNAL(sectionClicked(int)), + this, SLOT(headerClicked(int))); +} + +QString SourceView::whatsThis() const +{ + return tr( "Annotated Source" + "

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.

" + "

Select a inserted call information line to " + "make the destination function current.

"); +} + +void SourceView::context(const QPoint & p) +{ + int c = columnAt(p.x()); + QTreeWidgetItem* i = itemAt(p); + QMenu popup; + + 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; + + QAction* activateFunctionAction = 0; + QAction* activateLineAction = 0; + if (f) { + QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(f->prettyName())); + activateFunctionAction = popup.addAction(menuText); + popup.addSeparator(); + } + else if (line) { + QString menuText = tr("Go to Line %1").arg(line->name()); + activateLineAction = popup.addAction(menuText); + popup.addSeparator(); + } + + if ((c == 1) || (c == 2)) { + addEventTypeMenu(&popup); + popup.addSeparator(); + } + addGoMenu(&popup); + + QAction* a = popup.exec(mapToGlobal(p + QPoint(0,header()->height()))); + if (a == activateFunctionAction) + TraceItemView::activated(f); + else if (a == activateLineAction) + TraceItemView::activated(line); +} + + +void SourceView::selectedSlot(QTreeWidgetItem *i, QTreeWidgetItem *) +{ + 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(QTreeWidgetItem* i, int) +{ + if (!i) return; + + TraceLineCall* lc = ((SourceItem*) i)->lineCall(); + TraceLineJump* lj = ((SourceItem*) i)->lineJump(); + + if (!lc && !lj) { + TraceLine* l = ((SourceItem*) i)->line(); + if (l) TraceItemView::activated(l); + return; + } + + TraceFunction* f = lc ? lc->call()->called() : 0; + if (f) TraceItemView::activated(f); + else { + TraceLine* line = lj ? lj->lineTo() : 0; + if (line) TraceItemView::activated(line); + } +} + +void SourceView::keyPressEvent(QKeyEvent* event) +{ + QTreeWidgetItem *item = currentItem(); + if (item && ((event->key() == Qt::Key_Return) || + (event->key() == Qt::Key_Space))) + { + activatedSlot(item, 0); + } + QTreeView::keyPressEvent(event); +} + +CostItem* SourceView::canShow(CostItem* i) +{ + ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType; + + switch(t) { + case ProfileContext::Function: + case ProfileContext::Instr: + case ProfileContext::Line: + return i; + + default: + break; + } + + return 0; +} + +void SourceView::doUpdate(int changeType, bool) +{ + // Special case ? + if (changeType == selectedItemChanged) { + + if (!_selectedItem) { + clearSelection(); + return; + } + + TraceLine* sLine = 0; + if (_selectedItem->type() == ProfileContext::Line) + sLine = (TraceLine*) _selectedItem; + if (_selectedItem->type() == ProfileContext::Instr) + sLine = ((TraceInstr*)_selectedItem)->line(); + if ((_selectedItem->type() != ProfileContext::Function) && (sLine == 0)) + return; + + QList items = selectedItems(); + SourceItem* si = (items.count() > 0) ? (SourceItem*)items[0] : 0; + if (si) { + if (sLine && (si->line() == sLine)) return; + if (si->lineCall() && + (si->lineCall()->call()->called() == _selectedItem)) return; + } + + QTreeWidgetItem *item, *item2; + for (int i=0; iline() == sLine)) { + scrollToItem(item); + _inSelectionUpdate = true; + setCurrentItem(item); + _inSelectionUpdate = false; + break; + } + bool foundCall = false; + for (int j=0; jchildCount(); j++) { + item2 = item->child(j); + si = (SourceItem*)item2; + if (!si->lineCall()) continue; + if (si->lineCall()->call()->called() == _selectedItem) { + scrollToItem(item2); + _inSelectionUpdate = true; + setCurrentItem(item2); + _inSelectionUpdate = false; + foundCall = true; + break; + } + } + if (foundCall) break; + } + return; + } + + if (changeType == groupTypeChanged) { + // update group colors for call lines + QTreeWidgetItem *item, *item2; + for (int i=0; ichildCount(); i++) { + item2 = item->child(j); + ((SourceItem*)item2)->updateGroup(); + } + } + return; + } + + // On eventTypeChanged, we can not just change the costs shown in + // already existing items, as costs of 0 should make the line to not + // be shown at all. So we do a full refresh. + + refresh(); +} + +void SourceView::refresh() +{ + int originalPosition = verticalScrollBar()->value(); + clear(); + setColumnWidth(0, 20); + setColumnWidth(1, 50); + setColumnWidth(2, _eventType2 ? 50:0); + setColumnWidth(3, 0); // arrows, defaults to invisible + if (_eventType) + headerItem()->setText(1, _eventType->name()); + if (_eventType2) + headerItem()->setText(2, _eventType2->name()); + + _arrowLevels = 0; + if (!_data || !_activeItem) { + headerItem()->setText(4, tr("(No Source)")); + return; + } + + ProfileContext::Type t = _activeItem->type(); + TraceFunction* f = 0; + if (t == ProfileContext::Function) f = (TraceFunction*) _activeItem; + if (t == ProfileContext::Instr) { + f = ((TraceInstr*)_activeItem)->function(); + if (!_selectedItem) + _selectedItem = ((TraceInstr*)_activeItem)->line(); + } + if (t == ProfileContext::Line) { + f = ((TraceLine*)_activeItem)->functionSource()->function(); + if (!_selectedItem) _selectedItem = _activeItem; + } + + if (!f) return; + + TraceFunctionSource* mainSF = f->sourceFile(); + + // skip first source if there is 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); + + int fileno = 0; + foreach(TraceFunctionSource* sf, f->sourceFiles()) { + fileno++; + if (sf != mainSF) + fillSourceFile(sf, fileno); + } + + if (!_eventType2) { +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(2, QHeaderView::Interactive); +#else + header()->setResizeMode(2, QHeaderView::Interactive); +#endif + setColumnWidth(2, 0); + } + // reset to the original position - this is useful when the view + // is refreshed just because we change between relative/absolute + // FIXME: this overrides scrolling to selected item + verticalScrollBar()->setValue(originalPosition); +} + + +/* Helper for fillSourceList: + * search recursive for a file, starting from a base dir + * If found, returns true and is set to the file path. + */ +static bool searchFileRecursive(QString& dir, const QString& name) +{ + // we leave this in... + qDebug("Checking %s/%s", qPrintable(dir), qPrintable(name)); + + 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::const_iterator it =subdirs.constBegin(); + for(; it != subdirs.constEnd(); ++it ) { + if (*it == "." || *it == ".." || *it == "CVS") continue; + + dir = d.filePath(*it); + if (searchFileRecursive(dir, name)) return true; + } + return false; +} + +/* Search for a source file in different places. + * If found, returns true and is set to the file path. + */ +bool SourceView::searchFile(QString& dir, + TraceFunctionSource* sf) +{ + QString name = sf->file()->shortName(); + + if (QDir::isAbsolutePath(dir)) { + if (QFile::exists(dir + '/' + name)) return true; + } + else { + /* Directory is relative. Check + * - relative to cwd + * - relative to path of data file + */ + QString base = QDir::currentPath() + '/' + dir; + if (QFile::exists(base + '/' + name)) { + dir = base; + return true; + } + + TracePart* firstPart = _data->parts().first(); + if (firstPart) { + QFileInfo partFile(firstPart->name()); + if (QFileInfo(partFile.absolutePath(), name).exists()) { + dir = partFile.absolutePath(); + return true; + } + } + } + + QStringList list = GlobalConfig::sourceDirs(_data, + sf->function()->object()); + QStringList::const_iterator it; + for ( it = list.constBegin(); it != list.constEnd(); ++it ) { + dir = *it; + if (searchFileRecursive(dir, name)) return true; + } + + return false; +} + + +void SourceView::updateJumpArray(uint lineno, SourceItem* si, + bool ignoreFrom, bool ignoreTo) +{ + uint lowLineno, highLineno; + int iEnd = -1, iStart = -1; + + if (0) qDebug("updateJumpArray(line %d, jump to %s)", + lineno, + si->lineJump() + ? qPrintable(si->lineJump()->lineTo()->name()) : "?" ); + + while(_lowListIter != _lowList.end()) { + TraceLineJump* lj= *_lowListIter; + 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;iStartlineTo() == lj->lineTo())) break; +#else + iStart = asize; +#endif + + if (iStart == asize) { + for(iStart=0;iStart _arrowLevels) _arrowLevels = asize; + } + + if (0) qDebug(" start %d (%s to %s)", + iStart, + qPrintable(lj->lineFrom()->name()), + qPrintable(lj->lineTo()->name())); + + _jump[iStart] = lj; + } + _lowListIter++; + } + + si->setJumpArray(_jump); + + while(_highListIter != _highList.end()) { + TraceLineJump* lj= *_highListIter; + 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; + } + + if (0 && (iEnd>=0)) + qDebug(" end %d (%s to %s)", + iEnd, + qPrintable(_jump[iEnd]->lineFrom()->name()), + qPrintable(_jump[iEnd]->lineTo()->name())); + + if (0 && lj) qDebug("next end: %s to %s", + qPrintable(lj->lineFrom()->name()), + qPrintable(lj->lineTo()->name())); + + _highListIter++; + + if (highLineno > lineno) + break; + else { + if (iEnd>=0) _jump[iEnd] = 0; + iEnd = -1; + } + } + if (iEnd>=0) _jump[iEnd] = 0; +} + + +// compare functions for jump arrow drawing + +void getJumpLines(const TraceLineJump* jump, uint& low, uint& high) +{ + low = jump->lineFrom()->lineno(); + high = jump->lineTo()->lineno(); + + if (low > high) { + uint t = low; + low = high; + high = t; + } +} + +// sort jumps according to lower line number +bool lineJumpLowLessThan(const TraceLineJump* jump1, + const TraceLineJump* jump2) +{ + uint line1Low, line1High, line2Low, line2High; + + getJumpLines(jump1, line1Low, line1High); + getJumpLines(jump2, line2Low, line2High); + + if (line1Low != line2Low) return (line1Low < line2Low); + // jump ends come before jump starts + if (line1Low == jump1->lineTo()->lineno()) return true; + if (line2Low == jump2->lineTo()->lineno()) return false; + return (line1High < line2High); +} + +// sort jumps according to higher line number +bool lineJumpHighLessThan(const TraceLineJump* jump1, + const TraceLineJump* jump2) +{ + uint line1Low, line1High, line2Low, line2High; + + getJumpLines(jump1, line1Low, line1High); + getJumpLines(jump2, line2Low, line2High); + + if (line1High != line2High) return (line1High < line2High); + // jump ends come before jump starts + if (line1High == jump1->lineTo()->lineno()) return true; + if (line2High == jump2->lineTo()->lineno()) return false; + return (line1Low < line2Low); +} + +/* 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 ? qPrintable(_selectedItem->name()) : "(none)"); + + TraceLineMap::Iterator lineIt, lineItEnd; + int nextCostLineno = 0, lastCostLineno = 0; + + bool validSourceFile = (!sf->file()->name().isEmpty()); + + TraceLine* sLine = 0; + if (_selectedItem) { + if (_selectedItem->type() == ProfileContext::Line) + sLine = (TraceLine*) _selectedItem; + if (_selectedItem->type() == ProfileContext::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(_eventType)) break; + if (_eventType2 && (*lineIt).hasCost(_eventType2)) break; + ++lineIt; + } + + nextCostLineno = (lineIt == lineItEnd) ? 0 : (*lineIt).lineno(); + if (nextCostLineno<0) { + qDebug() << "SourceView::fillSourceFile: Negative line number " + << nextCostLineno; + qDebug() << " Function '" << sf->function()->name() << "'"; + qDebug() << " File '" << sf->file()->name() << "'"; + nextCostLineno = 0; + } + + } + + if (nextCostLineno == 0) { + new SourceItem(this, this, fileno, 1, false, + tr("There is no cost of current selected type associated")); + new SourceItem(this, this, fileno, 2, false, + tr("with any source line of this function in file")); + new SourceItem(this, this, fileno, 3, false, + QString(" '%1'").arg(sf->file()->prettyName())); + new SourceItem(this, this, fileno, 4, false, + tr("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 (searchFile(dir, sf)) { + filename = dir + '/' + sf->file()->shortName(); + // no need to search again + sf->file()->setDirectory(dir); + } + else + nextCostLineno = 0; + } + + // do it here, because the source directory could have been set before + if (topLevelItemCount()==0) { + headerItem()->setText(4, validSourceFile ? + tr("Source ('%1')").arg(filename) : + tr("Source (unknown)")); + } + else { + new SourceItem(this, this, fileno, 0, true, + validSourceFile ? + tr("--- Inlined from '%1' ---").arg(filename) : + tr("--- Inlined from unknown source ---")); + } + + if (nextCostLineno == 0) { + new SourceItem(this, this, fileno, 1, false, + tr("There is no source available for the following function:")); + new SourceItem(this, this, fileno, 2, false, + QString(" '%1'").arg(sf->function()->prettyName())); + if (sf->file()->name().isEmpty()) { + new SourceItem(this, this, fileno, 3, false, + tr("This is because no debug information is present.")); + new SourceItem(this, this, fileno, 4, false, + tr("Recompile source and redo the profile run.")); + if (sf->function()->object()) { + new SourceItem(this, this, fileno, 5, false, + tr("The function is located in this ELF object:")); + new SourceItem(this, this, fileno, 6, false, + QString(" '%1'") + .arg(sf->function()->object()->prettyName())); + } + } + else { + new SourceItem(this, this, fileno, 3, false, + tr("This is because its source file cannot be found:")); + new SourceItem(this, this, fileno, 4, false, + QString(" '%1'").arg(sf->file()->name())); + new SourceItem(this, this, fileno, 5, false, + tr("Add the folder of this file to the source folder list.")); + new SourceItem(this, this, fileno, 6, false, + tr("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(_eventType)) break; + if (_eventType2 && (*nextIt).hasCost(_eventType2)) break; + ++nextIt; + } + + TraceLineJumpList jlist = (*it).lineJumps(); + foreach(TraceLineJump* lj, jlist) { + 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; + } + qSort(_lowList.begin(), _lowList.end(), lineJumpLowLessThan); + qSort(_highList.begin(), _highList.end(), lineJumpHighLessThan); + _lowListIter = _lowList.begin(); // iterators to list start + _highListIter = _highList.begin(); + _jump.resize(0); + + char buf[160]; + bool inside = false, skipLineWritten = true; + int readBytes; + int fileLineno = 0; + SubCost most = 0; + + QList items; + TraceLine* currLine; + SourceItem *si, *si2, *item = 0, *first = 0, *selected = 0; + QFile file(filename); + bool fileEndReached = false; + if (!file.open(QIODevice::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<0) fileEndReached = true; + } + + if ((readBytes >0) && (buf[readBytes-1] != '\n')) { + /* Something was read but not ending in newline. I.e. + * - buffer was not big enough: discard rest of line, add "..." + * - this is last line of file, not ending in newline + * NB: checking for '\n' is enough for all systems. + */ + int r; + char buf2[32]; + bool somethingRead = false; + while(1) { + r = file.readLine(buf2, sizeof(buf2)); + if ((r<=0) || (buf2[r-1] == '\n')) break; + somethingRead = true; + } + if (somethingRead) { + // add dots as sign that we truncated the line + Q_ASSERT(readBytes>3); + buf[readBytes-1] = buf[readBytes-2] = buf[readBytes-3] = '.'; + } + } + 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(_eventType)) break; + if (_eventType2 && (*lineIt).hasCost(_eventType2)) 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 - GlobalConfig::noCostInside()) )) + inside = false; + } + + int context = GlobalConfig::context(); + + if ( ((lastCostLineno==0) || (fileLineno > lastCostLineno + context)) && + ((nextCostLineno==0) || (fileLineno < nextCostLineno - context))) { + if ((lineIt == lineItEnd) || fileEndReached) break; + + if (!skipLineWritten) { + skipLineWritten = true; + // a "skipping" line: print "..." instead of a line number + strcpy(buf,"..."); + } + else + continue; + } + else + skipLineWritten = false; + + QString s = QString(buf); + if(s.size() > 0 && s.at(s.length()-1) == '\r') + s = s.left(s.length()-1); + si = new SourceItem(this, 0, + fileno, fileLineno, inside, s, + currLine); + items.append(si); + + if (!currLine) continue; + + if (!selected && (currLine == sLine)) selected = si; + if (!first) first = si; + + if (currLine->subCost(_eventType) > most) { + item = si; + most = currLine->subCost(_eventType); + } + + si->setExpanded(true); + foreach(TraceLineCall* lc, currLine->lineCalls()) { + if ((lc->subCost(_eventType)==0) && + (lc->subCost(_eventType2)==0)) continue; + + if (lc->subCost(_eventType) > most) { + item = si; + most = lc->subCost(_eventType); + } + + si2 = new SourceItem(this, si, fileno, fileLineno, currLine, lc); + + if (!selected && (lc->call()->called() == _selectedItem)) + selected = si2; + } + + foreach(TraceLineJump* lj, currLine->lineJumps()) { + if (lj->executedCount()==0) continue; + + new SourceItem(this, si, fileno, fileLineno, currLine, lj); + } + } + + file.close(); + + // Resize column 0 (line number) and 1/2 (cost) to contents +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); +#else + header()->setResizeMode(0, QHeaderView::ResizeToContents); + header()->setResizeMode(1, QHeaderView::ResizeToContents); + header()->setResizeMode(2, QHeaderView::ResizeToContents); +#endif + + setSortingEnabled(false); + addTopLevelItems(items); + this->expandAll(); + setSortingEnabled(true); + // always reset to line number sort + sortByColumn(0, Qt::AscendingOrder); + header()->setSortIndicatorShown(false); + + // Reallow interactive column size change after resizing to content +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(0, QHeaderView::Interactive); + header()->setSectionResizeMode(1, QHeaderView::Interactive); + header()->setSectionResizeMode(2, QHeaderView::Interactive); +#else + header()->setResizeMode(0, QHeaderView::Interactive); + header()->setResizeMode(1, QHeaderView::Interactive); + header()->setResizeMode(2, QHeaderView::Interactive); +#endif + + if (selected) item = selected; + if (item) first = item; + if (first) { + scrollToItem(first); + _inSelectionUpdate = true; + setCurrentItem(first); + _inSelectionUpdate = false; + } + + // for arrows: go down the list according to list sorting + QTreeWidgetItem *item1, *item2; + for (int i=0; ilineno(), si, true, false); + + for (int j=0; jchildCount(); j++) { + item2 = item1->child(j); + si2 = (SourceItem*)item2; + if (si2->lineJump()) + updateJumpArray(si->lineno(), si2, false, true); + else + si2->setJumpArray(_jump); + } + } + + if (arrowLevels()) + //fix this: setColumnWidth(3, 10 + 6*arrowLevels() + itemMargin() * 2); + setColumnWidth(3, 10 + 6*arrowLevels() + 2); + else + setColumnWidth(3, 0); +} + + +void SourceView::headerClicked(int col) +{ + if (col == 0) { + sortByColumn(col, Qt::AscendingOrder); + } + //All others but Source Text column Descending + else if (col !=4) { + sortByColumn(col, Qt::DescendingOrder); + } +} + +#include "moc_sourceview.cpp" diff --git a/kcachegrind/libviews/sourceview.h b/kcachegrind/libviews/sourceview.h new file mode 100644 index 00000000..12415453 --- /dev/null +++ b/kcachegrind/libviews/sourceview.h @@ -0,0 +1,72 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Source View + */ + +#ifndef SOURCEVIEW_H +#define SOURCEVIEW_H + +#include +#include "traceitemview.h" + +class SourceItem; + +class SourceView : public QTreeWidget, public TraceItemView +{ + friend class SourceItem; + + Q_OBJECT + +public: + explicit SourceView(TraceItemView* parentView, + QWidget* parent = 0); + + QWidget* widget() { return this; } + QString whatsThis() const; + int arrowLevels() { return _arrowLevels; } + +protected slots: + void context(const QPoint &); + void selectedSlot(QTreeWidgetItem*, QTreeWidgetItem*); + void activatedSlot(QTreeWidgetItem*,int); + void headerClicked(int); + +protected: + void keyPressEvent(QKeyEvent* event); + +private: + CostItem* canShow(CostItem*); + void doUpdate(int, bool); + void refresh(); + void updateJumpArray(uint,SourceItem*,bool,bool); + bool searchFile(QString&, TraceFunctionSource*); + void fillSourceFile(TraceFunctionSource*, int); + + bool _inSelectionUpdate; + + // arrows + int _arrowLevels; + // temporary needed on creation... + QVector _jump; + TraceLineJumpList _lowList, _highList; + TraceLineJumpList::iterator _lowListIter, _highListIter; +}; + +#endif diff --git a/kcachegrind/libviews/stackitem.cpp b/kcachegrind/libviews/stackitem.cpp new file mode 100644 index 00000000..f9172f5c --- /dev/null +++ b/kcachegrind/libviews/stackitem.cpp @@ -0,0 +1,125 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Items of stack dockable. + */ + +#include "stackitem.h" + +#include + +#include "globalguiconfig.h" +#include "listutils.h" +#include "stackselection.h" + + +// StackItem + +StackItem::StackItem(StackSelection* ss, + QTreeWidget* parent, TraceFunction* f) + :QTreeWidgetItem(parent) +{ + _view = ss; + _function = f; + _call = 0; + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + updateGroup(); + updateCost(); + + setText(2, QString("-- ")); + setText(3, f->prettyName()); +} + +StackItem::StackItem(StackSelection* ss, + QTreeWidget* parent, TraceCall* call) + :QTreeWidgetItem(parent) +{ + _view = ss; + _call = call; + _function = call->called(); + + setTextAlignment(0, Qt::AlignRight); + setTextAlignment(1, Qt::AlignRight); + setTextAlignment(2, Qt::AlignRight); + + updateGroup(); + updateCost(); + + setText(3, _function->prettyName()); +} + + +void StackItem::updateGroup() +{ + QColor c = GlobalGUIConfig::functionColor(_view->groupType(), + _function); + setIcon(3, colorPixmap(10, 10, c)); +} + +void StackItem::updateCost() +{ + if (!_call) return; + + setText(2, _call->prettyCallCount()); + + EventType* ct = _view->eventType(); + _sum = _call->subCost(ct); + double total = _call->called()->data()->subCost(ct); + if (total == 0.0) { + setText(0, "-"); + setIcon(0, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (GlobalConfig::showPercentage()) + setText(0, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(0, _call->prettySubCost(ct)); + + setIcon(0, costPixmap(ct, _call, total, false)); + } + + // if _eventType2 is 0, column1 is hidden, no change needed + EventType* ct2 = _view->eventType2(); + if (!ct2) return; + + _sum = _call->subCost(ct2); + total = _call->called()->data()->subCost(ct2); + if (total == 0.0) { + setText(1, "-"); + setIcon(1, QPixmap()); + } + else { + double sum = 100.0 * _sum / total; + + if (GlobalConfig::showPercentage()) + setText(1, QString("%1") + .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); + else + setText(1, _call->prettySubCost(ct2)); + + setIcon(1, costPixmap(ct2, _call, total, false)); + } +} diff --git a/kcachegrind/libviews/stackitem.h b/kcachegrind/libviews/stackitem.h new file mode 100644 index 00000000..111da920 --- /dev/null +++ b/kcachegrind/libviews/stackitem.h @@ -0,0 +1,54 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2011, Josef Weidendorfer + + 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. +*/ + +/* + * Items of stack dockable. + */ + +#ifndef STACKITEM_H +#define STACKITEM_H + +#include + +#include "tracedata.h" + +class StackSelection; + + +// for the stack browser + +class StackItem: public QTreeWidgetItem +{ +public: + // for top + StackItem(StackSelection* ss, QTreeWidget* parent, TraceFunction* f); + StackItem(StackSelection* ss, QTreeWidget* 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 // STACKITEM_H diff --git a/kcachegrind/libviews/stackselection.cpp b/kcachegrind/libviews/stackselection.cpp new file mode 100644 index 00000000..b60b7afd --- /dev/null +++ b/kcachegrind/libviews/stackselection.cpp @@ -0,0 +1,268 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * StackSelection for KCachegrind + * For function selection of a most expected stack, + * to be put into a QDockWindow + */ + +#include "stackselection.h" + +#include +#include +#include +#include + +#include "stackbrowser.h" +#include "stackitem.h" + + +StackSelection::StackSelection(QWidget* parent) + : QWidget(parent) +{ + _data = 0; + _browser = new StackBrowser(); + _function = 0; + _eventType = 0; + _eventType2 = 0; + _groupType = ProfileContext::Function; + + setWindowTitle(tr("Stack Selection")); + + QVBoxLayout* vboxLayout = new QVBoxLayout(this); + vboxLayout->setSpacing(6); + vboxLayout->setMargin(3); + + _stackList = new QTreeWidget(this); + QStringList headerLabels; + headerLabels << tr("Cost") + << tr("Cost2") + << tr("Calls") + << tr("Function"); + _stackList->setHeaderLabels(headerLabels); + _stackList->setRootIsDecorated(false); + _stackList->setAllColumnsShowFocus(true); + _stackList->setUniformRowHeights(true); + _stackList->setSortingEnabled(false); + _stackList->setColumnWidth(0, 50); + // 2nd cost column hidden at first (_eventType2 == 0) + _stackList->setColumnWidth(1, 0); + _stackList->setColumnWidth(2, 50); + vboxLayout->addWidget(_stackList); + + connect(_stackList, + SIGNAL( currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, SLOT( stackSelected(QTreeWidgetItem*,QTreeWidgetItem*) ) ); +} + +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; + + //qDebug() << "StackSelection::setFunction " << f->name(); + + 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, _eventType2 ? 50:0); + _stackList->setColumnWidth(2, 50); + if (!item || !item->stack()) return; + + TraceFunction* top = item->stack()->top(); + if (!top) return; + + + QList items; + QTreeWidgetItem* activeItem = 0; + TraceCallList l = item->stack()->calls(); + for(int i=l.count()-1; i>=0; i--) { + StackItem* si = new StackItem(this, 0, l.at(i)); + if (si->function() == item->function()) + activeItem = si; + items.prepend(si); + } + StackItem* si = new StackItem(this, 0, top); + if (si->function() == item->function()) + activeItem = si; + items.prepend(si); + +#if QT_VERSION >= 0x050000 + _stackList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + _stackList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + _stackList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); +#else + _stackList->header()->setResizeMode(0, QHeaderView::ResizeToContents); + _stackList->header()->setResizeMode(1, QHeaderView::ResizeToContents); + _stackList->header()->setResizeMode(2, QHeaderView::ResizeToContents); +#endif + + _stackList->addTopLevelItems(items); + if (activeItem) { + // this calls stackFunctionSelected() + _stackList->setCurrentItem(activeItem); + _stackList->scrollToItem(activeItem); + } + +#if QT_VERSION >= 0x050000 + _stackList->header()->setSectionResizeMode(0, QHeaderView::Interactive); + _stackList->header()->setSectionResizeMode(1, QHeaderView::Interactive); + _stackList->header()->setSectionResizeMode(2, QHeaderView::Interactive); +#else + _stackList->header()->setResizeMode(0, QHeaderView::Interactive); + _stackList->header()->setResizeMode(1, QHeaderView::Interactive); + _stackList->header()->setResizeMode(2, QHeaderView::Interactive); +#endif + + if (!_eventType2) { + _stackList->setColumnWidth(1, 0); + } +} + +void StackSelection::stackSelected(QTreeWidgetItem* i, QTreeWidgetItem*) +{ + 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() +{ +#if QT_VERSION >= 0x050000 + _stackList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + _stackList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); +#else + _stackList->header()->setResizeMode(0, QHeaderView::ResizeToContents); + _stackList->header()->setResizeMode(1, QHeaderView::ResizeToContents); +#endif + + // there is no resorting allowed, so this is save + for(int i = 0; i < _stackList->topLevelItemCount(); i++) { + QTreeWidgetItem* item = _stackList->topLevelItem(i); + ((StackItem*)item)->updateCost(); + } + + if (!_eventType2) { +#if QT_VERSION >= 0x050000 + _stackList->header()->setSectionResizeMode(1, QHeaderView::Interactive); +#else + _stackList->header()->setResizeMode(1, QHeaderView::Interactive); +#endif + _stackList->setColumnWidth(1, 0); + } +} + +void StackSelection::setEventType(EventType* ct) +{ + if (ct == _eventType) return; + _eventType = ct; + + if (_eventType) + _stackList->headerItem()->setText(0, _eventType->name()); + + refresh(); +} + +void StackSelection::setEventType2(EventType* ct) +{ + if (ct == _eventType2) return; + _eventType2 = ct; + + if (_eventType2) + _stackList->headerItem()->setText(1, _eventType2->name()); + + refresh(); +} + +void StackSelection::setGroupType(ProfileContext::Type gt) +{ + if (_groupType == gt) return; + _groupType = gt; + + for(int i = 0; i < _stackList->topLevelItemCount(); i++) { + QTreeWidgetItem* item = _stackList->topLevelItem(i); + ((StackItem*)item)->updateGroup(); + } +} + +#include "moc_stackselection.cpp" diff --git a/kcachegrind/libviews/stackselection.h b/kcachegrind/libviews/stackselection.h new file mode 100644 index 00000000..42280648 --- /dev/null +++ b/kcachegrind/libviews/stackselection.h @@ -0,0 +1,83 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * StackSelection for KCachegrind + * For function selection of a most expected stack, + * to be put into a QDockWindow + */ + +#ifndef STACKSELECTION_H +#define STACKSELECTION_H + +#include +#include "tracedata.h" + +class QTreeWidget; +class QTreeWidgetItem; +class TraceFunction; +class TraceData; +class StackBrowser; + + +class StackSelection : public QWidget +{ + Q_OBJECT + +public: + StackSelection(QWidget* parent = 0); + ~StackSelection(); + + TraceData* data() const { return _data; } + void setData(TraceData*); + StackBrowser* browser() const { return _browser; } + EventType* eventType() { return _eventType; } + EventType* eventType2() { return _eventType2; } + ProfileContext::Type groupType() { return _groupType; } + +signals: + void functionSelected(CostItem*); + +public slots: + void setFunction(TraceFunction*); + void setEventType(EventType*); + void setEventType2(EventType*); + void setGroupType(ProfileContext::Type); + + void stackSelected(QTreeWidgetItem*,QTreeWidgetItem*); + void browserBack(); + void browserForward(); + void browserUp(); + void browserDown(); + void refresh(); + void rebuildStackList(); + +private: + void selectFunction(); + + TraceData* _data; + StackBrowser* _browser; + TraceFunction* _function; + EventType* _eventType; + EventType* _eventType2; + ProfileContext::Type _groupType; + + QTreeWidget* _stackList; +}; + +#endif diff --git a/kcachegrind/libviews/tabview.cpp b/kcachegrind/libviews/tabview.cpp new file mode 100644 index 00000000..9cdc1def --- /dev/null +++ b/kcachegrind/libviews/tabview.cpp @@ -0,0 +1,1039 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Tab View, enclosing detailed views for one trace item in + * two tab widgets, separated by a splitter + */ + + +#include "tabview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "globalconfig.h" +#include "eventtypeview.h" +#include "partview.h" +#include "callview.h" +#include "coverageview.h" +#include "callmapview.h" +#include "instrview.h" +#include "sourceview.h" +#include "callgraphview.h" + + +// defaults for subviews in TabView + +#define DEFAULT_TOPTABS \ + "EventTypeView" << "CallerView" << "AllCallerView" \ + << "CalleeMapView" << "SourceView" +#define DEFAULT_BOTTOMTABS \ + "PartView" << "CalleeView" << "CallGraphView" \ + << "AllCalleeView" << "CallerMapView" << "InstrView" + +#define DEFAULT_ACTIVETOP "CallerView" +#define DEFAULT_ACTIVEBOTTOM "CalleeView" + +#define DEFAULT_RIGHTSIZE 0 +#define DEFAULT_TOPSIZE 50 +#define DEFAULT_LEFTSIZE 0 + +// TabBar + +TabBar::TabBar(TabView* v, QTabWidget* parent, const char *name) : + QTabBar(parent) +{ + setObjectName(name); + _tabWidget = parent; + _tabView = v; +} + +void TabBar::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::RightButton) { + int idx = tabAt(e->pos()); + QWidget* page = 0; + if (idx >=0) { + setCurrentIndex(idx); + page = _tabWidget->widget(idx); + } + context(page, e->globalPos()); + } + QTabBar::mousePressEvent(e ); +} + +void TabBar::context(QWidget* page, const QPoint & pos) +{ + QMenu popup, popup2, popup3; + + QAction* pageToTopAction = 0; + QAction* areaToTopAction = 0; + QAction* showOnTopAction = 0; + QAction* pageToRightAction = 0; + QAction* areaToRightAction = 0; + QAction* showOnRightAction = 0; + QAction* pageToBottomAction = 0; + QAction* areaToBottomAction = 0; + QAction* showOnBottomAction = 0; + QAction* pageToLeftAction = 0; + QAction* areaToLeftAction = 0; + QAction* showOnLeftAction = 0; + QAction* hidePageAction = 0; + QAction* hideAreaAction = 0; + + if (page) { + TraceItemView::Position p = _tabView->tabPosition(page); + if (p != TraceItemView::Top) { + pageToTopAction = popup.addAction(tr("Move to Top")); + areaToTopAction = popup2.addAction(tr("Top", "Move to Top")); + } + if (p != TraceItemView::Right) { + pageToRightAction = popup.addAction(tr("Move to Right")); + areaToRightAction = popup2.addAction(tr("Right", "Move to Right")); + } + if (p != TraceItemView::Bottom) { + pageToBottomAction = popup.addAction(tr("Move to Bottom")); + areaToBottomAction = popup2.addAction(tr("Bottom", "Move to Bottom")); + } + if (p != TraceItemView::Left) { + pageToLeftAction = popup.addAction(tr("Move to Bottom Left")); + areaToLeftAction = popup2.addAction(tr("Bottom Left", "Move to Bottom Left")); + } + popup2.setTitle(tr("Move Area To")); + popup.addMenu(&popup2); + popup.addSeparator(); + hidePageAction = popup.addAction(tr("Hide This Tab")); + hideAreaAction = popup.addAction(tr("Hide Area")); + + if (_tabView->visibleTabs() <2) { + hidePageAction->setEnabled(false); + hideAreaAction->setEnabled(false); + } else if (_tabView->visibleAreas() <2) + hideAreaAction->setEnabled(false); + } + + showOnTopAction = popup3.addAction(tr("Top", "Show on Top")); + showOnRightAction = popup3.addAction(tr("Right", "Show on Right")); + showOnBottomAction = popup3.addAction(tr("Bottom", "Show on Bottom")); + showOnLeftAction = popup3.addAction(tr("Bottom Left", "Show on Bottom Left")); + popup3.setTitle(tr("Show Hidden On")); + popup.addMenu(&popup3); + + QAction* a = popup.exec(pos); + if (a == hidePageAction) + _tabView->moveTab(page, TraceItemView::Hidden, false); + else if (a == hideAreaAction) + _tabView->moveTab(page, TraceItemView::Hidden, true); + + else if (a == pageToTopAction) + _tabView->moveTab(page, TraceItemView::Top, false); + else if (a == pageToRightAction) + _tabView->moveTab(page, TraceItemView::Right, false); + else if (a == pageToBottomAction) + _tabView->moveTab(page, TraceItemView::Bottom, false); + else if (a == pageToLeftAction) + _tabView->moveTab(page, TraceItemView::Left, false); + + else if (a == areaToTopAction) + _tabView->moveTab(page, TraceItemView::Top, true); + else if (a == areaToRightAction) + _tabView->moveTab(page, TraceItemView::Right, true); + else if (a == areaToBottomAction) + _tabView->moveTab(page, TraceItemView::Bottom, true); + else if (a == areaToLeftAction) + _tabView->moveTab(page, TraceItemView::Left, true); + + else if (a == showOnTopAction) + _tabView->moveTab(0, TraceItemView::Top, true); + else if (a == showOnRightAction) + _tabView->moveTab(0, TraceItemView::Right, true); + else if (a == showOnBottomAction) + _tabView->moveTab(0, TraceItemView::Bottom, true); + else if (a == showOnLeftAction) + _tabView->moveTab(0, TraceItemView::Left, true); +} + + +// +// Splitter +// + +Splitter::Splitter(Qt::Orientation o, QWidget* parent) + : QSplitter(o, parent) +{} + +void Splitter::moveEvent(QMoveEvent* e) +{ + QSplitter::moveEvent(e); + + if (0) qDebug("Splitter %s: Move", qPrintable(objectName())); + checkVisiblity(); +} + +void Splitter::checkVisiblity() +{ +#if 0 + const QObjectList l = children(); + QObjectList::Iterator 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(); + } +#endif +} + + + + +// +// TabWidget +// + +TabWidget::TabWidget(TabView* v, QWidget* parent) + : QTabWidget(parent) +{ + _hasVisibleRect = false; + setTabBar(new TabBar(v, this)); +} + +void TabWidget::checkVisibility() +{ + bool hasVisibleRect = (visibleRegion().boundingRect().width()>1) && + (visibleRegion().boundingRect().height()>1); + + if (0) qDebug("TabWidget %s: VR (%dx%d) HasVisibleRect: %s => %s", + qPrintable(objectName()), + visibleRegion().boundingRect().width(), + visibleRegion().boundingRect().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)", + objectName().toLatin1().constData(), + 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", objectName().toLatin1().constData()); + checkVisibility(); +} + +void TabWidget::hideEvent(QHideEvent* e) +{ + QTabWidget::hideEvent(e); + + if (0) qDebug("TabWidget %s: Hide", objectName().toLatin1().constData()); + checkVisibility(); +} + +void TabWidget::moveEvent(QMoveEvent* e) +{ + QTabWidget::moveEvent(e); + + if (0) qDebug("TabWidget %s: Move", objectName().toLatin1().constData()); + checkVisibility(); +} + + + +// +// TabView +// + +/* + * Areas for child views + * + * leftSplitter + * | + * | ----- ----- + * | _/ \_______________/ \____ + * | | Top | Right | + * | | | | + * -> |---------------------| | + * | BottomLeft | Bottom | | + * | | | | + * -\_____/------\____/-------------- + * + * ^ ^ + * bottomSplitter mainSplitter + */ + +TabView::TabView(TraceItemView* parentView, QWidget* parent) + : QWidget(parent), TraceItemView(parentView) +{ + setFocusPolicy(Qt::StrongFocus); + + _isCollapsed = true; + + QVBoxLayout* vbox = new QVBoxLayout( this ); + vbox->setSpacing( 6 ); + vbox->setMargin( 6 ); + + _nameLabel = new QLabel(this); //KSqueezedTextLabel( this); + _nameLabel->setSizePolicy(QSizePolicy( QSizePolicy::Ignored, + QSizePolicy::Fixed )); + _nameLabel->setObjectName( "nameLabel" ); + _nameLabel->setText(tr("(No profile data file loaded)")); + vbox->addWidget( _nameLabel ); + updateNameLabel(tr("(No profile data file loaded)")); + + _mainSplitter = new QSplitter(Qt::Horizontal, this); + _leftSplitter = new Splitter(Qt::Vertical, _mainSplitter); + _leftSplitter->setObjectName("Left"); + vbox->addWidget( _mainSplitter ); + + _rightTW = new TabWidget(this, _mainSplitter); + _rightTW->setObjectName("Right"); + connect(_rightTW, SIGNAL(currentChanged(int)), + this, SLOT(tabChanged(int))); + connect(_rightTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _topTW = new TabWidget(this, _leftSplitter); + _topTW->setObjectName("Top"); + connect(_topTW, SIGNAL(currentChanged(int)), + this, SLOT(tabChanged(int))); + connect(_topTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _bottomSplitter = new Splitter(Qt::Horizontal, _leftSplitter); + _bottomSplitter->setObjectName("Bottom"); + + _leftTW = new TabWidget(this, _bottomSplitter); + _leftTW->setObjectName("Left"); + _leftTW->setTabPosition(QTabWidget::South); + connect(_leftTW, SIGNAL(currentChanged(int)), + this, SLOT(tabChanged(int))); + connect(_leftTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + _bottomTW = new TabWidget(this, _bottomSplitter); + _bottomTW->setObjectName("Bottom"); + _bottomTW->setTabPosition(QTabWidget::South); + connect(_bottomTW, SIGNAL(currentChanged(int)), + this, SLOT(tabChanged(int))); + connect(_bottomTW, SIGNAL(visibleRectChanged(TabWidget*)), + this, SLOT(visibleRectChangedSlot(TabWidget*))); + + CallView* callerView = new CallView(true, this); + CallView* calleeView = new CallView(false, this); + CoverageView * allCallerView = new CoverageView(true, this); + CoverageView * allCalleeView = new CoverageView(false, this); + SourceView* sourceView = new SourceView(this); + InstrView* instrView = new InstrView(this); + PartView* partView = new PartView(this); + + // Options of visualization views are stored by their view name + callerView->setObjectName("CallerView"); + calleeView->setObjectName("CalleeView"); + allCallerView->setObjectName("AllCallerView"); + allCalleeView->setObjectName("AllCalleeView"); + sourceView->setObjectName("SourceView"); + instrView->setObjectName("InstrView"); + partView->setObjectName("PartView"); + + // default positions... + // Keep following order in sync with DEFAULT_xxxTABS defines! + + addTop( addTab( tr("Types"), + new EventTypeView(this, 0, + "EventTypeView"))); + addTop( addTab( tr("Callers"), callerView) ); + addTop( addTab( tr("All Callers"), allCallerView) ); + addTop( addTab( tr("Callee Map"), + new CallMapView(false, this, 0, + "CalleeMapView"))); + addTop( addTab( tr("Source Code"), sourceView) ); + + addBottom( addTab( tr("Parts"), partView ) ); + addBottom( addTab( tr("Callees"), calleeView) ); + addBottom( addTab( tr("Call Graph"), + new CallGraphView(this, 0, + "CallGraphView"))); + addBottom( addTab( tr("All Callees"), allCalleeView) ); + addBottom( addTab( tr("Caller Map"), + new CallMapView(true, this, 0, + "CallerMapView"))); + addBottom( addTab( tr("Machine Code"), instrView) ); + + // after all child widgets are created... + _lastFocus = 0; + _active = false; + installFocusFilters(); + + updateVisibility(); + + this->setWhatsThis( whatsThis() ); +} + +void TabView::updateNameLabel(QString n) +{ + QFontMetrics fm(_nameLabel->fontMetrics()); + + if (!n.isNull()) { + _nameLabelText = n; + _textWidth = fm.width(_nameLabelText); + } + + int labelWidth = _nameLabel->size().width(); + if (_textWidth > labelWidth) { + _nameLabel->setText(fm.elidedText(_nameLabelText, + Qt::ElideMiddle, labelWidth)); + _nameLabel->setToolTip(_nameLabelText); + } + else { + _nameLabel->setText(_nameLabelText); + _nameLabel->setToolTip(QString()); + } + if (!_nameLabelTooltip.isEmpty()) + _nameLabel->setToolTip(_nameLabelTooltip); +} + +void TabView::setData(TraceData* d) +{ + TraceItemView::setData(d); + + foreach(TraceItemView* v, _tabs) + v->setData(d); +} + +TraceItemView* TabView::addTab(const QString& label, TraceItemView* view) +{ + view->setTitle(label); + _tabs.append(view); + return view; +} + +void TabView::addTop(TraceItemView* view) +{ + view->setPosition(TraceItemView::Top); + _topTW->addTab(view->widget(), view->title()); +} + +void TabView::addBottom(TraceItemView* view) +{ + view->setPosition(TraceItemView::Bottom); + _bottomTW->addTab(view->widget(), view->title()); +} + +TraceItemView::Position TabView::tabPosition(QWidget* w) +{ + foreach(TraceItemView* v, _tabs) + if (v->widget() == w) return v->position(); + + return Hidden; +} + +int TabView::visibleTabs() +{ + int c = 0; + foreach(TraceItemView* v, _tabs) { + if (v->position() == Hidden) continue; + c++; + } + return c; +} + +// calculate count of tabs in areas +void TabView::tabCounts(int& top, int& bottom, + int& left, int& right) +{ + top = bottom = left = right = 0; + + foreach(TraceItemView* v, _tabs) { + switch(v->position()) { + case TraceItemView::Top: + top++; + break; + case TraceItemView::Bottom: + bottom++; + break; + case TraceItemView::Left: + left++; + break; + case TraceItemView::Right: + right++; + break; + default: + break; + } + } + + if (0) qDebug("TabView::tabCounts top %d, bottom %d, left %d, right %d", + top, bottom, left, right); +} + +int TabView::visibleAreas() +{ + int count, top, bottom, left, right; + + tabCounts(top, bottom, left, right); + count = 0; + if (top>0) count++; + if (bottom>0) count++; + if (left>0) count++; + if (right>0) count++; + + return count; +} + +// This hides/shows splitters and tabwidgets according to tab children +void TabView::updateVisibility() +{ + int top, bottom, left, right; + + tabCounts(top, bottom, left, right); + + QList s; + s.append(100); + + // children of mainSplitter + if (_rightTW->isHidden() != (right == 0)) { + if (right == 0) { + _rightTW->hide(); + } + else + _rightTW->show(); + } + if (_leftSplitter->isHidden() != (top+bottom+left == 0)) { + if (top+bottom+left == 0) { + _leftSplitter->hide(); + } + else + _leftSplitter->show(); + } + + // children of leftSplitter + if (_topTW->isHidden() != (top == 0)) { + if (top == 0) { + _topTW->hide(); + } + else + _topTW->show(); + } + + if (_bottomSplitter->isHidden() != (bottom+left == 0)) { + if (bottom+left == 0) { + _bottomSplitter->hide(); + } + else + _bottomSplitter->show(); + } + + // children of bottomSplitter + if (_bottomTW->isHidden() != (bottom == 0)) { + if (bottom == 0) { + _bottomTW->hide(); + } + else + _bottomTW->show(); + } + if (_leftTW->isHidden() != (left == 0)) { + if (left == 0) { + _leftTW->hide(); + } + 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) +{ + Position origPos = Hidden; + if (w) { + TraceItemView* found = 0; + foreach(TraceItemView* v, _tabs) + if (v->widget() == w) { found = v; break; } + + if (!found) return; + origPos = found->position(); + } + if (origPos == p) return; + + TabWidget *from, *to; + from = tabWidget(origPos); + to = tabWidget(p); + + QList tabs; + foreach(TraceItemView* v, _tabs) + if ((v->position() == origPos) && + (wholeArea || (v->widget() == w))) tabs.append(v); + + bool isEnabled; + foreach(TraceItemView* v, tabs) { + v->setPosition(p); + w = v->widget(); + + if (from) { + isEnabled = from->isTabEnabled(from->indexOf(w)); + from->removeTab(from->indexOf(w)); + } + else isEnabled = (v->canShow(_activeItem)!=0); + + if (to) { + int idx = -1, i; + foreach(TraceItemView* vv, _tabs) { + if (v == vv) continue; + i = to->indexOf(vv->widget()); + if (i>=0) idx = i; + } + to->insertTab(idx+1, w, v->title()); + to->setTabEnabled(to->indexOf(w), isEnabled); + if (isEnabled) { + to->setCurrentIndex(to->indexOf(w)); + v->updateView(); + } + } + } + updateVisibility(); +} + + +QString TabView::whatsThis() const +{ + return tr( "Information Tabs" + "

This widget shows information for the " + "currently selected function in different tabs: " + "

    " + "
  • The Costs tab shows a list of available event types " + "and the inclusive and self-costs related to these types.
  • " + "
  • The Parts tab shows a list of trace parts " + "if the trace consists of more than one part (otherwise, " + "this tab is hidden). " + "The cost of the selected function spent in the different " + "parts together with the calls happening is shown.
  • " + "
  • The Call Lists tab shows direct callers and " + "callees of the function in more detail.
  • " + "
  • The Coverage tab shows the same as the Call " + "Lists tab, but also shows indirect callers and callees, " + "not just direct ones.
  • " + "
  • The Call Graph tab shows a graphical " + "visualization of the calls made by this function.
  • " + "
  • The Source Code tab presents annotated source code " + "if debugging information and the source file " + "are available.
  • " + "
  • The Machine Code tab presents annotated assembly " + "instructions if profile information at instruction level " + "is available.
" + "For more information, see the What's This? " + "help of the corresponding tab widget.

"); +} + +void TabView::installFocusFilters() +{ + QList wList = findChildren(); + foreach(QWidget* w, wList) { + if (w->focusPolicy() != Qt::NoFocus) + w->installEventFilter(this); + } +} + + +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 ); + // force recalculation of label width by passing current label text + updateNameLabel(_nameLabelText); + + if (0) qDebug("%s::setActive(%s)", objectName().toLatin1().constData(), + a ? "true":"false"); + + if (a) emit tabActivated(this); +} + +void TabView::doUpdate(int changeType, bool force) +{ + if (changeType & (activeItemChanged | + configChanged | + dataChanged)) + { + if (_data && _activeItem) { + _nameLabelTooltip = _activeItem->formattedName(); + updateNameLabel(_activeItem->prettyName()); + } + else { + _nameLabelTooltip = QString(); + updateNameLabel( !_data ? + tr("(No profile data file loaded)") : + tr("(No function selected)")); + } + } + + bool canShow; + foreach(TraceItemView *v, _tabs) { + + 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, _eventType, _eventType2, + _groupType, _partList, + _activeItem, _selectedItem); + v->notifyChange(changeType); + + if (!tw) continue; + int idx = tw->indexOf(v->widget()); + if (tw->isTabEnabled(idx) != canShow) + tw->setTabEnabled(idx, canShow); + + if (v->widget() == tw->currentWidget()) + v->updateView(force); + } +} + + +void TabView::tabChanged(int i) +{ + TabWidget* tw = qobject_cast(sender()); + if (!tw) return; + QWidget* w = tw->widget(i); + + foreach(TraceItemView *v, _tabs) + if (v->widget() == w) v->updateView(); +} + +void TabView::visibleRectChangedSlot(TabWidget* tw) +{ + if (0) qDebug("%s: %svisible !", + tw->objectName().toLatin1().constData(), + tw->hasVisibleRect() ? "":"un"); + + if (tw->hasVisibleRect()) doUpdate(0, false); +} + +void TabView::resizeEvent(QResizeEvent* e) +{ + QWidget::resizeEvent(e); + + updateNameLabel(); + + 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*, CostItem* s) +{ + // we set selected item for our own children + select(s); + + // still forward to parent + if (_parentView) _parentView->selected(this, s); +} + +void TabView::restoreLayout(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + int rightSize = g->value("RightSize", DEFAULT_RIGHTSIZE).toInt(); + int topSize = g->value("TopSize", DEFAULT_TOPSIZE).toInt(); + int leftSize = g->value("LeftSize", DEFAULT_LEFTSIZE).toInt(); + + QList mainSizes, leftSizes, bottomSizes; + + int mainWidth = _mainSplitter->width(); + mainSizes << (100 - rightSize)*mainWidth/100 << rightSize*mainWidth/100; + _mainSplitter->setSizes(mainSizes); + + int leftHeight = _leftSplitter->height(); + leftSizes << topSize*leftHeight/100 << (100 - topSize)*leftHeight/100; + _leftSplitter->setSizes(leftSizes); + + int bottomWidth = _bottomSplitter->width(); + bottomSizes << leftSize*bottomWidth/100 << (100 - leftSize)*bottomWidth/100; + _bottomSplitter->setSizes(bottomSizes); + + QString activeT = g->value("ActiveTop", QString(DEFAULT_ACTIVETOP)).toString(); + QString activeB = g->value("ActiveBottom", QString(DEFAULT_ACTIVEBOTTOM)).toString(); + QString activeL = g->value("ActiveLeft", QString()).toString(); + QString activeR = g->value("ActiveRight", QString()).toString(); + + QStringList topTabsDefault, bottomTabsDefault; + topTabsDefault << DEFAULT_TOPTABS; + bottomTabsDefault << DEFAULT_BOTTOMTABS; + + QStringList topTabs = g->value("TopTabs",topTabsDefault).toStringList(); + QStringList bottomTabs = g->value("BottomTabs",bottomTabsDefault).toStringList(); + QStringList leftTabs = g->value("LeftTabs",QStringList()).toStringList(); + QStringList rightTabs = g->value("RightTabs",QStringList()).toStringList(); + + delete g; + + if (topTabs.isEmpty() && bottomTabs.isEmpty() && + rightTabs.isEmpty() && leftTabs.isEmpty()) { + // no tabs visible ?! Reset to default + topTabs = topTabsDefault; + bottomTabs = bottomTabsDefault; + } + + TraceItemView *activeTop = 0, *activeBottom = 0; + TraceItemView *activeLeft = 0, *activeRight = 0; + + moveTab(0, TraceItemView::Top, true); + foreach(TraceItemView *v, _tabs) { + QString n = v->widget()->objectName(); + 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 (activeTop) + _topTW->setCurrentIndex(_topTW->indexOf(activeTop->widget())); + if (activeBottom) + _bottomTW->setCurrentIndex(_bottomTW->indexOf(activeBottom->widget())); + if (activeLeft) + _leftTW->setCurrentIndex(_leftTW->indexOf(activeLeft->widget())); + if (activeRight) + _rightTW->setCurrentIndex(_rightTW->indexOf(activeRight->widget())); + + if (!_data) return; + + updateView(); +} + +void TabView::saveLayout(const QString& prefix, const QString& postfix) +{ + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + // convert splitter sizes into percentage numbers + QList s; + s = _mainSplitter->sizes(); + int rightSize = (s[0]+s[1]==0) ? 0 : (100 * s[1]/(s[0]+s[1])); + s = _leftSplitter->sizes(); + int topSize = (s[0]+s[1]==0) ? 0 : (100 * s[0]/(s[0]+s[1])); + s = _bottomSplitter->sizes(); + int leftSize = (s[0]+s[1]==0) ? 0 : (100 * s[0]/(s[0]+s[1])); + + g->setValue("RightSize", rightSize, DEFAULT_RIGHTSIZE); + g->setValue("TopSize", topSize, DEFAULT_TOPSIZE); + g->setValue("LeftSize", leftSize, DEFAULT_LEFTSIZE); + + QString a; + QWidget* w; + w = _topTW->currentWidget(); + if ((_topTW->count()>0) && + (_topTW->isTabEnabled(_topTW->indexOf(w)))) + a = w->objectName(); + g->setValue("ActiveTop", a, QString(DEFAULT_ACTIVETOP)); + + a = QString(); + w = _bottomTW->currentWidget(); + if ((_bottomTW->count()>0) && + (_bottomTW->isTabEnabled(_bottomTW->indexOf(w)))) + a = w->objectName(); + g->setValue("ActiveBottom", a, QString(DEFAULT_ACTIVEBOTTOM)); + + a = QString(); + w = _leftTW->currentWidget(); + if ((_leftTW->count()>0) && + (_leftTW->isTabEnabled(_leftTW->indexOf(w)))) + a = w->objectName(); + g->setValue("ActiveLeft", a, QString()); + + a= QString(); + w = _rightTW->currentWidget(); + if ((_rightTW->count()>0) && + (_rightTW->isTabEnabled(_rightTW->indexOf(w)))) + a = w->objectName(); + g->setValue("ActiveRight", a, QString()); + + QStringList topList, bottomList, leftList, rightList; + foreach(TraceItemView *v, _tabs) { + switch(v->position()) { + case TraceItemView::Top: + topList << v->widget()->objectName(); + break; + + case TraceItemView::Bottom: + bottomList << v->widget()->objectName(); + break; + + case TraceItemView::Left: + leftList << v->widget()->objectName(); + break; + + case TraceItemView::Right: + rightList << v->widget()->objectName(); + break; + + default: break; + } + } + + QStringList topTabsDefault, bottomTabsDefault; + topTabsDefault << DEFAULT_TOPTABS; + bottomTabsDefault << DEFAULT_BOTTOMTABS; + + g->setValue("TopTabs", topList, topTabsDefault); + g->setValue("BottomTabs", bottomList, bottomTabsDefault); + g->setValue("LeftTabs", leftList, QStringList()); + g->setValue("RightTabs", rightList, QStringList()); + + delete g; +} + +void TabView::restoreOptions(const QString& prefix, const QString& postfix) +{ + foreach(TraceItemView *v, _tabs) + v->restoreOptions(QString("%1-%2").arg(prefix).arg(v->widget()->objectName()), + postfix); + + if (!_data) return; + + ConfigGroup* g = ConfigStorage::group(prefix, postfix); + + QString activeType = g->value("ActiveItemType", QString()).toString(); + QString activeName = g->value("ActiveItemName", QString()).toString(); + QString selectedType = g->value("SelectedItemType", QString()).toString(); + QString selectedName = g->value("SelectedItemName", QString()).toString(); + + delete g; + + // restore active item + ProfileContext::Type t = ProfileContext::type(activeType); + if (t==ProfileContext::InvalidType) t = ProfileContext::Function; + ProfileCostArray* activeItem = _data->search(t, activeName, _eventType); + if (!activeItem) return; + activated(activeItem); + + // restore selected item + t = ProfileContext::type(selectedType); + if (t==ProfileContext::InvalidType) t = ProfileContext::Function; + ProfileCostArray* selectedItem; + selectedItem = _data->search(t, selectedName, _eventType, activeItem); + if (selectedItem) + selected(this, selectedItem); +} + +void TabView::saveOptions(const QString& prefix, const QString& postfix) +{ + if (_activeItem) { + ConfigGroup* g = ConfigStorage::group(prefix + postfix); + + g->setValue("ActiveItemType", + ProfileContext::typeName(_activeItem->type())); + g->setValue("ActiveItemName", _activeItem->name()); + + if (_selectedItem) { + g->setValue("SelectedItemType", + ProfileContext::typeName(_selectedItem->type())); + g->setValue("SelectedItemName", _selectedItem->name()); + } + delete g; + } + + foreach(TraceItemView *v, _tabs) + v->saveOptions(QString("%1-%2").arg(prefix) + .arg(v->widget()->objectName()), postfix); +} + +#include "moc_tabview.cpp" diff --git a/kcachegrind/libviews/tabview.h b/kcachegrind/libviews/tabview.h new file mode 100644 index 00000000..f5b4e978 --- /dev/null +++ b/kcachegrind/libviews/tabview.h @@ -0,0 +1,185 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Tab View, enclosing detailed views for one trace item in + * two tab widgets, separated by a splitter + */ + +#ifndef TABVIEW_H +#define TABVIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "traceitemview.h" + +class QLabel; +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: + void context(QWidget*, const QPoint &); + + 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: + explicit Splitter(Qt::Orientation o, QWidget* parent = 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: + + explicit TabWidget(TabView*, QWidget* parent = 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: + + explicit TabView( TraceItemView* parentView, + QWidget* parent = 0 ); + + virtual QWidget* widget() { return this; } + QString whatsThis() const; + void setData(TraceData*); + bool isViewVisible() { return !_isCollapsed; } + void selected(TraceItemView*, CostItem*); + bool active() const { return _active; } + void setActive(bool); + + /** + * Rearrange tabs + * if == 0, move hidden tabs + */ + void moveTab(QWidget* w, Position, bool wholeArea = false); + + Position tabPosition(QWidget*); + int visibleTabs(); + int visibleAreas(); + + void saveLayout(const QString& prefix, const QString& postfix); + void restoreLayout(const QString& prefix, const QString& postfix); + void saveOptions(const QString& prefix, const QString& postfix); + void restoreOptions(const QString& prefix, const QString& postfix); + +public slots: + void tabChanged(int); + void visibleRectChangedSlot(TabWidget*); + +signals: + void tabActivated(TabView*); + +protected: + void resizeEvent(QResizeEvent *); + bool eventFilter(QObject*, QEvent*); + void mousePressEvent(QMouseEvent*); + +private: + TraceItemView* addTab(const QString&, TraceItemView*); + void addTop(TraceItemView*); + void addBottom(TraceItemView*); + TabWidget* tabWidget(Position); + void updateVisibility(); + void doUpdate(int, bool); + void updateNameLabel(QString n = QString::null); + void installFocusFilters(); + void tabCounts(int&, int&, int&, int&); + + // this is true if width or height <= 1, and no child updates are done + bool _isCollapsed; + + QLabel* _nameLabel; + QString _nameLabelText, _nameLabelTooltip; + int _textWidth; + + QSplitter *_mainSplitter, *_leftSplitter, *_bottomSplitter; + TabWidget *_topTW, *_leftTW, *_bottomTW, *_rightTW; + QList _tabs; + + QWidget* _lastFocus; + bool _active; +}; + +#endif diff --git a/kcachegrind/libviews/toplevelbase.cpp b/kcachegrind/libviews/toplevelbase.cpp new file mode 100644 index 00000000..ac7a0a04 --- /dev/null +++ b/kcachegrind/libviews/toplevelbase.cpp @@ -0,0 +1,4 @@ +#include "toplevelbase.h" + +TopLevelBase::~TopLevelBase() +{} diff --git a/kcachegrind/libviews/toplevelbase.h b/kcachegrind/libviews/toplevelbase.h new file mode 100644 index 00000000..091b4a2d --- /dev/null +++ b/kcachegrind/libviews/toplevelbase.h @@ -0,0 +1,56 @@ +/* This file is part of KCachegrind. + Copyright (C) 2008 Josef Weidendorfer + + 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. +*/ + +/* + * Abstract base class for top level windows + * - provides services from the top level main window such as action + * bindings for popup menus + * - consumes changes to visualization state + */ + +#ifndef TOPLEVELBASE_H +#define TOPLEVELBASE_H + +#include "tracedata.h" +#include "traceitemview.h" + +class QMenu; + +class TopLevelBase +{ +public: + virtual ~TopLevelBase(); + + /* notify about changes in the visualization state from profile views */ + virtual void activePartsChangedSlot(const TracePartList& list) = 0; + virtual void setTraceItemDelayed(CostItem*) = 0; + virtual void setEventTypeDelayed(EventType*) = 0; + virtual void setEventType2Delayed(EventType*) = 0; + virtual void setGroupTypeDelayed(ProfileContext::Type) = 0; + virtual void setGroupDelayed(TraceCostItem*) = 0; + virtual void setDirectionDelayed(TraceItemView::Direction) = 0; + virtual void configChanged() = 0; + + virtual TracePartList hiddenParts() = 0; + + virtual void addEventTypeMenu(QMenu*,bool) = 0; + virtual void addGoMenu(QMenu*) = 0; + virtual void showMessage(const QString&, int msec) = 0; +}; + +#endif // TOPLEVELBASE_H diff --git a/kcachegrind/libviews/traceitemview.cpp b/kcachegrind/libviews/traceitemview.cpp new file mode 100644 index 00000000..50ebbf1f --- /dev/null +++ b/kcachegrind/libviews/traceitemview.cpp @@ -0,0 +1,476 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2009 Josef Weidendorfer + + 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. +*/ + +/* + * Trace Item View + */ + +#include "traceitemview.h" + +#include +#include + +#include "toplevelbase.h" + +#define TRACE_UPDATES 0 + + +// TraceItemViewUpdateTimer + +TraceItemViewUpdateTimer::TraceItemViewUpdateTimer(TraceItemView* view) +{ + _view = view; + setSingleShot(true); + connect(this, SIGNAL(timeout()), this, SLOT(timeoutTriggered())); +} + +void TraceItemViewUpdateTimer::timeoutTriggered() +{ + _view->triggerUpdate(false); +} + +// TraceItemView + +TraceItemView::TraceItemView(TraceItemView* parentView, TopLevelBase* top) +{ + _parentView = parentView; + _topLevel = top ? top : parentView->topLevel(); + + _data = _newData = 0; + // _partList and _newPartList is empty + _activeItem = _newActiveItem = 0; + _selectedItem = _newSelectedItem = 0; + _eventType = _newEventType = 0; + _eventType2 = _newEventType2 = 0; + _groupType = _newGroupType = ProfileContext::InvalidType; + + _status = nothingChanged; + _needsUpdate = false; + _pos = Hidden; + + _mergeUpdates = true; + _updateTimer = new TraceItemViewUpdateTimer(this); +} + +TraceItemView::~TraceItemView() +{ + delete _updateTimer; +} + +QString TraceItemView::whatsThis() const +{ + return QObject::tr("No description available"); +} + +void TraceItemView::select(CostItem* i) +{ + if (_selectedItem == i) return; + + _newSelectedItem = i; + updateView(); +} + +void TraceItemView::saveLayout(const QString&, const QString&) +{ +#if 0 + qDebug("In '%s', saveLayout not implemented", + widget()->name()); +#endif +} + +void TraceItemView::saveOptions(const QString&, const QString&) +{ +#if 0 + qDebug("In '%s', saveOptions not implemented", + widget()->name()); +#endif +} + +void TraceItemView::restoreLayout(const QString&, const QString&) +{} + +void TraceItemView::restoreOptions(const QString&, const QString&) +{} + + +bool TraceItemView::activate(CostItem* i) +{ + _newActiveItem = canShow(i); + if (_activeItem != _newActiveItem) { + // new item activated, start with empty selection + _newSelectedItem = 0; + updateView(); + } + + return (_newActiveItem != 0); +} + +TraceFunction* TraceItemView::activeFunction() +{ + if (!_activeItem) return 0; + + ProfileContext::Type t = _activeItem->type(); + switch(t) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + return (TraceFunction*) _activeItem; + default: + break; + } + return 0; +} + +bool TraceItemView::set(int changeType, TraceData* d, + EventType* t1, EventType* t2, + ProfileContext::Type g, const TracePartList& l, + CostItem* a, CostItem* s) +{ + _status |= changeType; + _newData = d; + _newGroupType = g; + _newEventType = t1; + _newEventType2 = t2; + _newPartList = l; + _newSelectedItem = s; + _newActiveItem = canShow(a); + if (_activeItem != _newActiveItem) { + // new item activated, start with empty selection + _newSelectedItem = 0; + } + updateView(); + + return (_newActiveItem != 0); +} + + +bool TraceItemView::isViewVisible() +{ + QWidget* w = widget(); + if (w) + return w->isVisible(); + return false; +} + +void TraceItemView::setData(TraceData* d) +{ + if (_data == d) return; + _newData = d; + + // invalidate all pointers to old data + _activeItem = _newActiveItem = 0; + _selectedItem = _newSelectedItem = 0; + _eventType = _newEventType = 0; + _eventType2 = _newEventType2 = 0; + _groupType = _newGroupType = ProfileContext::InvalidType; + _partList.clear(); + _newPartList.clear(); + + // updateView will change this to dataChanged + _status = nothingChanged; + updateView(); +} + +// force: update immediately even if invisible and no change was detected +void TraceItemView::updateView(bool force) +{ + if (!_mergeUpdates || force) { + _needsUpdate = true; + _updateTimer->stop(); + triggerUpdate(force); + return; + } + + // if _needsUpdate is true, an update is already scheduled + if (_needsUpdate) return; + + _needsUpdate = true; + _updateTimer->start(1); +} + + +void TraceItemView::triggerUpdate(bool force) +{ + // If already updated no need to update again. + // This can happen if a update by timer is scheduled, but a "force"d + // update is done before the timer triggers + if (!_needsUpdate) return; + _needsUpdate = false; + + if (!force) { + if (!isViewVisible()) return; + } + + if (_newData != _data) { + _status |= dataChanged; + _data = _newData; + } + else { + _status &= ~dataChanged; + + // if there is 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 is no selection + _selectedItem = 0; + + _status |= activeItemChanged; + _activeItem = _newActiveItem; + } + else + _status &= ~activeItemChanged; + + if (_newEventType != _eventType) { + _status |= eventTypeChanged; + _eventType = _newEventType; + } + else + _status &= ~eventTypeChanged; + + if (_newEventType2 != _eventType2) { + _status |= eventType2Changed; + _eventType2 = _newEventType2; + } + else + _status &= ~eventType2Changed; + + 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 + qDebug("%s::doUpate", + widget() ? qPrintable(widget()->objectName()) : "TraceItemView"); + + if (_status & dataChanged) + qDebug(" data changed"); + + if (_status & configChanged) + qDebug(" config changed"); + + if (_status & partsChanged) + qDebug(" parts changed: %s", qPrintable(_partList[0]->name()) ); + + if (_status & eventTypeChanged) + qDebug(" event type 1 changed: %s", + _eventType ? qPrintable( _eventType->name() ) : "(None)"); + + if (_status & eventType2Changed) + qDebug(" event type 2 changed: %s", + _eventType2 ? qPrintable( _eventType2->name() ) : "(None)"); + + if (_status & groupTypeChanged) + qDebug(" group type changed: %s", + qPrintable(ProfileContext::typeName(_groupType))); + + if (_status & activeItemChanged) + qDebug(" active item changed: %s", + _activeItem ? qPrintable( _activeItem->fullName() ) : "(none)"); + + if (_status & selectedItemChanged) + qDebug(" selected item changed: %s", + _selectedItem ? qPrintable( _selectedItem->fullName() ) : "(none)"); +#endif + + int st = _status; + _status = nothingChanged; + doUpdate(st, force); +} + + +void TraceItemView::selected(TraceItemView* /*sender*/, CostItem* i) +{ +#if 0 // TRACE_UPDATES + qDebug("%s::selected( item %s, sender %s )", + widget() ? qPrintable(widget()->objectName()) : "TraceItemView", + i ? qPrintable( i->name() ): "(none)", + qPrintable(sender->widget()->objectName()) ); +#endif + + if (_parentView) _parentView->selected(this, i); +} + +void TraceItemView::partsSelected(TraceItemView* /*sender*/, const TracePartList& l) +{ +#if 0 // TRACE_UPDATES + qDebug() << (widget() ? widget()->name() : "TraceItemView") + << "::selected " + << l.names() + << ", sender " + << sender->widget()->name(); +#endif + + if (_parentView) + _parentView->partsSelected(this, l); + else + if (_topLevel) _topLevel->activePartsChangedSlot(l); +} + +void TraceItemView::activated(TraceItemView* /*sender*/, CostItem* i) +{ +#if 0 // TRACE_UPDATES + qDebug("%s::activated( item %s, sender %s )", + widget() ? qPrintable(widget()->objectName()) : "TraceItemView", + i ? qPrintable( i->name() ): "(none)", + qPrintable(sender->widget()->objectName()) ); +#endif + + if (_parentView) + _parentView->activated(this, i); + else + if (_topLevel) _topLevel->setTraceItemDelayed(i); +} + +void TraceItemView::selectedEventType(TraceItemView*, EventType* t) +{ + if (_parentView) + _parentView->selectedEventType(this, t); + else + if (_topLevel) _topLevel->setEventTypeDelayed(t); +} + +void TraceItemView::selectedEventType2(TraceItemView*, EventType* t) +{ + if (_parentView) + _parentView->selectedEventType2(this, t); + else + if (_topLevel) _topLevel->setEventType2Delayed(t); +} + +void TraceItemView::selectedGroupType(TraceItemView*, ProfileContext::Type t) +{ + if (_parentView) + _parentView->selectedGroupType(this, t); + else + if (_topLevel) _topLevel->setGroupTypeDelayed(t); +} + + +void TraceItemView::directionActivated(TraceItemView*, TraceItemView::Direction d) +{ + if (_parentView) + _parentView->directionActivated(this, d); + else + if (_topLevel) _topLevel->setDirectionDelayed(d); +} + +void TraceItemView::doUpdate(int, bool) +{ +} + +void TraceItemView::selected(CostItem* i) +{ +#if TRACE_UPDATES + qDebug("%s::selected( item %s )", + widget() ? qPrintable(widget()->objectName()) : "TraceItemView", + i ? qPrintable( i->name() ): "(none)" ); +#endif + + if (_parentView) + _parentView->selected(this, i); + +} + +void TraceItemView::partsSelected(const TracePartList& l) +{ + if (_parentView) + _parentView->partsSelected(this, l); + else + if (_topLevel) _topLevel->activePartsChangedSlot(l); +} + +void TraceItemView::activated(CostItem* i) +{ +#if TRACE_UPDATES + qDebug("%s::activated( item %s )", + widget() ? qPrintable(widget()->objectName()) : "TraceItemView", + i ? qPrintable( i->name() ): "(none)" ); +#endif + + if (_parentView) + _parentView->activated(this, i); + else + if (_topLevel) _topLevel->setTraceItemDelayed(i); +} + +void TraceItemView::selectedEventType(EventType* t) +{ + if (_parentView) + _parentView->selectedEventType(this, t); + else + if (_topLevel) _topLevel->setEventTypeDelayed(t); +} + +void TraceItemView::selectedEventType2(EventType* t) +{ + if (_parentView) + _parentView->selectedEventType2(this, t); + else + if (_topLevel) _topLevel->setEventType2Delayed(t); +} + +void TraceItemView::selectedGroupType(ProfileContext::Type t) +{ + if (_parentView) + _parentView->selectedGroupType(this, t); + else + if (_topLevel) _topLevel->setGroupTypeDelayed(t); +} + +void TraceItemView::directionActivated(TraceItemView::Direction d) +{ + if (_parentView) + _parentView->directionActivated(this, d); + else + if (_topLevel) _topLevel->setDirectionDelayed(d); +} + +void TraceItemView::addEventTypeMenu(QMenu* p, bool withCost2) +{ + if (_topLevel) _topLevel->addEventTypeMenu(p, withCost2); +} + +void TraceItemView::addGoMenu(QMenu* p) +{ + if (_topLevel) _topLevel->addGoMenu(p); +} + +#include "moc_traceitemview.cpp" diff --git a/kcachegrind/libviews/traceitemview.h b/kcachegrind/libviews/traceitemview.h new file mode 100644 index 00000000..9854df39 --- /dev/null +++ b/kcachegrind/libviews/traceitemview.h @@ -0,0 +1,236 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003-2009 Josef Weidendorfer + + 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. +*/ + +/* + * Trace Item View + */ + +#ifndef TRACEITEMVIEW_H +#define TRACEITEMVIEW_H + +#include + +#include "tracedata.h" + +class QWidget; +class QMenu; + +class TopLevelBase; +class TraceItemView; + +/* Helper class for TraceItemView for merging update requests. + * + * This can not be directly done in TraceItemView which can not have slots, + * as this would need TraceItemView to be inherited from QObject. However, + * we want subclasses of TraceItemView to also inherit from QWidget, and + * multiple inheritance of a QObject is impossible + */ +class TraceItemViewUpdateTimer: public QTimer +{ + Q_OBJECT + +public: + explicit TraceItemViewUpdateTimer(TraceItemView* view); + +private slots: + void timeoutTriggered(); + +private: + TraceItemView* _view; +}; + + +/** + * Abstract Base Class for KCachegrind Views + * + * This class delivers the shared functionality of all KCachegrind + * Views for one ProfileCost (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 +{ + friend class TraceItemViewUpdateTimer; + +public: + + /** + * Change type for update functions + * - is used if e.g. cycles are recalculated + */ + enum { nothingChanged = 0, + eventTypeChanged = 1, + eventType2Changed = 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 }; + + explicit TraceItemView(TraceItemView* parentView, TopLevelBase* top = 0); + virtual ~TraceItemView(); + + virtual QString whatsThis() const; + + // visualization layout and options (uses ConfigStorage) + virtual void saveLayout(const QString& prefix, const QString& postfix); + virtual void restoreLayout(const QString& prefix, const QString& postfix); + virtual void saveOptions(const QString& prefix, const QString& postfix); + virtual void restoreOptions(const QString& prefix, const QString& postfix); + + // 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); + + // modify visualization state, updates automatically + void setEventType(EventType* t) { _newEventType = t; updateView(); } + void setEventType2(EventType* t) { _newEventType2 = t; updateView(); } + void set(ProfileContext::Type g) { _newGroupType = g; updateView(); } + void set(const TracePartList& l) { _newPartList = l; updateView(); } + // returns false if nothing can be shown for this trace item + bool activate(CostItem* i); + void select(CostItem* i); + void notifyChange(int changeType) { _status |= changeType; updateView(); } + // all in one + bool set(int, TraceData*, EventType*, EventType*, + ProfileContext::Type, const TracePartList&, + CostItem*, CostItem*); + + // if mergeUpdates is true (default), calls to updateView do not + // directly trigger an update of the view + void setMergeUpdates(bool b) { _mergeUpdates = b; } + + // general update request, call if view is/gets visible + // force: update immediately even if invisible and no change was detected + void updateView(bool force = false); + + /** + * Notification from child views. + * Default implementation notifies parent + */ + virtual void selected(TraceItemView* sender, CostItem*); + virtual void partsSelected(TraceItemView* sender, const TracePartList&); + virtual void directionActivated(TraceItemView* sender, Direction); + virtual void selectedEventType(TraceItemView* sender, EventType*); + virtual void selectedEventType2(TraceItemView* sender, EventType*); + virtual void activated(TraceItemView* sender, CostItem*); + virtual void selectedGroupType(TraceItemView* sender, ProfileContext::Type); + + // getters... + // always get the newest values + TraceData* data() const { return _newData; } + CostItem* activeItem() const { return _newActiveItem; } + CostItem* selectedItem() const { return _newSelectedItem; } + EventType* eventType() const { return _newEventType; } + EventType* eventType2() const { return _newEventType2; } + ProfileContext::Type 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(TopLevelBase* t) { _topLevel = t; } + TopLevelBase* 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; + + /** + * Called when a new item is about to become active. + * Itemviews should reimplement this to notify that a + * given item cannot be shown (return 0) or should be + * redirected to another item to be shown as active. + * + * Use the methods like data() instead of _data here, as + * _data possibly will give old/wrong information. + */ + virtual CostItem* canShow(CostItem* i) { return i; } + + /* convenience functions for often used context menu items */ + void addEventTypeMenu(QMenu*,bool withCost2 = true); + void addGoMenu(QMenu*); + +protected: + // helpers to call selected()/activated() of parentView + void selected(CostItem*); + void partsSelected(const TracePartList&); + void activated(CostItem*); + void selectedEventType(EventType*); + void selectedEventType2(EventType*); + void selectedGroupType(ProfileContext::Type); + void directionActivated(TraceItemView::Direction); + + /* Is this view visible? + * if not, doUpdate() will not be called by updateView() + */ + virtual bool isViewVisible(); + + // update handler (to be reimplemented) + virtual void doUpdate(int changeType, bool force); + + TraceItemView* _parentView; + TopLevelBase* _topLevel; + + TraceData* _data; + TracePartList _partList; + CostItem *_activeItem, *_selectedItem; + EventType *_eventType, *_eventType2; + ProfileContext::Type _groupType; + +private: + /* Multiple update requests via updateView() are merged, and result in one + * call to triggerUpdate() after a timeout (using TraceItemViewUpdateTimer) + */ + void triggerUpdate(bool force); + + TraceData* _newData; + TracePartList _newPartList; + CostItem *_newActiveItem, *_newSelectedItem; + EventType *_newEventType, *_newEventType2; + ProfileContext::Type _newGroupType; + TraceItemViewUpdateTimer* _updateTimer; + + QString _title; + int _status; + bool _mergeUpdates, _needsUpdate; + Position _pos; +}; + + + +#endif diff --git a/kcachegrind/libviews/treemap.cpp b/kcachegrind/libviews/treemap.cpp new file mode 100644 index 00000000..2fa8d46e --- /dev/null +++ b/kcachegrind/libviews/treemap.cpp @@ -0,0 +1,2865 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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 "treemap.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// 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; + _drawFrame = true; + + _backColor = Qt::white; + + // field array has size 0 +} + +StoredDrawParams::StoredDrawParams(const 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(); + + 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) +{ + if (f<0 || f>=MAX_FIELD) return; + + if ((int)_field.size() < f+1) { + int oldSize = _field.size(); + _field.resize(f+1); + while(oldSize < f+1) { + _field[oldSize].pos = Default; + _field[oldSize].maxLines = 0; + oldSize++; + } + } +} + + +void StoredDrawParams::setField(int f, const QString& t, const 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(const 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) +{ + delete _dp; + _dp = dp; +} + +void RectDrawing::setRect(const 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() && (r.width()>0 && r.height()>0)) { + // adjustment for drawRect semantic in Qt4: decrement height/width + r.setRect(r.x(), r.y(), r.width()-1, r.height()-1); + + // some shading + bool goDark = qGray(normal.rgb())>128; + int rBase, gBase, bBase; + normal.getRgb(&rBase, &gBase, &bBase); + p->setBrush(Qt::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 && (r.width()>=0 && r.height()>=0)) { + 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); + factor = 1.0 - ((1.0 - factor) * forth); + } + + // and back (1st half) + while (factor>toBack2 && (r.width()>=0 && r.height()>=0)) { + 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); + factor = 1.0 - ((1.0 - factor) / back1); + } + + // and back (2nd half) + while (factor>.01 && (r.width()>=0 && r.height()>=0)) { + 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); + factor = factor * back2; + } + + normal = shadeColor; + // for filling, width and height has to be incremented again + r.setRect(r.x(), r.y(), r.width()+1, r.height()+1); + } + + // fill inside + p->fillRect(r, normal); +} + + +/* Helper for drawField + * Find a line break position in a string, given a font and maximum width + * + * Returns the actually used width, and sets + */ +static +int findBreak(int& breakPos, QString text, QFontMetrics* fm, int maxWidth) +{ + int usedWidth; + + // does full text fit? + breakPos = text.length(); + usedWidth = fm->width(text); + if (usedWidth < maxWidth) + return usedWidth; + + // binary search for best break position in [bottomPos,breakPos]. + // We want the result of the binary search to be a bit too large + int bottomPos = 0; + while(1) { + int halfPos = (bottomPos + breakPos)/2; + int halfWidth = fm->width(text, halfPos); + if (halfWidth < maxWidth) { + bottomPos = halfPos+1; + continue; + } + breakPos = halfPos; + usedWidth = halfWidth; + if (breakPos - bottomPos <3) break; + } + + // final position by taking break boundaries into account. + // possible break boundaries are changing char categories, + // but not middle of "Aa" + QChar::Category lastCat, cat; + int pos = breakPos; + lastCat = text[pos-1].category(); + // at minimum 2 chars before break + while (pos > 2) { + pos--; + cat = text[pos-1].category(); + if (cat == lastCat) continue; + + // "Aa" has not a possible break inbetween + if ((cat == QChar::Letter_Uppercase) && + (lastCat == QChar::Letter_Lowercase)) { + lastCat = cat; + continue; + } + lastCat = cat; + + breakPos = pos; + usedWidth = fm->width(text, breakPos); + if (usedWidth < maxWidth) break; + } + return usedWidth; +} + + +/* Helper for drawField + * Find last line break position in a string from backwards, + * given a font and maximum width + * + * Returns the actually used width, and sets + */ +static +int findBreakBackwards(int& breakPos, QString text, QFontMetrics* fm, int maxWidth) +{ + int usedWidth; + + // does full text fit? + breakPos = 0; + usedWidth = fm->width(text); + if (usedWidth < maxWidth) + return usedWidth; + + // binary search for best break position in [breakPos,topPos]. + // We want the result of the binary search to be a bit too small + int topPos = text.length(); + while(1) { + int halfPos = (breakPos + topPos)/2; + int halfWidth = fm->width(text.mid(halfPos)); + if (halfWidth > maxWidth) { + topPos = halfPos-1; + continue; + } + breakPos = halfPos; + usedWidth = halfWidth; + if (topPos - breakPos <3) break; + } + + // final position by taking break boundaries into account. + // possible break boundaries are changing char categories but not middle of "Aa" + QChar::Category lastCat, cat; + int pos = breakPos; + lastCat = text[pos].category(); + // at minimum 2 chars before break + while (pos < text.length()-2) { + pos++; + cat = text[pos].category(); + if (cat == lastCat) continue; + + // "Aa" has not a possible break inbetween + if ((lastCat == QChar::Letter_Uppercase) && + (cat == QChar::Letter_Lowercase)) { + lastCat = cat; + continue; + } + lastCat = cat; + + breakPos = pos; + usedWidth = fm->width(text.mid(breakPos)); + if (usedWidth < maxWidth) break; + } + return usedWidth; +} + + + +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) qDebug() << "DrawField: Rect " << r.x() << "/" << r.y() + << " - " << r.width() << "x" << r.height(); + + 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 there is no space available + if (lines<1) return false; + + // calculate free space in first line () + 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 is 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)) { + // do not draw + pixW = 0; + } + else + pixDrawn = false; + } + + // width of text and pixmap to be drawn + int w = pixW + _fm->width(name); + + if (0) qDebug() << " For '" << name << "': Unused " << unused + << ", StrW " << w << ", Width " << width; + + // 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 is 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) { + + // more than one line: search for line break + if (w>width && lines>1) { + int breakPos; + + if (!isBottom) { + w = pixW + findBreak(breakPos, name, _fm, width - pixW); + + remaining = name.mid(breakPos); + // remove space on break point + if (name[breakPos-1].category() == QChar::Separator_Space) + name = name.left(breakPos-1); + else + name = name.left(breakPos); + } + else { // bottom + w = pixW + findBreakBackwards(breakPos, name, _fm, width - pixW); + + remaining = name.left(breakPos); + // remove space on break point + if (name[breakPos].category() == QChar::Separator_Space) + name = name.mid(breakPos+1); + else + name = name.mid(breakPos); + } + } + else + remaining = QString(); + + /* truncate and add ... if needed */ + if (w > width) { + name = _fm->elidedText(name, Qt::ElideRight, width - pixW); + w = _fm->width(name) + pixW; + } + + 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) qDebug() << " Drawing '" << name << "' at " + << x+pixW << "/" << y; + + 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 && (pixYy)) y = pixY; + } + + if (origLines > lines) { + // if only 1 line written, do not 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 +// + + +TreeMapItem* TreeMapItemList::commonParent() +{ + if (isEmpty()) return 0; + + TreeMapItem* parent = first(); + for(int i = 1; parent && icommonParent(at(i)); + + return parent; +} + +class TreeMapItemLessThan +{ +public: + bool operator()(const TreeMapItem* i1, const TreeMapItem* i2) const + { + TreeMapItem* p = i1->parent(); + // should not happen + if (!p) return false; + + bool ascending; + bool result; + int textNo = p->sorting(&ascending); + if (textNo < 0) + result = i1->value() < i2->value(); + else + result = i1->text(textNo) < i2->text(textNo); + + return ascending ? result : !result; + } +}; + +TreeMapItemLessThan treeMapItemLessThan; + +// 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; + + 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, + const QString& text1, const QString& text2, + const QString& text3, const 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; + + if (_parent) _parent->addItem(this); +} + +TreeMapItem::~TreeMapItem() +{ + if (_children) { + qDeleteAll(*_children); + delete _children; + _children = 0; + } + + // 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); + + qDeleteAll(*_children); + delete _children; + _children = 0; + } +} + + +// invalidates current children and forces redraw +// this is only useful 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; + return false; + } + return true; +} + +void TreeMapItem::addItem(TreeMapItem* i) +{ + if (!i) return; + + if (!_children) + _children = new TreeMapItemList; + + i->setParent(this); + + _children->append(i); // preserve insertion order + if (sorting(0) != -1) + qSort(_children->begin(), _children->end(), treeMapItemLessThan); +} + + +// 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 does not do anything + _sortAscending = ascending; + return; + } + } + _sortAscending = ascending; + _sortTextNo = textNo; + + if (_children && _sortTextNo != -1) + qSort(_children->begin(), _children->end(), treeMapItemLessThan); +} + +void TreeMapItem::resort(bool recursive) +{ + if (!_children) return; + + if (_sortTextNo != -1) + qSort(_children->begin(), _children->end(), treeMapItemLessThan); + + if (recursive) + foreach(TreeMapItem* i, *_children) + 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; + + return _children; +} + +void TreeMapItem::clearItemRect() +{ + _rect = QRect(); + clearFreeRects(); +} + +void TreeMapItem::clearFreeRects() +{ + _freeRects.clear(); +} + +void TreeMapItem::addFreeRect(const QRect& r) +{ + // do not add invalid rects + if ((r.width() < 1) || (r.height() < 1)) return; + + if (0) qDebug() << "addFree(" << path(0).join("/") << ", " + << r.x() << "/" << r.y() << "-" + << r.width() << "x" << r.height() << ")"; + + if (_freeRects.isEmpty()) { + _freeRects.append(r); + return; + } + + // join rect with last rect if possible + // this saves memory and does not make the tooltip flicker + QRect& last = _freeRects.last(); + 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(r); + return; + } + + if (0) qDebug() << " united with last to (" + << last.x() << "/" << last.y() << "-" + << last.width() << "x" << last.height() << ")"; +} + + + +// TreeMapWidget + +TreeMapWidget::TreeMapWidget(TreeMapItem* base, + QWidget* parent) + : QWidget(parent) +{ + _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; + + setAttribute(Qt::WA_NoSystemBackground, true); + setFocusPolicy(Qt::StrongFocus); +} + +TreeMapWidget::~TreeMapWidget() +{ + delete _base; +} + +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(const 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 tr("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()) { + int oldSize = _attr.size(); + _attr.resize(size); + while (oldSizeparent() 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 +{ + + if (!rect().contains(x, y)) return 0; + if (DEBUG_DRAWING) qDebug() << "item(" << x << "," << y << "):"; + + TreeMapItem* p = _base; + TreeMapItem* i; + while (1) { + TreeMapItemList* list = p->children(); + i = 0; + if (list) { + int idx; + for (idx=0; idxsize(); idx++) { + i = list->at(idx); + + if (DEBUG_DRAWING) + qDebug() << " Checking " << i->path(0).join("/") << " (" + << i->itemRect().x() << "/" << i->itemRect().y() + << "-" << i->itemRect().width() + << "x" << i->itemRect().height() << ")"; + + if (i->itemRect().contains(x, y)) { + + if (DEBUG_DRAWING) qDebug() << " .. Got. Index " << idx; + + p->setIndex(idx); + break; + } + } + if (idx == list->size()) i = 0; // not contained in child + } + + if (!i) { + static TreeMapItem* last = 0; + if (p != last) { + last = p; + + if (DEBUG_DRAWING) + qDebug() << "item(" << x << "," << y << "): Got " + << p->path(0).join("/") << " (Size " + << p->itemRect().width() << "x" << p->itemRect().height() + << ", Val " << p->value() << ")"; + } + + 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()->indexOf(i); + idx--; + if (idx<0) + i = p; + else + i = p->children()->at(idx); + } + } + return i; +} + +void TreeMapWidget::setSelected(TreeMapItem* item, bool selected) +{ + if (!item) return; + 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) qDebug() << (selected ? "S":"Des") << "elected Item " + << (item ? item->path(0).join("") : QString("(null)")) + << " (depth " << (item ? item->depth() : -1) + << ")"; +} + +void TreeMapWidget::setMarked(int markNo, bool redrawWidget) +{ + // if there is 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; + + foreach(TreeMapItem* i, l1) + if (!l2.contains(i)) + l.append(i); + + foreach(TreeMapItem* i, l2) + if (!l1.contains(i)) + l.append(i); + + 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) { + // first remove any selection which is parent or child of + foreach(TreeMapItem* i, _tmpSelection) + if (i->isChildOf(item) || item->isChildOf(i)) + _tmpSelection.removeAll(i); + + _tmpSelection.append(item); + } + else + _tmpSelection.removeAll(item); + } + + return diff(old, _tmpSelection).commonParent(); +} + + +bool TreeMapWidget::clearSelection(TreeMapItem* parent) +{ + TreeMapItemList old = _selection; + + // remove any selection which is child of + foreach(TreeMapItem* i, _selection) + if (i->isChildOf(parent)) + _selection.removeAll(i); + + TreeMapItem* changed = diff(old, _selection).commonParent(); + if (changed) { + changed->redraw(); + emit selectionChanged(); + } + return (changed != 0); +} + +bool TreeMapWidget::isSelected(TreeMapItem* i) const +{ + if (!i) return false; + return _selection.contains(i); +} + +bool TreeMapWidget::isTmpSelected(TreeMapItem* i) +{ + if (!i) return false; + return _tmpSelection.contains(i); +} + + +void TreeMapWidget::setCurrent(TreeMapItem* i, bool kbd) +{ + TreeMapItem* old = _current; + _current = i; + + if (_markNo >0) { + // remove mark + _markNo = 0; + + if (i) qDebug() << "setCurrent(" << i->path(0).join("/") + << ") - mark removed"; + + // 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(); + } + + //qDebug() << "Current Item " << (i ? qPrintable(i->path()) : "(null)"); + + 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; + + bool between = false; + foreach(TreeMapItem* i, *list) { + 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; + } + + return changed; +} + +void TreeMapWidget::contextMenuEvent( QContextMenuEvent* e ) +{ + //qDebug() << "TreeMapWidget::contextMenuEvent"; + + 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 ) +{ + //qDebug() << "TreeMapWidget::mousePressEvent"; + + _oldCurrent = _current; + + TreeMapItem* i = item(e->x(), e->y()); + + _pressed = i; + + _inShiftDrag = e->modifiers() & Qt::ShiftModifier; + _inControlDrag = e->modifiers() & Qt::ControlModifier; + _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() == Qt::RightButton) { + TreeMapItem* changed2 = setTmpSelected(item, true); + if (changed2) changed = changed2->commonParent(changed); + } + + setCurrent(_pressed); + + if (changed) + redraw(changed); + + if (e->button() == Qt::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 ) +{ + //qDebug() << "TreeMapWidget::mouseMoveEvent"; + + 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* ) +{ + //qDebug() << "TreeMapWidget::mouseReleaseEvent"; + + 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()->indexOf(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()->indexOf(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() == Qt::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() == Qt::Key_Space) || + (e->key() == Qt::Key_Return)) { + + switch(_selectionMode) { + case NoSelection: + break; + case Single: + setSelected(_current, true); + break; + case Multi: + setSelected(_current, !isSelected(_current)); + break; + case Extended: + if ((e->modifiers() & Qt::ControlModifier) || + (e->modifiers() & Qt::ShiftModifier)) + setSelected(_current, !isSelected(_current)); + else { + _selectionMode = Single; + setSelected(_current, true); + _selectionMode = Extended; + } + } + + if (_current && (e->key() == Qt::Key_Return)) + emit returnPressed(_current); + + return; + } + + if (!_current) { + if (e->key() == Qt::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() == Qt::Key_Backspace) || + (e->key() == Qt::Key_Up)) { + newItem = visibleItem(p); + setCurrent(newItem, true); + } + else if (e->key() == Qt::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() == Qt::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() == Qt::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->modifiers() & Qt::ControlModifier)) return; + if (! (e->modifiers() & Qt::ShiftModifier)) return; + + switch(_selectionMode) { + case NoSelection: + break; + case Single: + setSelected(_current, true); + break; + case Multi: + setSelected(_current, !isSelected(_current)); + break; + case Extended: + if (e->modifiers() & Qt::ControlModifier) + setSelected(_current, !isSelected(_current)); + else + setSelected(_current, isSelected(old)); + } +} + +void TreeMapWidget::fontChange( const QFont& ) +{ + redraw(); +} + +// react on tooltip events +bool TreeMapWidget::event(QEvent *event) +{ + if (event->type() == QEvent::ToolTip) { + QHelpEvent *helpEvent = static_cast(event); + TreeMapItem* i = item(helpEvent->pos().x(), helpEvent->pos().y()); + bool hasTip = false; + if (i) { + const QList& rList = i->freeRects(); + foreach(const QRect& r, rList) { + if (r.contains(helpEvent->pos())) { + hasTip = true; + break; + } + } + } + if (hasTip) + QToolTip::showText(helpEvent->globalPos(), tipString(i)); + else + QToolTip::hideText(); + } + return QWidget::event(event); +} + +void TreeMapWidget::paintEvent( QPaintEvent * ) +{ + 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) + qDebug() << "Redrawing " << _needsRefresh->path(0).join("/"); + + if (_needsRefresh == _base) { + // redraw whole widget + _pixmap = QPixmap(size()); + _pixmap.fill(palette().color(backgroundRole())); + } + QPainter p(&_pixmap); + if (_needsRefresh == _base) { + p.setPen(Qt::black); + p.drawRect(QRect(2, 2, QWidget::width()-5, QWidget::height()-5)); + _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; + } + + QPainter p(this); + p.drawPixmap(0, 0, _pixmap, 0, 0, + QWidget::width(), QWidget::height()); + + if (hasFocus()) { + QStylePainter p(this); + QStyleOptionFocusRect opt; + opt.rect = rect(); + opt.palette = palette(); + opt.state = QStyle::State_None; + p.drawPrimitive( QStyle::PE_FrameFocusRect, opt ); + } +} + + + +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; + + if (_markNo>0) { + for(TreeMapItem* i = item; i; i=i->parent()) { + if (i->isMarked(_markNo)) { + isSelected = true; + break; + } + } + } + else { + foreach(TreeMapItem* i, _tmpSelection) { + if (item->isChildOf(i)) { + isSelected = true; + break; + } + } + } + + 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) + qDebug() << "+drawItems(" << item->path(0).join("/") << ", " + << item->itemRect().x() << "/" << item->itemRect().y() + << "-" << item->itemRect().width() << "x" + << item->itemRect().height() << "), Val " << item->value() + << ", Sum " << item->sum(); + + 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(); + + 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 + foreach(TreeMapItem* i, *list) + i->clearItemRect(); + } + // tooltip appears 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) + qDebug() << "-drawItems(" << item->path(0).join("/") << ")"; + return; + } + + double user_sum, child_sum, self; + + // user supplied sum + user_sum = item->sum(); + + // own sum + child_sum = 0; + foreach(TreeMapItem* i, *list) { + child_sum += i->value(); + if (DEBUG_DRAWING) + qDebug() << " child: " << i->text(0) << ", value " + << i->value(); + } + + 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 did not supply any sum + user_sum = child_sum; + self = 0; + } + else { + self = user_sum - child_sum; + + if (user_sum < child_sum) { + //qDebug() << "TreeMWidget " << + // item->path() << ": User sum " << user_sum << " < Child Items sum " << child_sum; + + // 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) qDebug() << "Item " << item->path(0).join("/") << ": SelfR " + << sr.x() << "/" << sr.y() << "-" << sr.width() + << "/" << sr.height() << ", self " << self << "/" + << user_sum; + + 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; + } + + int idx = goBack ? (list->size()-1) : 0; + + if (item->splitMode() == TreeMapItem::Columns) { + int len = list->count(); + bool drawDetails = true; + + while (len>0 && user_sum>0) { + int firstIdx = idx; + 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 += list->at(idx)->value(); + if (goBack) --idx; else ++idx; + 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, list, firstIdx, len, goBack); + break; + } + } + else { + drawDetails = drawItemArray(p, item, firstRect, + valSum, list, firstIdx, 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, list, idx, len, goBack); + break; + } + } + } + } + else if (item->splitMode() == TreeMapItem::Rows) { + int len = list->count(); + bool drawDetails = true; + + while (len>0 && user_sum>0) { + int firstIdx = idx; + 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 += list->at(idx)->value(); + if (goBack) --idx; else ++idx; + 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, list, firstIdx, len, goBack); + break; + } + } + else { + drawDetails = drawItemArray(p, item, firstRect, + valSum, list, firstIdx, 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, list, idx, len, goBack); + break; + } + } + } + } + else + drawItemArray(p, item, r, user_sum, list, idx, list->count(), goBack); + + if (DEBUG_DRAWING) + qDebug() << "-drawItems(" << item->path(0).join("/") << ")"; +} + +// fills area with a pattern if to small to draw children +void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r) +{ + p->setBrush(Qt::Dense4Pattern); + p->setPen(Qt::NoPen); + p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1)); + i->addFreeRect(r); +} + +// fills area with a pattern if to small to draw children +void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r, + TreeMapItemList* list, int idx, int len, bool goBack) +{ + if (DEBUG_DRAWING) + qDebug() << " +drawFill(" << r.x() << "/" << r.y() + << "-" << r.width() << "x" << r.height() + << ", len " << len << ")"; + + p->setBrush(Qt::Dense4Pattern); + p->setPen(Qt::NoPen); + p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1)); + i->addFreeRect(r); + + // reset rects + while (len>0 && (i=list->value(idx))) { + + if (DEBUG_DRAWING) + qDebug() << " Reset Rect " << i->path(0).join("/"); + + i->clearItemRect(); + if (goBack) --idx; else ++idx; + len--; + } + if (DEBUG_DRAWING) + qDebug() << " -drawFill(" << r.x() << "/" << r.y() + << "-" << r.width() << "x" << r.height() + << ", len " << len << ")"; +} + +// returns false if rect gets to small +bool TreeMapWidget::drawItemArray(QPainter* p, TreeMapItem* item, + const QRect& r, double user_sum, + TreeMapItemList* list, int idx, 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, list, idx, len, goBack); + return false; + } + + if (DEBUG_DRAWING) + qDebug() << " +drawItemArray(" << item->path(0).join("/") + << ", " << r.x() << "/" << r.y() << "-" << r.width() + << "x" << r.height() << ")"; + + if (len>2 && (item->splitMode() == TreeMapItem::Bisection)) { + + int firstIdx = idx; + double valSum = 0; + int lenLeft = len; + //while (lenLeft>0 && valSumlen/2) { + valSum += list->at(idx)->value(); + if (goBack) --idx; else ++idx; + lenLeft--; + } + + // draw first half... + bool drawOn; + QRect secondRect; + + 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, list, firstIdx, len-lenLeft, goBack); + secondRect.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, list, firstIdx, len-lenLeft, goBack); + secondRect.setRect(r.x(), r.y()+halfPos, r.width(), r.height()-halfPos); + } + + // if no sorting, do not stop drawing + if (item->sorting(0) == -1) drawOn = true; + + // second half + if (drawOn) + drawOn = drawItemArray(p, item, secondRect, user_sum - valSum, + list, idx, lenLeft, goBack); + else { + drawFill(item, p, secondRect, list, idx, len, goBack); + } + + if (DEBUG_DRAWING) + qDebug() << " -drawItemArray(" << item->path(0).join("/") + << ")"; + + return drawOn; + } + + bool hor = horizontal(item,r); + + TreeMapItem* i; + QRect fullRect = r; + while (len>0) { + i = list->at(idx); + if (user_sum <= 0) { + + if (DEBUG_DRAWING) + qDebug() << "drawItemArray: Reset " << i->path(0).join("/"); + + i->clearItemRect(); + if (goBack) --idx; else ++idx; + len--; + continue; + } + + // stop drawing for small rectangles + if (((fullRect.height() < _visibleWidth) && + (fullRect.width() < _visibleWidth)) || + ((_minimalArea > 0) && + (fullRect.width() * fullRect.height() < _minimalArea))) { + + drawFill(item, p, fullRect, list, idx, len, goBack); + if (DEBUG_DRAWING) + qDebug() << " -drawItemArray(" << item->path(0).join("/") + << "): Stop"; + return false; + } + + if (i->splitMode() == TreeMapItem::AlwaysBest) + hor = fullRect.width() > fullRect.height(); + + int lastPos = hor ? fullRect.width() : fullRect.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, fullRect, list, idx, len, goBack); + if (DEBUG_DRAWING) + qDebug() << " -drawItemArray(" << item->path(0).join("/") + << "): Stop"; + return false; + } + + QRect currRect = fullRect; + + if (hor) + currRect.setWidth(nextPos); + else { + if (b2t) + currRect.setRect(fullRect.x(), fullRect.bottom()-nextPos+1, fullRect.width(), nextPos); + else + currRect.setHeight(nextPos); + } + + // do not draw very small rectangles: + if (nextPos >= _visibleWidth) { + i->setItemRect(currRect); + drawItems(p, i); + } + else { + i->clearItemRect(); + drawFill(item, p, currRect); + } + + // draw Separator + if (_drawSeparators && (nextPossetPen(Qt::black); + if (hor) { + if (fullRect.top() <= fullRect.bottom()) + p->drawLine(fullRect.x() + nextPos, fullRect.top(), fullRect.x() + nextPos, fullRect.bottom()); + } + else { + if (fullRect.left() <= fullRect.right()) + p->drawLine(fullRect.left(), fullRect.y() + nextPos, fullRect.right(), fullRect.y() + nextPos); + } + nextPos++; + } + + if (hor) + fullRect.setRect(fullRect.x() + nextPos, fullRect.y(), + lastPos - nextPos, fullRect.height()); + else { + if (b2t) + fullRect.setRect(fullRect.x(), fullRect.y(), + fullRect.width(), lastPos-nextPos); + else + fullRect.setRect(fullRect.x(), fullRect.y() + nextPos, + fullRect.width(), lastPos-nextPos); + } + + user_sum -= val; + if (goBack) --idx; else ++idx; + len--; + } + + if (DEBUG_DRAWING) + qDebug() << " -drawItemArray(" << item->path(0).join("/") + << "): Continue"; + + return true; +} + + +/*---------------------------------------------------------------- + * Popup menus for option setting + */ + +void TreeMapWidget::splitActivated(QAction* a) +{ + setSplitMode( (TreeMapItem::SplitMode) a->data().toInt()); +} + +void TreeMapWidget::addSplitAction(QMenu* m, const QString& s, int v) +{ + QAction* a = m->addAction(s); + a->setData(v); + a->setCheckable(true); + a->setChecked(splitMode() == v); +} + +void TreeMapWidget::addSplitDirectionItems(QMenu* m) +{ + connect(m, SIGNAL(triggered(QAction*)), + this, SLOT(splitActivated(QAction*)) ); + + addSplitAction(m, tr("Recursive Bisection"), TreeMapItem::Bisection); + addSplitAction(m, tr("Columns"), TreeMapItem::Columns); + addSplitAction(m, tr("Rows"), TreeMapItem::Rows); + addSplitAction(m, tr("Always Best"), TreeMapItem::AlwaysBest); + addSplitAction(m, tr("Best"), TreeMapItem::Best); + addSplitAction(m, tr("Alternate (V)"), TreeMapItem::VAlternate); + addSplitAction(m, tr("Alternate (H)"), TreeMapItem::HAlternate); + addSplitAction(m, tr("Horizontal"), TreeMapItem::Horizontal); + addSplitAction(m, tr("Vertical"), TreeMapItem::Vertical); +} + + +#include "moc_treemap.cpp" diff --git a/kcachegrind/libviews/treemap.h b/kcachegrind/libviews/treemap.h new file mode 100644 index 00000000..436554af --- /dev/null +++ b/kcachegrind/libviews/treemap.h @@ -0,0 +1,728 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002-2009 Josef Weidendorfer + + 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 +#include +#include +#include +#include +#include +#include +#include +#include + +class QMenu; +class TreeMapWidget; +class TreeMapItem; +class TreeMapItemList; + + +/** + * 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(); + explicit StoredDrawParams(const 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, const 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 + void ensureField(int f); + + struct Field { + QString text; + QPixmap pix; + Position pos; + int maxLines; + }; + + QVector _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(const 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(const 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 QList +{ +public: + TreeMapItem* commonParent(); +}; + + +/** + * 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 }; + + explicit TreeMapItem(TreeMapItem* parent = 0, double value = 1.0 ); + TreeMapItem(TreeMapItem* parent, double value, + const QString& text1, const QString& text2 = QString(), + const QString& text3 = QString(), const QString& text4 = QString()); + 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(); + const QList& 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: = -1 + * For value() sorting, use = -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; + QList _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 gets owner of the base item */ + explicit TreeMapWidget(TreeMapItem* base, QWidget* parent=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 . 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(). + * 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 Extended 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(const 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 , 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 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, const QString&); + QString fieldType(int) const; + + /** + * Stop drawing at item with name + */ + void setFieldStop(int, const 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, const 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; } + + /** + * Populate given menu with option items. + * The added items are automatically connected to handlers. + */ + void addSplitDirectionItems(QMenu*); + + 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(QAction*); + +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 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 fontChange( const QFont& ); + bool event(QEvent *event); + +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, const QRect& r); + void drawFill(TreeMapItem*,QPainter* p, const QRect& r, + TreeMapItemList* list, int idx, int len, bool goBack); + bool drawItemArray(QPainter* p, TreeMapItem*, const QRect& r, double, + TreeMapItemList* list, int idx, int len, bool); + bool resizeAttr(int); + + void addSplitAction(QMenu*, const QString&, int); + + TreeMapItem* _base; + TreeMapItem *_current, *_pressed, *_lastOver, *_oldCurrent; + int _maxSelectDepth, _maxDrawingDepth; + + // attributes for field, per textNo + struct FieldAttr { + QString type, stop; + bool visible, forced; + DrawParams::Position pos; + }; + QVector _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; + + // 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/pics/CMakeLists.txt b/kcachegrind/pics/CMakeLists.txt new file mode 100644 index 00000000..19cc15b6 --- /dev/null +++ b/kcachegrind/pics/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( hicolor ) diff --git a/kcachegrind/pics/hicolor/CMakeLists.txt b/kcachegrind/pics/hicolor/CMakeLists.txt new file mode 100644 index 00000000..ecdea272 --- /dev/null +++ b/kcachegrind/pics/hicolor/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons( ${DATA_INSTALL_DIR}/kcachegrind/icons ) diff --git a/kcachegrind/pics/hicolor/hi16-action-move.png b/kcachegrind/pics/hicolor/hi16-action-move.png new file mode 100644 index 00000000..3e934ceb Binary files /dev/null and b/kcachegrind/pics/hicolor/hi16-action-move.png differ diff --git a/kcachegrind/pics/hicolor/hi16-action-percent.png b/kcachegrind/pics/hicolor/hi16-action-percent.png new file mode 100644 index 00000000..3aa0ee0a Binary files /dev/null and b/kcachegrind/pics/hicolor/hi16-action-percent.png differ diff --git a/kcachegrind/pics/hicolor/hi22-action-hidetemplates.png b/kcachegrind/pics/hicolor/hi22-action-hidetemplates.png new file mode 100644 index 00000000..c25776c9 Binary files /dev/null and b/kcachegrind/pics/hicolor/hi22-action-hidetemplates.png differ diff --git a/kcachegrind/pics/hicolor/hi22-action-move.png b/kcachegrind/pics/hicolor/hi22-action-move.png new file mode 100644 index 00000000..ea161246 Binary files /dev/null and b/kcachegrind/pics/hicolor/hi22-action-move.png differ diff --git a/kcachegrind/pics/hicolor/hi22-action-percent.png b/kcachegrind/pics/hicolor/hi22-action-percent.png new file mode 100644 index 00000000..5f5fbab6 Binary files /dev/null and b/kcachegrind/pics/hicolor/hi22-action-percent.png differ diff --git a/kcachegrind/pics/hicolor/hi32-action-percent.png b/kcachegrind/pics/hicolor/hi32-action-percent.png new file mode 100644 index 00000000..d16ec341 Binary files /dev/null and b/kcachegrind/pics/hicolor/hi32-action-percent.png differ diff --git a/kcachegrind/qcachegrind/CMakeLists.txt b/kcachegrind/qcachegrind/CMakeLists.txt new file mode 100644 index 00000000..6af19835 --- /dev/null +++ b/kcachegrind/qcachegrind/CMakeLists.txt @@ -0,0 +1,19 @@ +include_directories(../libcore ../libviews) + +set(qcachegrind_SRCS qcgmain.cpp qcgtoplevel.cpp qcgconfig.cpp + configdialog.cpp configpage.cpp qtcolorbutton.cpp + generalsettings.cpp sourcesettings.cpp colorsettings.cpp) + +qt4_automoc(${qcachegrind_SRCS}) +qt4_wrap_ui(qcachegrind_H generalsettings.ui sourcesettings.ui colorsettings.ui) +qt4_add_resources(qcachegrind_RSRC qcachegrind.qrc) + +add_executable(qcachegrind ${qcachegrind_SRCS} ${qcachegrind_H} ${qcachegrind_RSRC}) + +target_link_libraries(qcachegrind + core views + ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY}) + +# do not install example code... +# install(TARGETS qcachegrind ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/kcachegrind/qcachegrind/README b/kcachegrind/qcachegrind/README new file mode 100644 index 00000000..eeea53a9 --- /dev/null +++ b/kcachegrind/qcachegrind/README @@ -0,0 +1,11 @@ +This directory just contains _example_ code for using KCachegrind's +libcore/libviews in Qt-only apps, such as IDEs. + +As it is useless to provide a stripped-down Qt-only application +within a KDE package together with the full-blown KDE version, +it is not installed. + +For demonstration purpose, qcachegrind can be build without KDE libs +at all, using just qmake with the example qcachegrind.pro file. +For this, run "qmake; make". + diff --git a/kcachegrind/qcachegrind/colorsettings.cpp b/kcachegrind/qcachegrind/colorsettings.cpp new file mode 100644 index 00000000..3b129dba --- /dev/null +++ b/kcachegrind/qcachegrind/colorsettings.cpp @@ -0,0 +1,182 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Color settings config page + */ + +#include +#include + +#include "tracedata.h" +#include "colorsettings.h" +#include "globalguiconfig.h" +#include "listutils.h" + +// +// ColorSettings +// + +static void insertColorItems(QTreeWidget* w, ProfileContext::Type type, + QStringList items) +{ + items.sort(); + foreach(QString s, items) { + ConfigColorSetting* cs = GlobalGUIConfig::groupColorSetting(type, s); + QTreeWidgetItem* i = new QTreeWidgetItem(w); + i->setText(0, ProfileContext::i18nTypeName(type)); + i->setData(0, Qt::UserRole, QVariant::fromValue((void*)cs)); + i->setIcon(1, QIcon(colorPixmap(20,10, cs->color()))); + i->setText(1, cs->automatic() ? QObject::tr("(auto)") : QString()); + i->setData(1, Qt::UserRole, cs->color()); + i->setText(2, s); + } +} + +ColorSettings::ColorSettings(TraceData* data, QWidget* parent) + : ConfigPage(parent, + QObject::tr("Group Colors"), + QObject::tr("Color Settings for Function Groups")) +{ + ui.setupUi(this); + + ui.colorList->setRootIsDecorated(false); + ui.colorList->setSortingEnabled(false); + QStringList items; + if (data) { + TraceObjectMap::Iterator oit = data->objectMap().begin(); + for(; oit != data->objectMap().end(); ++oit) + items << (*oit).prettyName(); + } + if (!items.contains(TraceObject::prettyEmptyName())) + items << TraceObject::prettyEmptyName(); + insertColorItems(ui.colorList, ProfileContext::Object, items); + items.clear(); + if (data) { + TraceFileMap::Iterator fit = data->fileMap().begin(); + for(; fit != data->fileMap().end(); ++fit) + items << (*fit).prettyName(); + } + if (!items.contains(TraceFile::prettyEmptyName())) + items << TraceFile::prettyEmptyName(); + insertColorItems(ui.colorList, ProfileContext::File, items); + items.clear(); + if (data) { + TraceClassMap::Iterator cit = data->classMap().begin(); + for(; cit != data->classMap().end(); ++cit) + items << (*cit).prettyName(); + } + if (!items.contains(TraceClass::prettyEmptyName())) + items << TraceClass::prettyEmptyName(); + insertColorItems(ui.colorList, ProfileContext::Class, items); + ui.colorList->setSortingEnabled(true); + + ui.colorList->resizeColumnToContents(0); + ui.colorList->resizeColumnToContents(1); + ui.colorList->resizeColumnToContents(2); + + connect(ui.resetButton, SIGNAL(clicked()), + this, SLOT(resetClicked())); + connect(ui.colorList, + SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, + SLOT(colorListItemChanged(QTreeWidgetItem*,QTreeWidgetItem*))); + connect(ui.colorButton, SIGNAL(colorChanged(const QColor &)), + this, SLOT(colorChanged(const QColor &))); + + _current = 0; + update(); +} + +ColorSettings::~ColorSettings() +{} + +void ColorSettings::activate(QString s) +{ + int idx = s.toInt(); + if ((idx==0) || (idx>ui.colorList->topLevelItemCount())) return; + + ui.colorList->setCurrentItem(ui.colorList->topLevelItem(idx-1)); + ui.colorButton->setFocus(); +} + +void ColorSettings::update() +{ + if (!_current) { + ui.resetButton->setEnabled(false); + ui.colorButton->setEnabled(false); + return; + } + ui.resetButton->setEnabled(true); + ui.colorButton->setEnabled(true); + + QColor c = _current->data(1, Qt::UserRole).value(); + ui.colorButton->setColor(c); +} + +void ColorSettings::resetClicked() +{ + if (!_current) return; + + ConfigColorSetting*cs; + cs = (ConfigColorSetting*) _current->data(0, Qt::UserRole).value(); + QColor c = cs->autoColor(); + _current->setIcon(1, QIcon(colorPixmap(20,10, c))); + _current->setData(1, Qt::UserRole, c); + _current->setText(1, QObject::tr("(auto)")); + ui.colorButton->setColor(c); +} + + +void ColorSettings::colorListItemChanged(QTreeWidgetItem* current, + QTreeWidgetItem*) +{ + _current = current; + update(); +} + +void ColorSettings::colorChanged(const QColor& c) +{ + if (!_current) return; + + _current->setIcon(1, QIcon(colorPixmap(20,10, c))); + _current->setData(1, Qt::UserRole, c); + _current->setText(1, QString()); +} + +bool ColorSettings::check(QString&, QString&) +{ + return true; +} + +void ColorSettings::accept() +{ + QTreeWidgetItem* item; + ConfigColorSetting* cs; + QColor c; + for(int i = 0; i< ui.colorList->topLevelItemCount(); i++) { + item = ui.colorList->topLevelItem(i); + cs = (ConfigColorSetting*) item->data(0, Qt::UserRole).value(); + c = item->data(1, Qt::UserRole).value(); + if (cs->color() == c) continue; + + cs->setColor(c); + } +} + +#include "moc_colorsettings.cpp" diff --git a/kcachegrind/qcachegrind/colorsettings.h b/kcachegrind/qcachegrind/colorsettings.h new file mode 100644 index 00000000..eddd88f7 --- /dev/null +++ b/kcachegrind/qcachegrind/colorsettings.h @@ -0,0 +1,58 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Color settings config page + */ + +#ifndef COLORSETTINGS_H +#define COLORSETTINGS_H + +#include "configpage.h" +#include "context.h" +#include "ui_colorsettings.h" + +class TraceData; +class QTreeWidgetItem; + +class ColorSettings: public ConfigPage +{ + Q_OBJECT + +public: + ColorSettings(TraceData* data, QWidget* parent); + virtual ~ColorSettings(); + + bool check(QString&, QString&); + void accept(); + void activate(QString s); + +public slots: + void resetClicked(); + void colorListItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); + void colorChanged(const QColor &); + +private: + void update(); + + Ui::ColorSettings ui; + QTreeWidgetItem* _current; +}; + + +#endif // COLORSETTINGS_H diff --git a/kcachegrind/qcachegrind/colorsettings.ui b/kcachegrind/qcachegrind/colorsettings.ui new file mode 100644 index 00000000..9dadc7b0 --- /dev/null +++ b/kcachegrind/qcachegrind/colorsettings.ui @@ -0,0 +1,93 @@ + + + ColorSettings + + + + 0 + 0 + 400 + 300 + + + + + 0 + + + 0 + + + 0 + + + + + + Type + + + + + Color + + + + + Name + + + + + + + + Color: + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset + + + + + + + + QtColorButton + QToolButton +
qtcolorbutton.h
+
+
+ + +
diff --git a/kcachegrind/qcachegrind/configdialog.cpp b/kcachegrind/qcachegrind/configdialog.cpp new file mode 100644 index 00000000..86b1f036 --- /dev/null +++ b/kcachegrind/qcachegrind/configdialog.cpp @@ -0,0 +1,168 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * QCachegrind configuration dialog + */ + +#include "configdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "generalsettings.h" +#include "sourcesettings.h" +#include "colorsettings.h" + +// +// ConfigDialog +// + +ConfigDialog::ConfigDialog(TraceData* data, QWidget* parent, QString s) + : QDialog(parent) +{ + setWindowTitle(tr("Configure QCachegrind")); + + _listWidget = new QListWidget(this); + _listWidget->setMaximumWidth(140); + _widgetStack = new QStackedWidget(this); + _titleLabel = new QLabel(this); + QFont labelFont; + labelFont.setBold(true); + _titleLabel->setFont(labelFont); + _errorLabel = new QLabel(this); + _errorLabel->setIndent(9); + + QDialogButtonBox* bbox = new QDialogButtonBox(this); + bbox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + + QVBoxLayout* vbox1 = new QVBoxLayout(); + vbox1->addWidget(_titleLabel); + QFrame* f1 = new QFrame(this); + f1->setFrameShape(QFrame::HLine); + vbox1->addWidget(f1); + vbox1->addWidget(_errorLabel); + vbox1->addWidget(_widgetStack); + + QHBoxLayout* hbox = new QHBoxLayout(); + hbox->addWidget(_listWidget); + hbox->addLayout(vbox1); + QVBoxLayout* vbox = new QVBoxLayout(this); + vbox->addLayout(hbox); + QFrame* f2 = new QFrame(this); + f2->setFrameStyle(QFrame::HLine | QFrame::Sunken); + vbox->addWidget(f2); + vbox->addWidget(bbox); + + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); + connect(_listWidget, SIGNAL(currentTextChanged(QString)), + this, SLOT(listItemChanged(QString))); + connect(&_clearTimer, SIGNAL(timeout()), this, SLOT(clearError())); + + addPage(new GeneralSettings(this)); + addPage(new SourceSettings(data, this)); + addPage(new ColorSettings(data, this)); + + activate(s); +} + +void ConfigDialog::addPage(ConfigPage* p) +{ + _widgetStack->addWidget(p); + _listWidget->addItem(p->title()); + _pages.insert(p->title(), p); +} + +void ConfigDialog::listItemChanged(QString s) +{ + ConfigPage* p = _pages.value(s); + if (!p) return; + + _titleLabel->setText(p->longTitle()); + _widgetStack->setCurrentWidget(p); + if (!_activeSetting.isEmpty()) { + p->activate(_activeSetting); + _activeSetting.clear(); + } +} + +void ConfigDialog::clearError() +{ + _errorLabel->setText(QString()); +} + +void ConfigDialog::activate(QString s) +{ + if (s.isEmpty()) + _listWidget->setCurrentRow(0); + + QString page = s; + _activeSetting.clear(); + int p = s.indexOf("/"); + if (p>0) { + page = s.left(p); + _activeSetting = s.mid(p+1); + } + + for(int row=0; row<_listWidget->count(); row++) { + QListWidgetItem* i = _listWidget->item(row); + if (i->text() != page) continue; + + if (_listWidget->currentRow() == row) + // even without page change, forward activation + listItemChanged(page); + else + _listWidget->setCurrentRow(row); + } +} + +QString ConfigDialog::currentPage() +{ + return _listWidget->currentItem()->text(); +} + +void ConfigDialog::accept() +{ + ConfigPage* p; + QString errorMsg, errorItem; + foreach(p, _pages) + if (!p->check(errorMsg, errorItem)) { + if (!errorMsg.isEmpty()) { + errorMsg = QString("%1").arg(errorMsg); + _errorLabel->setText(errorMsg); + _clearTimer.start(5000); + } + activate(QString("%1/%2").arg(p->title(), errorItem)); + return; + } + + foreach(p, _pages) + p->accept(); + + QDialog::accept(); +} + +#include "moc_configdialog.cpp" diff --git a/kcachegrind/qcachegrind/configdialog.h b/kcachegrind/qcachegrind/configdialog.h new file mode 100644 index 00000000..3f777612 --- /dev/null +++ b/kcachegrind/qcachegrind/configdialog.h @@ -0,0 +1,68 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * QCachegrind configuration dialog + */ + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include +#include +#include +#include + +#include "configpage.h" + +class QWidget; +class QLabel; +class QListWidget; +class QStackedWidget; + +class TraceData; + +class ConfigDialog: public QDialog +{ + Q_OBJECT + +public: + // If s is not empty, navigate to a given setting on opening + ConfigDialog(TraceData* data, QWidget* parent, QString s = QString::null); + + void activate(QString); + QString currentPage(); + +public slots: + void accept(); + void listItemChanged(QString); + void clearError(); + +private: + void addPage(ConfigPage*); + + QLabel* _titleLabel; + QLabel* _errorLabel; + QListWidget *_listWidget; + QStackedWidget *_widgetStack; + QMap _pages; + QString _activeSetting; + QTimer _clearTimer; +}; + +#endif // CONFIGDIALOG diff --git a/kcachegrind/qcachegrind/configpage.cpp b/kcachegrind/qcachegrind/configpage.cpp new file mode 100644 index 00000000..cd5085ff --- /dev/null +++ b/kcachegrind/qcachegrind/configpage.cpp @@ -0,0 +1,59 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Base class of a subpage in the configuration dialog + */ + +#include "configpage.h" + +#include +#include + +// +// ConfigPage +// + +ConfigPage::ConfigPage(QWidget* parent, QString title, QString longTitle) + : QWidget(parent) +{ + _title = title; + if (longTitle.isEmpty()) + _longTitle = title; + else + _longTitle = longTitle; +} + +void ConfigPage::activate(QString s) +{ + QWidget* w = _names.value(s); + if (w) w->setFocus(); +} + +bool ConfigPage::check(QString&, QString&) +{ + return true; +} + +void ConfigPage::accept() +{} + +QString ConfigPage::inRangeError(int from, int to) +{ + return tr("Value must be between %1 and %2.").arg(from).arg(to); +} diff --git a/kcachegrind/qcachegrind/configpage.h b/kcachegrind/qcachegrind/configpage.h new file mode 100644 index 00000000..14e2e94d --- /dev/null +++ b/kcachegrind/qcachegrind/configpage.h @@ -0,0 +1,57 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Base class for pages in configuration dialog + */ + +#ifndef CONFIGPAGE_H +#define CONFIGPAGE_H + +#include +#include +#include + +class ConfigPage: public QWidget +{ +public: + ConfigPage(QWidget* parent, QString title, QString longTitle = QString()); + virtual ~ConfigPage() {} + + QString title() { return _title; } + QString longTitle() { return _longTitle; } + + // the default implementation focuses on widget named via + virtual void activate(QString); + + // called on OK; prohibits closing by returning false + // an error message to show and item name to navigate to can be set + virtual bool check(QString& errorMsg, QString& errorItem); + virtual void accept(); + +protected: + QString inRangeError(int, int); + + QMap _names; + +private: + QString _title; + QString _longTitle; +}; + +#endif // CONFIGPAGE diff --git a/kcachegrind/qcachegrind/generalsettings.cpp b/kcachegrind/qcachegrind/generalsettings.cpp new file mode 100644 index 00000000..5337ba58 --- /dev/null +++ b/kcachegrind/qcachegrind/generalsettings.cpp @@ -0,0 +1,101 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * General settings config page + */ + +#include "generalsettings.h" +#include "globalconfig.h" + +// +// GeneralSettings +// + +GeneralSettings::GeneralSettings(QWidget* parent) + : ConfigPage(parent, + QObject::tr("General"), QObject::tr("General Settings")) +{ + ui.setupUi(this); + + GlobalConfig* c = GlobalConfig::config(); + ui.maxListEdit->setText(QString::number(c->maxListCount())); + ui.symbolCount->setText(QString::number(c->maxSymbolCount())); + ui.symbolLength->setText(QString::number(c->maxSymbolLength())); + ui.precisionEdit->setText(QString::number(c->percentPrecision())); + ui.contextEdit->setText(QString::number(c->context())); + + _names.insert(QString("maxListEdit"), ui.maxListEdit); + _names.insert(QString("symbolCount"), ui.symbolCount); + _names.insert(QString("symbolLength"), ui.symbolLength); + _names.insert(QString("precisionEdit"), ui.precisionEdit); + _names.insert(QString("contextEdit"), ui.contextEdit); +} + + +bool GeneralSettings::check(QString& errorMsg, QString& errorItem) +{ + int v; + v = ui.maxListEdit->text().toUInt(); + if ((v <1) || (v >500)) { + errorMsg = inRangeError(1, 500); + errorItem = QString("maxListEdit"); + return false; + } + + v = ui.symbolCount->text().toInt(); + if ((v <1) || (v >50)) { + errorMsg = inRangeError(1, 50); + errorItem = QString("symbolCount"); + return false; + } + + v = ui.symbolLength->text().toInt(); + if ((v <1) || (v >1000)) { + errorMsg = inRangeError(1, 1000); + errorItem = QString("symbolLength"); + return false; + } + + v = ui.precisionEdit->text().toInt(); + if ((v <1) || (v >5)) { + errorMsg = inRangeError(1, 5); + errorItem = QString("precisionEdit"); + return false; + } + + v = ui.contextEdit->text().toInt(); + if ((v <1) || (v >500)) { + errorMsg = inRangeError(1, 500); + errorItem = QString("contextEdit"); + return false; + } + + return true; +} + +void GeneralSettings::accept() +{ + GlobalConfig* c = GlobalConfig::config(); + + c->setMaxListCount(ui.maxListEdit->text().toUInt()); + c->setMaxSymbolCount(ui.symbolCount->text().toInt()); + c->setMaxSymbolLength(ui.symbolLength->text().toInt()); + c->setPercentPrecision(ui.precisionEdit->text().toInt()); + c->setContext(ui.contextEdit->text().toInt()); +} diff --git a/kcachegrind/qcachegrind/generalsettings.h b/kcachegrind/qcachegrind/generalsettings.h new file mode 100644 index 00000000..1294ab9c --- /dev/null +++ b/kcachegrind/qcachegrind/generalsettings.h @@ -0,0 +1,42 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * General settings config page + */ + +#ifndef GENERALSETTINGS_H +#define GENERALSETTINGS_H + +#include "configpage.h" +#include "ui_generalsettings.h" + +class GeneralSettings: public ConfigPage +{ +public: + GeneralSettings(QWidget* parent); + virtual ~GeneralSettings() {} + + bool check(QString&, QString&); + void accept(); + +private: + Ui::GeneralSettings ui; +}; + +#endif // GENERALSETTINGS_H diff --git a/kcachegrind/qcachegrind/generalsettings.ui b/kcachegrind/qcachegrind/generalsettings.ui new file mode 100644 index 00000000..68adc363 --- /dev/null +++ b/kcachegrind/qcachegrind/generalsettings.ui @@ -0,0 +1,203 @@ + + + GeneralSettings + + + + 0 + 0 + 274 + 260 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + + + Precision of percentage values: + + + false + + + + + + + + 2 + 0 + + + + + + + + Context lines in annotations: + + + false + + + + + + + + 0 + 0 + + + + + + + + Maximum number of items in lists: + + + false + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + Symbols in tooltips and context menus truncated + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + with more entries than: + + + false + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + with more characters than: + + + false + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 65 + + + + + + + + + diff --git a/kcachegrind/qcachegrind/qcachegrind.desktop b/kcachegrind/qcachegrind/qcachegrind.desktop new file mode 100755 index 00000000..7689de20 --- /dev/null +++ b/kcachegrind/qcachegrind/qcachegrind.desktop @@ -0,0 +1,157 @@ +[Desktop Entry] +Type=Application +Encoding=UTF-8 +Exec=qcachegrind %F +MimeType=application/x-kcachegrind; +Icon=kcachegrind +Terminal=false +Name=QCachegrind +Name[bs]=QCachegrind +Name[ca]=QCachegrind +Name[ca@valencia]=QCachegrind +Name[cs]=QCachegrind +Name[da]=QCachegrind +Name[de]=QCachegrind +Name[el]=QCachegrind +Name[en_GB]=QCachegrind +Name[es]=QCachegrind +Name[et]=QCachegrind +Name[fi]=QCachegrind +Name[fr]=QCachegrind +Name[ga]=QCachegrind +Name[gl]=QCachegrind +Name[hu]=QCachegrind +Name[it]=QCachegrind +Name[kk]=QCachegrind +Name[ko]=QCachegrind +Name[lt]=QCachegrind +Name[mr]=क्यु-कॅशेग्रिन्ड +Name[nb]=QCachegrind +Name[nds]=QCachegrind +Name[nl]=QCachegrind +Name[pa]=QCachegrind +Name[pl]=QCachegrind +Name[pt]=QCachegrind +Name[pt_BR]=QCachegrind +Name[ru]=QCachegrind +Name[sk]=QCachegrind +Name[sr]=Ку‑кешгринд +Name[sr@ijekavian]=Ку‑кешгринд +Name[sr@ijekavianlatin]=QCacheGrind +Name[sr@latin]=QCacheGrind +Name[sv]=Qcachegrind +Name[tr]=QCachegrind +Name[uk]=QCachegrind +Name[x-test]=xxQCachegrindxx +Name[zh_CN]=QCachegrind +Name[zh_TW]=QCachegrind +GenericName=Profiler Frontend +GenericName[ast]=Interface del analizador de rendimientu +GenericName[bs]=Profajlerski program +GenericName[ca]=Frontal del Profiler +GenericName[ca@valencia]=Frontal del 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[en_GB]=Profiler Frontend +GenericName[eo]=Fasado de Profililo +GenericName[es]=Interfaz del analizador de rendimiento +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[hu]=Ábrázoló előtétprogram +GenericName[is]=Myndrænt viðmót á afkastakönnuð +GenericName[it]=Interfaccia a profiler +GenericName[ja]=プロファイラフロントエンド +GenericName[kk]=Профильдеткіштің интерфейсі +GenericName[km]=កម្មវិធី​ផ្នែក​ខាង​មុខ​របស់​ទម្រង់ +GenericName[ko]=프로파일러 프론트엔드 +GenericName[lt]=Profiliuoklio naudotojo sąsaja +GenericName[lv]=Profilēšanas priekšpuse +GenericName[mr]=प्रोफाइलर फ्रंटएन्ड +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 Análise de Performance +GenericName[pt_BR]=Interface de análise de performance +GenericName[ro]=Interfață grafică pentru Profiler +GenericName[ru]=Интерфейс к профилировщику +GenericName[sk]=Rozhranie pre profiler +GenericName[sl]=Vmesnik profilnika +GenericName[sr]=Прочеље профилизатора +GenericName[sr@ijekavian]=Прочеље профилизатора +GenericName[sr@ijekavianlatin]=Pročelje profilizatora +GenericName[sr@latin]=Pročelje profilizatora +GenericName[sv]=Profileringsgränssnitt +GenericName[ta]= விவரக்குறிப்பு முன்பகுதி +GenericName[tg]=Интерфейс ба профилкунанда +GenericName[tr]=Profil Önyüzü +GenericName[uk]=Інтерфейс до Profiler +GenericName[wa]=Eterface grafike po Profiler +GenericName[x-test]=xxProfiler Frontendxx +GenericName[zh_CN]=性能测试数据前端 +GenericName[zh_TW]=分析器前端 +Comment=Visualization of Performance Profiling Data +Comment[ast]=Visualización de datos d'analís de rendimientu +Comment[bg]=Визуализация на данните за производителност +Comment[bs]=Predočenje podataka profiliranja performansi +Comment[ca]=Visualització de dades de perfilat de rendiment +Comment[ca@valencia]=Visualització 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]=Visualise les données de profilage de performances +Comment[ga]=Amharcléiriú ar Shonraí Próifílithe Feidhmíochta +Comment[gl]=Visualización dos datos da análise de rendemento +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 profilo delle prestazioni +Comment[ja]=パフォーマンスプロファイルデータを視覚化 +Comment[kk]=Деректерді профильдеудің визуализациясы +Comment[km]=កា​រ​មើល​ឃើញ​ការ​អនុវត្ត​របស់​ទិន្នន័យ​ទម្រង់ +Comment[ko]=성능 프로파일링 데이터 시각화 +Comment[lt]=Veikimo profiliavimo duomenų vizualizacija +Comment[lv]=Veiktspējas profilēšanas datu vizualizācija +Comment[mr]=कार्यक्षमता प्रोफाइलिंग डेटाचे व्हिज्युअलायझेशन +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 dos dados de análise de desempenho +Comment[ru]=Утилита для визуального профилирования приложений +Comment[sk]=Vizualizácia dát o výkone +Comment[sl]=Vizualizacija podatkov profilnih zmogljivosti +Comment[sr]=Визуелизација профилисања перформанси +Comment[sr@ijekavian]=Визуелизација профилисања перформанси +Comment[sr@ijekavianlatin]=Vizuelizacija profilisanja performansi +Comment[sr@latin]=Vizuelizacija profilisanja performansi +Comment[sv]=Åskådliggörande av profileringsdata för prestanda +Comment[ta]= விவர தகவலை செயல்பாட்டு காட்சியாளிப்பு +Comment[tg]=Утилита барои гузориши профили визуалӣ +Comment[tr]=Performans Profilleme Verisinin Görünür Hali +Comment[ug]=سانلىق-مەلۇماتلارنىڭ سەپلىنىشىنى سۈرەتلەشتۈرۈش +Comment[uk]=Візуалізація даних профілювання швидкодії +Comment[x-test]=xxVisualization of Performance Profiling Dataxx +Comment[zh_CN]=性能测试数据的可视化表现 +Comment[zh_TW]=效能分析資料視覺化 +Categories=Qt;Development; diff --git a/kcachegrind/qcachegrind/qcachegrind.pro b/kcachegrind/qcachegrind/qcachegrind.pro new file mode 100644 index 00000000..202128eb --- /dev/null +++ b/kcachegrind/qcachegrind/qcachegrind.pro @@ -0,0 +1,52 @@ +TEMPLATE = app + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +contains(QT_CONFIG, dbus) { + DEFINES += QT_DBUS_SUPPORT + QT += dbus +} + +include(../libcore/libcore.pri) +include(../libviews/libviews.pri) + +# needed for $$QMAKE_MOC below to become defined +load(moc) + +# moc handling of code shared with KDE the "KDE way": +# this generates *.moc files from HEADERS, which get included from *.cpp +new_moc.CONFIG = no_link moc_verify +new_moc.output = ${QMAKE_FILE_BASE}.moc +new_moc.commands = $$moc_header.commands +new_moc.input = NHEADERS +QMAKE_EXTRA_COMPILERS = new_moc + +# Input +FORMS += generalsettings.ui sourcesettings.ui colorsettings.ui + +RESOURCES = qcachegrind.qrc + +NHEADERS += \ + qcgconfig.h \ + qcgtoplevel.h \ + configdialog.h \ + configpage.h \ + generalsettings.h \ + sourcesettings.h \ + qtcolorbutton.h \ + colorsettings.h + +SOURCES += qcgmain.cpp \ + qcgconfig.cpp \ + qcgtoplevel.cpp \ + configdialog.cpp \ + configpage.cpp \ + generalsettings.cpp \ + sourcesettings.cpp \ + qtcolorbutton.cpp \ + colorsettings.cpp + +# makes headers visible in qt-creator (luckily no double moc handling) +HEADERS += $$NHEADERS diff --git a/kcachegrind/qcachegrind/qcachegrind.qrc b/kcachegrind/qcachegrind/qcachegrind.qrc new file mode 100644 index 00000000..3df0b566 --- /dev/null +++ b/kcachegrind/qcachegrind/qcachegrind.qrc @@ -0,0 +1,8 @@ + + + ../pics/hicolor/hi32-action-percent.png + ../pics/hicolor/hi22-action-move.png + ../pics/hicolor/hi22-action-hidetemplates.png + ../kcachegrind/hi48-app-kcachegrind.png + + diff --git a/kcachegrind/qcachegrind/qcgconfig.cpp b/kcachegrind/qcachegrind/qcgconfig.cpp new file mode 100644 index 00000000..4c6a4dc9 --- /dev/null +++ b/kcachegrind/qcachegrind/qcgconfig.cpp @@ -0,0 +1,98 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration for QCachegrind + */ + +#include "qcgconfig.h" + +#include + +#include "tracedata.h" +#include "traceitemview.h" + +// +// QCGConfigGroup +// + +QCGConfigGroup::QCGConfigGroup(QSettings* settings, QString prefix, + bool readOnly) +{ + _settings = settings; + _prefix = prefix; + _readOnly = readOnly; +} + +QCGConfigGroup::~QCGConfigGroup() +{} + +void QCGConfigGroup::setValue(const QString& key, const QVariant& value, + const QVariant& defaultValue) +{ + if ((_settings == 0) || _readOnly) return; + + QString fullKey = QString("%1/%2").arg(_prefix).arg(key); + if (value == defaultValue) + _settings->remove(fullKey); + else + _settings->setValue(fullKey, value); +} + +QVariant QCGConfigGroup::value(const QString& key, + const QVariant& defaultValue) const +{ + if (_settings == 0) return defaultValue; + + QString fullKey = QString("%1/%2").arg(_prefix).arg(key); + return _settings->value(fullKey, defaultValue); +} + + + +// +// QCGConfigStorage +// + +QCGConfigStorage::QCGConfigStorage() +{ + _settings = new QSettings; +} + +QCGConfigStorage::~QCGConfigStorage() +{ + delete _settings; +} + +ConfigGroup* QCGConfigStorage::getGroup(const QString& group, + const QString& optSuffix) +{ + // for writing + if (optSuffix.isEmpty()) + return new QCGConfigGroup(_settings, group, false); + + // for reading + QStringList gList = _settings->childGroups(); + if (gList.contains(group+optSuffix)) + return new QCGConfigGroup(_settings, group + optSuffix, true); + else if (gList.contains(group)) + return new QCGConfigGroup(_settings, group, true); + + // requested group does not exist, return only default values + return new QCGConfigGroup(0, QString::null, true); +} diff --git a/kcachegrind/qcachegrind/qcgconfig.h b/kcachegrind/qcachegrind/qcgconfig.h new file mode 100644 index 00000000..a97db2a9 --- /dev/null +++ b/kcachegrind/qcachegrind/qcgconfig.h @@ -0,0 +1,64 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * Configuration for QCachegrind + */ + +#ifndef QCGCONFIG_H +#define QCGCONFIG_H + +#include "config.h" + +class QSettings; +class QCGConfigStorage; + +class QCGConfigGroup: public ConfigGroup +{ + friend class QCGConfigStorage; + +public: + ~QCGConfigGroup(); + + void setValue(const QString& key, const QVariant& value, + const QVariant& defaultValue = QVariant()); + QVariant value(const QString& key, const QVariant& defaultValue) const; + +private: + QCGConfigGroup(QSettings*, QString prefix, bool); + + QSettings* _settings; + QString _prefix; + bool _readOnly; +}; + + +class QCGConfigStorage : public ConfigStorage +{ +public: + QCGConfigStorage(); + ~QCGConfigStorage(); + +private: + ConfigGroup* getGroup(const QString& group, + const QString& optSuffix); + + QSettings* _settings; +}; + +#endif // QCGCONFIG_H diff --git a/kcachegrind/qcachegrind/qcgmain.cpp b/kcachegrind/qcachegrind/qcgmain.cpp new file mode 100644 index 00000000..b6272811 --- /dev/null +++ b/kcachegrind/qcachegrind/qcgmain.cpp @@ -0,0 +1,65 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer + + 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. +*/ + +/* + * QCachegrind startup + */ + +#include +#include + +#include "qcgconfig.h" +#include "config.h" +#include "globalguiconfig.h" +#include "qcgtoplevel.h" +#include "tracedata.h" +#include "loader.h" + +int main( int argc, char ** argv ) +{ + QApplication app(argc, argv); + Loader::initLoaders(); + + QCoreApplication::setOrganizationName("kcachegrind.sf.net"); + QCoreApplication::setApplicationName("QCachegrind"); + ConfigStorage::setStorage(new QCGConfigStorage); + // creates global config object of type GlobalGUIConfig + //GlobalGUIConfig::config()->addDefaultTypes(); + + QStringList list = app.arguments(); + list.pop_front(); + QCGTopLevel* t = new QCGTopLevel(); + t->show(); + if (list.isEmpty()) { + // load files in current dir + t->loadDelayed( ".", false); + } + else { + foreach(const QString& file, list) + t->loadDelayed( QDir::fromNativeSeparators(file) ); + } + + int res = app.exec(); + + // to make leak checking in valgrind happy... + Loader::deleteLoaders(); + ProfileContext::cleanup(); + ConfigStorage::cleanup(); + + return res; +} diff --git a/kcachegrind/qcachegrind/qcgtoplevel.cpp b/kcachegrind/qcachegrind/qcgtoplevel.cpp new file mode 100644 index 00000000..dbd3f6ed --- /dev/null +++ b/kcachegrind/qcachegrind/qcgtoplevel.cpp @@ -0,0 +1,2039 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002 - 2009 Josef Weidendorfer + + 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. +*/ + +/* + * QCachegrind top level window + */ + +#define TRACE_UPDATES 0 + +#include "qcgtoplevel.h" + +#include // for system() + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef QT_DBUS_SUPPORT +#include +#endif + +#include "partselection.h" +#include "functionselection.h" +#include "stackselection.h" +#include "stackbrowser.h" +#include "tracedata.h" +#include "config.h" +#include "globalguiconfig.h" +#include "multiview.h" +#include "callgraphview.h" +#include "configdialog.h" + +QCGTopLevel::QCGTopLevel() +{ +#ifdef QT_DBUS_SUPPORT + QDBusConnection con = QDBusConnection::sessionBus(); + con.registerObject("/QCachegrind", this, + QDBusConnection::ExportScriptableSlots); +#endif + + _progressBar = 0; + _statusbar = statusBar(); + _statusLabel = new QLabel(_statusbar); + _statusbar->addWidget(_statusLabel, 1); + + _layoutCount = 1; + _layoutCurrent = 0; + + resetState(); + + GlobalGUIConfig::config()->readOptions(); + + createActions(); + createDocks(); + createMenu(); + createToolbar(); + + _multiView = new MultiView(this, this); + _multiView->setObjectName("MultiView"); + setCentralWidget(_multiView); + + // restore current state settings (not configuration options) + restoreCurrentState(QString::null); + + // restore docks & toolbars from config + QByteArray state, geometry; + ConfigGroup* topConfig = ConfigStorage::group("TopWindow"); + _forcePartDock = topConfig->value("ForcePartDockVisible", false).toBool(); + state = topConfig->value("State", QByteArray()).toByteArray(); + geometry = topConfig->value("Geometry", QByteArray()).toByteArray(); + delete topConfig; + + if (!geometry.isEmpty()) + restoreGeometry(geometry); + if (!state.isEmpty()) + restoreState(state); + + setWindowIcon(QIcon(":/app.png")); + setAttribute(Qt::WA_DeleteOnClose); +} + +QCGTopLevel::~QCGTopLevel() +{ + delete _data; +} + +// reset the visualization state, e.g. before loading new data +void QCGTopLevel::resetState() +{ + _activeParts.clear(); + _hiddenParts.clear(); + + _data = 0; + _function = 0; + _eventType = 0; + _eventType2 = 0; + _groupType = ProfileContext::InvalidType; + _group = 0; + + // for delayed slots + _traceItemDelayed = 0; + _eventTypeDelayed = 0; + _eventType2Delayed = 0; + _groupTypeDelayed = ProfileContext::InvalidType; + _groupDelayed = 0; + _directionDelayed = TraceItemView::None; + _lastSender = 0; +} + + +/** + * 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 QCGTopLevel::saveCurrentState(const QString& postfix) +{ + QString eventType, eventType2; + if (_eventType) eventType = _eventType->name(); + if (_eventType2) eventType2 = _eventType2->name(); + + ConfigGroup* stateConfig = ConfigStorage::group(QString("CurrentState") + postfix); + stateConfig->setValue("EventType", eventType); + stateConfig->setValue("EventType2", eventType2); + stateConfig->setValue("GroupType", ProfileContext::typeName(_groupType)); + delete stateConfig; + + _partSelection->saveOptions(QString("PartOverview"), postfix); + _multiView->saveLayout(QString("MainView"), postfix); + _multiView->saveOptions(QString("MainView"), postfix); +} + +/** + * This function is called when a trace is closed. + * Save browsing position for later restoring + */ +void QCGTopLevel::saveTraceSettings() +{ + QString key = traceKey(); + + ConfigGroup* lConfig = ConfigStorage::group("Layouts"); + lConfig->setValue(QString("Count%1").arg(key), _layoutCount); + lConfig->setValue(QString("Current%1").arg(key), _layoutCurrent); + delete lConfig; + + ConfigGroup* pConfig = ConfigStorage::group("TracePositions"); + if (_eventType) + pConfig->setValue(QString("EventType%1").arg(key), _eventType->name()); + if (_eventType2) + pConfig->setValue(QString("EventType2%1").arg(key), _eventType2->name()); + if (_groupType != ProfileContext::InvalidType) + pConfig->setValue(QString("GroupType%1").arg(key), + ProfileContext::typeName(_groupType)); + + if (_data) { + if (_group) + pConfig->setValue(QString("Group%1").arg(key), _group->name()); + saveCurrentState(key); + } + delete pConfig; +} + +/** + * This restores the current visualization state of the main window and + * of the profile views. + */ +void QCGTopLevel::restoreCurrentState(const QString& postfix) +{ + _partSelection->restoreOptions(QString("PartOverview"), postfix); + _multiView->restoreLayout(QString("MainView"), postfix); + _multiView->restoreOptions(QString("MainView"), postfix); + + _splittedToggleAction->setChecked(_multiView->childCount()>1); + _splitDirectionToggleAction->setEnabled(_multiView->childCount()>1); + _splitDirectionToggleAction->setChecked(_multiView->orientation() == + Qt::Horizontal); +} + +void QCGTopLevel::sidebarMenuAboutToShow() +{ + QAction* action; + QMenu *popup = _sidebarMenuAction->menu(); + + popup->clear(); + + action = popup->addAction(tr("Parts Overview")); + action->setCheckable(true); + action->setChecked(_partDock->isVisible()); + connect(action, SIGNAL(triggered(bool)), this, SLOT(togglePartDock())); + + action = popup->addAction(tr("Top Cost Call Stack")); + action->setCheckable(true); + action->setChecked(_stackDock->isVisible()); + connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleStackDock())); + + action = popup->addAction(tr("Flat Profile")); + action->setCheckable(true); + action->setChecked(_functionDock->isVisible()); + connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleFunctionDock())); +} + +void QCGTopLevel::recentFilesMenuAboutToShow() +{ + QStringList recentFiles; + QMenu *popup = _recentFilesMenuAction->menu(); + + popup->clear(); + + ConfigGroup* generalConfig = ConfigStorage::group("GeneralSettings"); + recentFiles = generalConfig->value("RecentFiles", + QStringList()).toStringList(); + delete generalConfig; + + if (recentFiles.count() == 0) + popup->addAction(tr("(No recent files)")); + else { + foreach(const QString& file, recentFiles) { + // paths shown to user should use OS-native separators + popup->addAction(QDir::toNativeSeparators(file)); + } + } +} + +void QCGTopLevel::recentFilesTriggered(QAction* action) +{ + if (action) + load(QStringList(QDir::fromNativeSeparators(action->text()))); +} + +void QCGTopLevel::createDocks() +{ + // part visualization/selection side bar + _partDock = new QDockWidget(this); + _partDock->setObjectName("part-dock"); + _partDock->setWindowTitle(tr("Parts Overview")); + _partSelection = new PartSelection(this, _partDock); + _partDock->setWidget(_partSelection); + + connect(_partSelection, SIGNAL(partsHideSelected()), + this, SLOT(partsHideSelectedSlotDelayed())); + connect(_partSelection, SIGNAL(partsUnhideAll()), + this, SLOT(partsUnhideAllSlotDelayed())); + + // stack selection side bar + _stackDock = new QDockWidget(this); + _stackDock->setObjectName("stack-dock"); + _stackSelection = new StackSelection(_stackDock); + _stackDock->setWidget(_stackSelection); + _stackDock->setWindowTitle(tr("Top Cost Call Stack")); + _stackSelection->setWhatsThis( tr( + "The Top Cost Call Stack" + "

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.

" + "

The Cost and Calls columns show the " + "cost used for all calls from the function in the line " + "above.

")); + connect(_stackSelection, SIGNAL(functionSelected(CostItem*)), + this, SLOT(setTraceItemDelayed(CostItem*))); + // actions are already created + connect(_upAction, SIGNAL(triggered(bool)), + _stackSelection, SLOT(browserUp()) ); + connect(_backAction, SIGNAL(triggered(bool)), + _stackSelection, SLOT(browserBack()) ); + connect(_forwardAction, SIGNAL(triggered(bool)), + _stackSelection, SLOT(browserForward())); + + // flat function profile side bar + _functionDock = new QDockWidget(this); + _functionDock->setObjectName("function-dock"); + _functionDock->setWindowTitle(tr("Flat Profile")); + _functionSelection = new FunctionSelection(this, _functionDock); + _functionDock->setWidget(_functionSelection); + // functionDock needs call to updateView() when getting visible + connect(_functionDock, SIGNAL(visibilityChanged(bool)), + this, SLOT(functionVisibilityChanged(bool))); + + // defaults (later to be adjusted from stored state in config) + addDockWidget(Qt::LeftDockWidgetArea, _partDock ); + addDockWidget(Qt::LeftDockWidgetArea, _stackDock ); + addDockWidget(Qt::LeftDockWidgetArea, _functionDock ); + _stackDock->hide(); + _partDock->hide(); +} + + + + +void QCGTopLevel::createActions() +{ + QString hint; + QIcon icon; + + // file menu actions + _newAction = new QAction(tr("&New"), this); + _newAction->setShortcuts(QKeySequence::New); + _newAction->setStatusTip(tr("Open new empty window")); + connect(_newAction, SIGNAL(triggered()), this, SLOT(newWindow())); + + icon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton); + _openAction = new QAction(icon, tr("&Open..."), this); + _openAction->setShortcuts(QKeySequence::Open); + _openAction->setStatusTip(tr("Open profile data file")); + connect(_openAction, SIGNAL(triggered()), this, SLOT(load())); + + _addAction = new QAction(tr( "&Add..." ), this); + _addAction->setStatusTip(tr("Add profile data to current window")); + connect(_addAction, SIGNAL(triggered(bool)), SLOT(add())); + + _exportAction = new QAction(tr("Export Graph"), this); + _exportAction->setStatusTip(tr("Generate GraphViz file 'callgraph.dot'")); + connect(_exportAction, SIGNAL(triggered(bool)), SLOT(exportGraph())); + + _recentFilesMenuAction = new QAction(tr("Open &Recent"), this); + _recentFilesMenuAction->setMenu(new QMenu(this)); + connect(_recentFilesMenuAction->menu(), SIGNAL(aboutToShow()), + this, SLOT(recentFilesMenuAboutToShow())); + connect(_recentFilesMenuAction->menu(), SIGNAL(triggered(QAction*)), + this, SLOT(recentFilesTriggered(QAction*))); + + _exitAction = new QAction(tr("E&xit"), this); + _exitAction->setShortcut(tr("Ctrl+Q")); + _exitAction->setStatusTip(tr("Exit the application")); + connect(_exitAction, SIGNAL(triggered()), this, SLOT(close())); + + // view menu actions + icon = QApplication::style()->standardIcon(QStyle::SP_BrowserReload); + _cyclesToggleAction = new QAction(icon, tr("Detect Cycles"), this); + _cyclesToggleAction->setCheckable(true); + _cyclesToggleAction->setStatusTip(tr("Do Cycle Detection")); + hint = tr("Detect recursive cycles" + "

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).

" + "

The correct handling for cycles is to detect them and " + "collapse all functions of a cycle into an artificial " + "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.

"); + _cyclesToggleAction->setWhatsThis(hint); + connect(_cyclesToggleAction, SIGNAL(triggered(bool)), + this, SLOT(toggleCycles())); + _cyclesToggleAction->setChecked(GlobalConfig::showCycles()); + + _percentageToggleAction = new QAction(QIcon(":/percent.png"), + tr("Relative Cost"), this); + _percentageToggleAction->setCheckable(true); + _percentageToggleAction->setStatusTip(tr("Show Relative Costs")); + connect(_percentageToggleAction, SIGNAL(triggered(bool)), + this, SLOT(togglePercentage())); + _percentageToggleAction->setChecked(GlobalConfig::showPercentage()); + + _hideTemplatesToggleAction = new QAction(QIcon(":/hidetemplates.png"), + tr("Shorten Templates"), this); + _hideTemplatesToggleAction->setCheckable(true); + _hideTemplatesToggleAction->setStatusTip(tr("Hide Template Parameters " + "in C++ Symbols")); + connect(_hideTemplatesToggleAction, SIGNAL(triggered(bool)), + this, SLOT(toggleHideTemplates())); + _hideTemplatesToggleAction->setChecked(GlobalConfig::hideTemplates()); + hint = tr("Hide Template Parameters in C++ Symbols" + "

If this is switched on, every symbol displayed will have " + "any C++ template parameters hidden, just showing <> " + "instead of a potentially nested template parameter.

" + "

In this mode, you can hover the mouse pointer over the " + "activated symbol label to show a tooltip with the " + "unabbreviated symbol.

"); + _hideTemplatesToggleAction->setWhatsThis(hint); + + _expandedToggleAction = new QAction(QIcon(":/move.png"), + tr("Relative to Parent"), this); + _expandedToggleAction->setCheckable(true); + _expandedToggleAction->setStatusTip( + tr("Show Percentage relative to Parent")); + hint = tr("Show percentage costs relative to parent" + "

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.

" + "
    " + "" + "" + "" + "" + "" + "
    Cost TypeParent Cost
    Function InclusiveTotal
    Function SelfFunction Group (*)/Total
    CallFunction Inclusive
    Source LineFunction Inclusive
" + "

(*) Only if function grouping is switched on " + "(e.g. ELF object grouping).

"); + _expandedToggleAction->setWhatsThis( hint ); + connect(_expandedToggleAction, SIGNAL(triggered(bool)), + this, SLOT(toggleExpanded())); + _expandedToggleAction->setChecked(GlobalConfig::showExpanded()); + + _splittedToggleAction = new QAction(tr("Splitted Visualization"), this); + _splittedToggleAction->setCheckable(true); + _splittedToggleAction->setStatusTip( + tr("Show visualization of two cost items")); + connect(_splittedToggleAction, SIGNAL(triggered(bool)), + this, SLOT(toggleSplitted())); + + _splitDirectionToggleAction = new QAction(tr("Split Horizontal"), this); + _splitDirectionToggleAction->setCheckable(true); + _splitDirectionToggleAction->setStatusTip( + tr("Split visualization area horizontally")); + connect(_splitDirectionToggleAction, SIGNAL(triggered(bool)), + this, SLOT(toggleSplitDirection())); + + _sidebarMenuAction = new QAction(tr("Sidebars"), this); + _sidebarMenuAction->setMenu(new QMenu(this)); + connect( _sidebarMenuAction->menu(), SIGNAL( aboutToShow() ), + this, SLOT( sidebarMenuAboutToShow() )); + + _layoutDup = new QAction(tr("&Duplicate"), this); + connect(_layoutDup, SIGNAL(triggered()), SLOT(layoutDuplicate())); + _layoutDup->setShortcut(Qt::CTRL + Qt::Key_Plus); + _layoutDup->setStatusTip(tr("Duplicate current layout")); + + _layoutRemove = new QAction(tr("&Remove"), this); + connect(_layoutRemove, SIGNAL(triggered()), SLOT(layoutRemove())); + _layoutRemove->setStatusTip(tr("Remove current layout")); + + _layoutNext = new QAction(tr("Go to &Next"), this); + connect(_layoutNext, SIGNAL(triggered()), SLOT(layoutNext())); + _layoutNext->setShortcut(Qt::CTRL + Qt::Key_Right); + _layoutNext->setStatusTip(tr("Switch to next layout")); + + _layoutPrev = new QAction(tr("Go to &Previous"), this); + connect(_layoutPrev, SIGNAL(triggered()), SLOT(layoutPrevious())); + _layoutPrev->setShortcut(Qt::CTRL + Qt::Key_Left); + _layoutPrev->setStatusTip(tr("Switch to previous layout")); + + _layoutRestore = new QAction(tr("&Restore to Default"), this); + connect(_layoutRestore, SIGNAL(triggered()), SLOT(layoutRestore())); + _layoutRestore->setStatusTip(tr("Restore layouts to default")); + + _layoutSave = new QAction(tr("&Save as Default"), this); + connect(_layoutSave, SIGNAL(triggered()), SLOT(layoutSave())); + _layoutSave->setStatusTip(tr("Save layouts as default")); + + // go menu actions + icon = QApplication::style()->standardIcon(QStyle::SP_ArrowUp); + _upAction = new QAction(icon, tr( "Up" ), this ); + _upAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Up) ); + _upAction->setStatusTip(tr("Go Up in Call Stack")); + _upAction->setMenu(new QMenu(this)); + connect(_upAction->menu(), SIGNAL(aboutToShow()), + this, SLOT(upAboutToShow()) ); + connect(_upAction->menu(), SIGNAL(triggered(QAction*)), + this, SLOT(upTriggered(QAction*)) ); + hint = tr("Go to last selected caller of current function"); + _upAction->setToolTip(hint); + + icon = QApplication::style()->standardIcon(QStyle::SP_ArrowBack); + _backAction = new QAction(icon, tr("Back"), this); + _backAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Left) ); + _backAction->setStatusTip(tr("Go Back")); + _backAction->setMenu(new QMenu(this)); + connect(_backAction->menu(), SIGNAL(aboutToShow()), + this, SLOT(backAboutToShow()) ); + connect(_backAction->menu(), SIGNAL(triggered(QAction*)), + this, SLOT(backTriggered(QAction*)) ); + hint = tr("Go back in function selection history"); + _backAction->setToolTip(hint); + + icon = QApplication::style()->standardIcon(QStyle::SP_ArrowForward); + _forwardAction = new QAction(icon, tr("Forward"), this); + _forwardAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Right) ); + _forwardAction->setStatusTip(tr("Go Forward")); + _forwardAction->setMenu(new QMenu(this)); + connect(_forwardAction->menu(), SIGNAL(aboutToShow()), + this, SLOT(forwardAboutToShow()) ); + connect(_forwardAction->menu(), SIGNAL(triggered(QAction*)), + this, SLOT(forwardTriggered(QAction*)) ); + hint = tr("Go forward in function selection history"); + _forwardAction->setToolTip( hint ); + + // settings menu actions + _configureAction = new QAction(tr("&Configure..."), this); + _configureAction->setStatusTip(tr("Configure QCachegrind")); + connect(_configureAction, SIGNAL(triggered()), this, SLOT(configure())); + + // help menu actions + _aboutAction = new QAction(tr("&About QCachegrind..."), this); + _aboutAction->setStatusTip(tr("Show the application's About box")); + connect(_aboutAction, SIGNAL(triggered()), this, SLOT(about())); + + _aboutQtAction = new QAction(tr("About Qt..."), this); + connect(_aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); + + // toolbar actions + _eventTypeBox = new QComboBox(this); + _eventTypeBox->setMinimumContentsLength(25); + hint = tr("Select primary event type of costs"); + _eventTypeBox->setToolTip( hint ); + connect( _eventTypeBox, SIGNAL(activated(const QString&)), + this, SLOT(eventTypeSelected(const QString&))); +} + +void QCGTopLevel::createMenu() +{ + QMenuBar* mBar = menuBar(); + + QMenu* fileMenu = mBar->addMenu(tr("&File")); + fileMenu->addAction(_newAction); + fileMenu->addAction(_openAction); + fileMenu->addAction(_recentFilesMenuAction); + fileMenu->addAction(_addAction); + fileMenu->addSeparator(); + fileMenu->addAction(_exportAction); + fileMenu->addSeparator(); + fileMenu->addAction(_exitAction); + + QMenu* layoutMenu = new QMenu(tr("&Layout"), this); + layoutMenu->addAction(_layoutDup); + layoutMenu->addAction(_layoutRemove); + layoutMenu->addSeparator(); + layoutMenu->addAction(_layoutPrev); + layoutMenu->addAction(_layoutNext); + layoutMenu->addSeparator(); + layoutMenu->addAction(_layoutSave); + layoutMenu->addAction(_layoutRestore); + + QMenu* viewMenu = mBar->addMenu(tr("&View")); + viewMenu->addAction(_cyclesToggleAction); + viewMenu->addAction(_percentageToggleAction); + viewMenu->addAction(_expandedToggleAction); + viewMenu->addAction(_hideTemplatesToggleAction); + viewMenu->addSeparator(); + viewMenu->addAction(_splittedToggleAction); + viewMenu->addAction(_splitDirectionToggleAction); + viewMenu->addMenu(layoutMenu); + + QMenu* goMenu = mBar->addMenu(tr("&Go")); + goMenu->addAction(_backAction); + goMenu->addAction(_forwardAction); + goMenu->addAction(_upAction); + + QMenu* settingsMenu = mBar->addMenu(tr("&Settings")); + settingsMenu->addAction(_sidebarMenuAction); + settingsMenu->addSeparator(); + settingsMenu->addAction(_configureAction); + + QMenu* helpMenu = mBar->addMenu(tr("&Help")); + helpMenu->addAction(QWhatsThis::createAction(this)); + helpMenu->addSeparator(); + helpMenu->addAction(_aboutAction); + helpMenu->addAction(_aboutQtAction); +} + +void QCGTopLevel::createToolbar() +{ + QToolBar* tb = new QToolBar(tr("Main Toolbar"), this); + tb->setObjectName("main-toolbar"); + addToolBar(Qt::TopToolBarArea, tb); + + tb->addAction(_openAction); + tb->addSeparator(); + + tb->addAction(_cyclesToggleAction); + tb->addAction(_percentageToggleAction); + tb->addAction(_expandedToggleAction); + tb->addAction(_hideTemplatesToggleAction); + tb->addSeparator(); + + tb->addAction(_backAction); + tb->addAction(_forwardAction); + tb->addAction(_upAction); + tb->addSeparator(); + + tb->addWidget(_eventTypeBox); +} + + +void QCGTopLevel::about() +{ + QString text, version; + version = QLatin1String("0.7.4kde"); + text = QString("

QCachegrind %1

").arg(version); + text += tr("

QCachegrind is a graphical user interface for analysing " + "profiling data, which helps in the performance optimization " + "phase of developing a computer program. " + "QCachegrind is open-source, and it is distributed under the " + "terms of the GPL v2. For details and source code, see the " + "homepage of the " + "KCachegrind project.

" + "Main author and maintainer: " + "" + "Josef Weidendorfer
" + "(with lots of bug fixes and porting to Qt4 by the KDE team)"); + QMessageBox::about(this, tr("About QCachegrind"), text); +} + +void QCGTopLevel::configure(QString s) +{ + static QString lastPage = QString::null; + + // if no specific config item should be focused, use last page + if (s.isEmpty()) s = lastPage; + ConfigDialog d(_data, this, s); + + if (d.exec() == QDialog::Accepted) { + GlobalConfig::config()->saveOptions(); + configChanged(); + } + lastPage = d.currentPage(); +} + +void QCGTopLevel::togglePartDock() +{ + if (!_partDock->isVisible()) + _partDock->show(); + else + _partDock->hide(); +} + +void QCGTopLevel::toggleStackDock() +{ + if (!_stackDock->isVisible()) + _stackDock->show(); + else + _stackDock->hide(); +} + +void QCGTopLevel::toggleFunctionDock() +{ + if (!_functionDock->isVisible()) + _functionDock->show(); + else + _functionDock->hide(); +} + +void QCGTopLevel::togglePercentage() +{ + setPercentage(_percentageToggleAction->isChecked()); +} + + +void QCGTopLevel::setAbsoluteCost() +{ + setPercentage(false); +} + +void QCGTopLevel::setRelativeCost() +{ + setPercentage(true); +} + +void QCGTopLevel::setPercentage(bool show) +{ + if (GlobalConfig::showPercentage() == show) return; + if (_percentageToggleAction->isChecked() != show) + _percentageToggleAction->setChecked(show); + _expandedToggleAction->setEnabled(show); + GlobalConfig::setShowPercentage(show); + + _partSelection->notifyChange(TraceItemView::configChanged); + _stackSelection->refresh(); + _functionSelection->notifyChange(TraceItemView::configChanged); + _multiView->notifyChange(TraceItemView::configChanged); +} + +void QCGTopLevel::toggleHideTemplates() +{ + bool show = _hideTemplatesToggleAction->isChecked(); + if (GlobalConfig::hideTemplates() == show) return; + GlobalConfig::setHideTemplates(show); + + _partSelection->notifyChange(TraceItemView::configChanged); + _stackSelection->refresh(); + _functionSelection->notifyChange(TraceItemView::configChanged); + _multiView->notifyChange(TraceItemView::configChanged); +} + +void QCGTopLevel::toggleExpanded() +{ + bool show = _expandedToggleAction->isChecked(); + if (GlobalConfig::showExpanded() == show) return; + GlobalConfig::setShowExpanded(show); + + _partSelection->notifyChange(TraceItemView::configChanged); + _stackSelection->refresh(); + _functionSelection->notifyChange(TraceItemView::configChanged); + _multiView->notifyChange(TraceItemView::configChanged); +} + +void QCGTopLevel::toggleCycles() +{ + bool show = _cyclesToggleAction->isChecked(); + if (GlobalConfig::showCycles() == show) return; + GlobalConfig::setShowCycles(show); + + if (!_data) return; + + _data->invalidateDynamicCost(); + _data->updateFunctionCycles(); + + _partSelection->notifyChange(TraceItemView::configChanged); + _stackSelection->rebuildStackList(); + _functionSelection->notifyChange(TraceItemView::configChanged); + _multiView->notifyChange(TraceItemView::configChanged); +} + + +void QCGTopLevel::functionVisibilityChanged(bool v) +{ + if (v) + _functionSelection->updateView(); +} + + +void QCGTopLevel::newWindow() +{ + QCGTopLevel* t = new QCGTopLevel(); + t->show(); +} + + +void QCGTopLevel::load() +{ + QStringList files; + files = QFileDialog::getOpenFileNames(this, + tr("Open Callgrind Data"), + _lastFile, + tr("Callgrind Files (callgrind.*);;All Files (*)")); + load(files); +} + +void QCGTopLevel::load(QStringList files, bool addToRecentFiles) +{ + if (files.isEmpty()) return; + _lastFile = files[0]; + + if (_data && _data->parts().count()>0) { + + // In new window + QCGTopLevel* t = new QCGTopLevel(); + t->show(); + t->loadDelayed(files, addToRecentFiles); + return; + } + + // this constructor enables progress bar callbacks + TraceData* d = new TraceData(this); + int filesLoaded = d->load(files); + if (filesLoaded >0) + setData(d); + + if (!addToRecentFiles) return; + + // add to recent file list in config + QStringList recentFiles; + ConfigGroup* generalConfig = ConfigStorage::group("GeneralSettings"); + recentFiles = generalConfig->value("RecentFiles", + QStringList()).toStringList(); + foreach(QString file, files) { + recentFiles.removeAll(file); + if (filesLoaded >0) + recentFiles.prepend(file); + if (recentFiles.count() >5) + recentFiles.removeLast(); + } + generalConfig->setValue("RecentFiles", recentFiles); + delete generalConfig; +} + + +void QCGTopLevel::add() +{ + QStringList files; + files = QFileDialog::getOpenFileNames(this, + tr("Add Callgrind Data"), + _lastFile, + tr("Callgrind Files (callgrind.*);;All Files (*)")); + add(files); +} + + +void QCGTopLevel::add(QStringList files) +{ + if (files.isEmpty()) return; + _lastFile = files[0]; + + if (_data) { + _data->load(files); + + // GUI update for added data + configChanged(); + return; + } + + // this constructor enables progress bar callbacks + TraceData* d = new TraceData(this); + int filesLoaded = d->load(files); + if (filesLoaded >0) + setData(d); +} + +void QCGTopLevel::loadDelayed(QString file, bool addToRecentFiles) +{ + _loadFilesDelayed << file; + + _addToRecentFiles = addToRecentFiles; + QTimer::singleShot(0, this, SLOT(loadFilesDelayed())); +} + +void QCGTopLevel::loadDelayed(QStringList files, bool addToRecentFiles) +{ + _loadFilesDelayed << files; + + _addToRecentFiles = addToRecentFiles; + QTimer::singleShot(0, this, SLOT(loadFilesDelayed())); +} + +void QCGTopLevel::loadFilesDelayed() +{ + if (_loadFilesDelayed.isEmpty()) return; + + load(_loadFilesDelayed, _addToRecentFiles); + _loadFilesDelayed.clear(); +} + + +void QCGTopLevel::exportGraph() +{ + if (!_data || !_function) return; + + QString n = QString("callgraph.dot"); + GraphExporter ge(_data, _function, _eventType, _groupType, n); + ge.writeDot(); + +#ifdef Q_OS_UNIX + // shell commands only work in UNIX + QString cmd = QString("(dot %1 -Tps > %2.ps; xdg-open %3.ps)&") + .arg(n).arg(n).arg(n); + if (::system(QFile::encodeName( cmd ))<0) + qDebug() << "QCGTopLevel::exportGraph: can not run " << cmd; +#endif +} + + +bool QCGTopLevel::setEventType(QString s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->type(s) : 0; + + // if costtype with given name not found, use first available + if (!ct && _data) ct = _data->eventTypes()->type(0); + + return setEventType(ct); +} + +bool QCGTopLevel::setEventType2(QString s) +{ + EventType* ct; + + // Special type tr("(Hidden)") gives 0 + ct = (_data) ? _data->eventTypes()->type(s) : 0; + + return setEventType2(ct); +} + +void QCGTopLevel::eventTypeSelected(const QString& s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; + setEventType(ct); +} + +void QCGTopLevel::eventType2Selected(const QString& s) +{ + EventType* ct; + + ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; + setEventType2(ct); +} + +bool QCGTopLevel::setEventType(EventType* ct) +{ + if (_eventType == ct) return false; + _eventType = ct; + + if (ct) { + int idx = _eventTypeBox->findText(ct->longName()); + if (idx >=0) _eventTypeBox->setCurrentIndex(idx); + } + + _partSelection->setEventType(_eventType); + _stackSelection->setEventType(_eventType); + _functionSelection->setEventType(_eventType); + _multiView->setEventType(_eventType); + + updateStatusBar(); + + return true; +} + +bool QCGTopLevel::setEventType2(EventType* ct) +{ + if (_eventType2 == ct) return false; + _eventType2 = ct; + + QString longName = ct ? ct->longName() : tr("(Hidden)"); + + _partSelection->setEventType2(_eventType2); + _stackSelection->setEventType2(_eventType2); + _functionSelection->setEventType2(_eventType2); + _multiView->setEventType2(_eventType2); + + updateStatusBar(); + + return true; +} + + +void QCGTopLevel::groupTypeSelected(int cg) +{ + switch(cg) { + case 0: setGroupType( ProfileContext::Function ); break; + case 1: setGroupType( ProfileContext::Object ); break; + case 2: setGroupType( ProfileContext::File ); break; + case 3: setGroupType( ProfileContext::Class ); break; + case 4: setGroupType( ProfileContext::FunctionCycle ); break; + default: break; + } +} + +bool QCGTopLevel::setGroupType(QString s) +{ + ProfileContext::Type gt; + + gt = ProfileContext::type(s); + // only allow Function/Object/File/Class as grouptype + switch(gt) { + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + case ProfileContext::FunctionCycle: + break; + default: + gt = ProfileContext::Function; + } + + return setGroupType(gt); +} + +bool QCGTopLevel::setGroupType(ProfileContext::Type gt) +{ + if (_groupType == gt) return false; + _groupType = gt; + + int idx = -1; + switch(gt) { + case ProfileContext::Function: idx = 0; break; + case ProfileContext::Object: idx = 1; break; + case ProfileContext::File: idx = 2; break; + case ProfileContext::Class: idx = 3; break; + case ProfileContext::FunctionCycle: idx = 4; break; + default: + break; + } + + if (idx==-1) return false; + +#if 0 + if (saGroup->currentItem() != idx) + saGroup->setCurrentItem(idx); +#endif + + _stackSelection->setGroupType(_groupType); + + _partSelection->set(_groupType); + _functionSelection->set(_groupType); + _multiView->set(_groupType); + + updateStatusBar(); + + return true; +} + +bool QCGTopLevel::setGroup(QString s) +{ + TraceCostItem* ci = _functionSelection->group(s); + if (!ci) + return false; + + return setGroup(ci); +} + + +bool QCGTopLevel::setGroup(TraceCostItem* g) +{ + if (_group == g) return false; + _group = g; + + _functionSelection->setGroup(g); + updateStatusBar(); + + return true; +} + +bool QCGTopLevel::setFunction(QString s) +{ + if (!_data) return false; + + ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType); + if (!f) return false; + + return setFunction((TraceFunction*)f); +} + +bool QCGTopLevel::setFunction(TraceFunction* f) +{ + if (_function == f) return false; + _function = f; + + _multiView->activate(f); + _functionSelection->activate(f); + _partSelection->activate(f); + _stackSelection->setFunction(_function); + + StackBrowser* b = _stackSelection->browser(); + if (b) { + // do not disable up: a press forces stack-up extending... + _forwardAction->setEnabled(b->canGoForward()); + _backAction->setEnabled(b->canGoBack()); + } + +#if TRACE_UPDATES + qDebug("QCGTopLevel::setFunction(%s), lastSender %s", + f ? f->prettyName().toAscii() : "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 QCGTopLevel::setEventTypeDelayed(EventType* ct) +{ + _eventTypeDelayed = ct; + QTimer::singleShot (0, this, SLOT(setEventTypeDelayed())); +} + +void QCGTopLevel::setEventType2Delayed(EventType* ct) +{ + _eventType2Delayed = ct; + QTimer::singleShot (0, this, SLOT(setEventType2Delayed())); +} + +void QCGTopLevel::setEventTypeDelayed() +{ + setEventType(_eventTypeDelayed); +} + +void QCGTopLevel::setEventType2Delayed() +{ + setEventType2(_eventType2Delayed); +} + +void QCGTopLevel::setGroupTypeDelayed(ProfileContext::Type gt) +{ + _groupTypeDelayed = gt; + QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed())); +} + +void QCGTopLevel::setGroupTypeDelayed() +{ + setGroupType(_groupTypeDelayed); +} + +void QCGTopLevel::setGroupDelayed(TraceCostItem* g) +{ +#if TRACE_UPDATES + qDebug("QCGTopLevel::setGroupDelayed(%s), sender %s", + g ? g->prettyName().toAscii() : "0", + _lastSender ? _lastSender->name() :"0" ); +#endif + + _groupDelayed = g; + QTimer::singleShot (0, this, SLOT(setGroupDelayed())); +} + +void QCGTopLevel::setGroupDelayed() +{ + setGroup(_groupDelayed); +} + +void QCGTopLevel::setDirectionDelayed(TraceItemView::Direction d) +{ + _directionDelayed = d; + QTimer::singleShot (0, this, SLOT(setDirectionDelayed())); +} + +void QCGTopLevel::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 QCGTopLevel::setTraceItemDelayed(CostItem* i) +{ + // no need to select same item a 2nd time... + if (_traceItemDelayed == i) return; + _traceItemDelayed = i; + _lastSender = sender(); + + qDebug() << "Selected " << (i ? i->fullName() : "(none)"); + +#if TRACE_UPDATES + qDebug("QCGTopLevel::setTraceItemDelayed(%s), sender %s", + i ? i->prettyName().toAscii() : "0", + _lastSender ? _lastSender->name() :"0" ); +#endif + + QTimer::singleShot (0, this, SLOT(setTraceItemDelayed())); +} + +void QCGTopLevel::setTraceItemDelayed() +{ + if (!_traceItemDelayed) return; + + switch(_traceItemDelayed->type()) { + case ProfileContext::Function: + case ProfileContext::FunctionCycle: + setFunction((TraceFunction*)_traceItemDelayed); + break; + + case ProfileContext::Object: + case ProfileContext::File: + case ProfileContext::Class: + _multiView->activate(_traceItemDelayed); + break; + + case ProfileContext::Instr: + case ProfileContext::Line: + // only for multiview + _multiView->activate(_traceItemDelayed); + break; + + 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 QCGTopLevel::setData(TraceData* data) +{ + if (data == _data) return; + + _lastSender = 0; + + saveTraceSettings(); + + if (_data) { + _partSelection->setData(0); + _stackSelection->setData(0); + _functionSelection->setData(0); + _multiView->setData(0); + _multiView->updateView(true); + + // we are the owner... + delete _data; + } + + // reset members + resetState(); + + _data = data; + + // fill cost type list + QStringList types; + + if (_data) { + /* add all supported virtual types */ + EventTypeSet* m = _data->eventTypes(); + m->addKnownDerivedTypes(); + + /* first, fill selection list with available cost types */ + for (int i=0;irealCount();i++) + types << m->realType(i)->longName(); + for (int i=0;iderivedCount();i++) + types << m->derivedType(i)->longName(); + } + _eventTypes = types; + _eventTypeBox->addItems(types); + + _stackSelection->setData(_data); + _partSelection->setData(_data); + _functionSelection->setData(_data); + _multiView->setData(_data); + // Force update of _data in all children of _multiView + // This is needed to make restoring of activeItem work! + _multiView->updateView(true); + + /* this is needed to let the other widgets know the types */ + restoreTraceTypes(); + + restoreTraceSettings(); + + QString caption; + if (_data) { + caption = QDir::toNativeSeparators(_data->traceName()); + if (!_data->command().isEmpty()) + caption += " [" + _data->command() + ']'; + } + setWindowTitle(caption); + + if (!_data || (!_forcePartDock && _data->parts().count()<2)) + _partDock->hide(); + else + _partDock->show(); + + updateStatusBar(); +} + +void QCGTopLevel::addEventTypeMenu(QMenu* popup, bool withCost2) +{ + if (_data) { + QMenu *popup1, *popup2 = 0; + QAction* action; + + popup1 = popup->addMenu(tr("Primary Event Type")); + connect(popup1, SIGNAL(triggered(QAction*)), + this, SLOT(setEventType(QAction*))); + + if (withCost2) { + popup2 = popup->addMenu(tr("Secondary Event Type")); + connect(popup2, SIGNAL(triggered(QAction*)), + this, SLOT(setEventType2(QAction*))); + + if (_eventType2) { + action = popup2->addAction(tr("Hide")); + action->setData(199); + popup2->addSeparator(); + } + } + + EventTypeSet* m = _data->eventTypes(); + EventType* ct; + for (int i=0;irealCount();i++) { + ct = m->realType(i); + + action = popup1->addAction(ct->longName()); + action->setCheckable(true); + action->setData(100+i); + if (_eventType == ct) action->setChecked(true); + + if (popup2) { + action = popup2->addAction(ct->longName()); + action->setCheckable(true); + action->setData(100+i); + if (_eventType2 == ct) action->setChecked(true); + } + } + + for (int i=0;iderivedCount();i++) { + ct = m->derivedType(i); + + action = popup1->addAction(ct->longName()); + action->setCheckable(true); + action->setData(200+i); + if (_eventType == ct) action->setChecked(true); + + if (popup2) { + action = popup2->addAction(ct->longName()); + action->setCheckable(true); + action->setData(200+i); + if (_eventType2 == ct) action->setChecked(true); + } + } + } + + if (GlobalConfig::showPercentage()) + popup->addAction(tr("Show Absolute Cost"), + this, SLOT(setAbsoluteCost())); + else + popup->addAction(tr("Show Relative Cost"), + this, SLOT(setRelativeCost())); +} + +bool QCGTopLevel::setEventType(QAction* action) +{ + if (!_data) return false; + int id = action->data().toInt(0); + + EventTypeSet* m = _data->eventTypes(); + EventType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->derivedType(id-200); + + return ct ? setEventType(ct) : false; +} + +bool QCGTopLevel::setEventType2(QAction* action) +{ + if (!_data) return false; + int id = action->data().toInt(0); + + EventTypeSet* m = _data->eventTypes(); + EventType* ct=0; + if (id >=100 && id<199) ct = m->realType(id-100); + if (id >=200 && id<299) ct = m->derivedType(id-200); + + return setEventType2(ct); +} + +void QCGTopLevel::addGoMenu(QMenu* popup) +{ + StackBrowser* b = _stackSelection->browser(); + if (b) { + if (b->canGoBack()) + popup->addAction(tr("Go Back"), this, SLOT(goBack())); + if (b->canGoForward()) + popup->addAction(tr("Go Forward"), this, SLOT(goForward())); + } + // do not disable up: a press forces stack-up extending... + popup->addAction(tr("Go Up"), this, SLOT(goUp())); +} + +void QCGTopLevel::goBack() +{ + setDirectionDelayed(TraceItemView::Back); +} + +void QCGTopLevel::goForward() +{ + setDirectionDelayed(TraceItemView::Forward); +} + +void QCGTopLevel::goUp() +{ + setDirectionDelayed(TraceItemView::Up); +} + +QString QCGTopLevel::traceKey() +{ + if (!_data || _data->command().isEmpty()) return QString(); + + QString name = _data->command(); + QString key; + for (int l=0;lvalue(QString("GroupType%1").arg(key),QString()).toString(); + eventType = pConfig->value(QString("EventType%1").arg(key),QString()).toString(); + eventType2 = pConfig->value(QString("EventType2%1").arg(key),QString()).toString(); + delete pConfig; + + ConfigGroup* cConfig = ConfigStorage::group("CurrentState"); + if (groupType.isEmpty()) + groupType = cConfig->value("GroupType",QString()).toString(); + if (eventType.isEmpty()) + eventType = cConfig->value("EventType",QString()).toString(); + if (eventType2.isEmpty()) + eventType2 = cConfig->value("EventType2",QString()).toString(); + delete cConfig; + + setGroupType(groupType); + setEventType(eventType); + setEventType2(eventType2); + + // if still no event type set, use first available + if (!_eventType && !_eventTypes.isEmpty()) + eventTypeSelected(_eventTypes.first()); + + ConfigGroup* aConfig = ConfigStorage::group("Layouts"); + _layoutCount = aConfig->value(QString("Count%1").arg(key), 0).toInt(); + _layoutCurrent = aConfig->value(QString("Current%1").arg(key), 0).toInt(); + delete aConfig; + + 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 QCGTopLevel::restoreTraceSettings() +{ + if (!_data) return; + + QString key = traceKey(); + + restoreCurrentState(key); + + ConfigGroup* pConfig = ConfigStorage::group("TracePositions"); + QString group = pConfig->value(QString("Group%1").arg(key),QString()).toString(); + delete pConfig; + if (!group.isEmpty()) setGroup(group); + + // 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")) { +#if 1 + _functionSelection->selectTopFunction(); +#else + HighestCostList hc; + hc.clear(50); + TraceFunctionMap::Iterator it; + for ( it = _data->functionMap().begin(); + it != _data->functionMap().end(); ++it ) + hc.addCost(&(*it), (*it).inclusive()->subCost(_eventType)); + + setFunction( (TraceFunction*) hc[0]); +#endif + } + } +} + + +/* Layout */ + +void QCGTopLevel::layoutDuplicate() +{ + // save current and allocate a new slot + _multiView->saveLayout(QString("Layout%1-MainView").arg(_layoutCurrent), + traceKey()); + _layoutCurrent = _layoutCount; + _layoutCount++; + + updateLayoutActions(); + + qDebug() << "QCGTopLevel::layoutDuplicate: count " << _layoutCount; +} + +void QCGTopLevel::layoutRemove() +{ + if (_layoutCount <2) return; + + int from = _layoutCount-1; + if (_layoutCurrent == from) { _layoutCurrent--; from--; } + + // restore from last and decrement count + _multiView->restoreLayout(QString("Layout%1-MainView").arg(from), + traceKey()); + _layoutCount--; + + updateLayoutActions(); + + qDebug() << "QCGTopLevel::layoutRemove: count " << _layoutCount; +} + +void QCGTopLevel::layoutNext() +{ + if (_layoutCount <2) return; + + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + _layoutCurrent++; + if (_layoutCurrent == _layoutCount) _layoutCurrent = 0; + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + qDebug() << "QCGTopLevel::layoutNext: current " << _layoutCurrent; +} + +void QCGTopLevel::layoutPrevious() +{ + if (_layoutCount <2) return; + + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + _layoutCurrent--; + if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1; + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + qDebug() << "QCGTopLevel::layoutPrevious: current " << _layoutCurrent; +} + +void QCGTopLevel::layoutSave() +{ + QString key = traceKey(); + QString layoutPrefix = QString("Layout%1-MainView"); + + _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); + + // save all layouts as defaults (ie. without any group name postfix) + for(int i=0;i<_layoutCount;i++) { + _multiView->restoreLayout(layoutPrefix.arg(i), key); + _multiView->saveLayout(layoutPrefix.arg(i), QString()); + } + // restore the previously saved current layout + _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); + + ConfigGroup* layoutConfig = ConfigStorage::group("Layouts"); + layoutConfig->setValue("DefaultCount", _layoutCount); + layoutConfig->setValue("DefaultCurrent", _layoutCurrent); + delete layoutConfig; +} + +void QCGTopLevel::layoutRestore() +{ + ConfigGroup* layoutConfig = ConfigStorage::group("Layouts"); + _layoutCount = layoutConfig->value("DefaultCount", 0).toInt(); + _layoutCurrent = layoutConfig->value("DefaultCurrent", 0).toInt(); + delete layoutConfig; + + if (_layoutCount == 0) { + _layoutCount++; + return; + } + + QString layoutPrefix = QString("Layout%1-MainView"); + _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey()); + + updateLayoutActions(); +} + + +void QCGTopLevel::updateLayoutActions() +{ + if (_layoutNext) + _layoutNext->setEnabled(_layoutCount>1); + + if (_layoutPrev) + _layoutPrev->setEnabled(_layoutCount>1); + + if (_layoutRemove) + _layoutRemove->setEnabled(_layoutCount>1); + + if (_statusbar) + _statusbar->showMessage(tr("Layout Count: %1").arg(_layoutCount), + 1000); +} + + +void QCGTopLevel::updateStatusBar() +{ + if (!_data || _data->parts().count()==0) { + _statusLabel->setText(tr("No profile data file loaded.")); + return; + } + + QString status = QString("%1 [%2] - ") + .arg(_data->shortTraceName()) + .arg(_data->activePartRange()); + + if (_eventType) { + status += tr("Total %1 Cost: %2") + .arg(_eventType->longName()) + .arg(_data->prettySubCost(_eventType)); + + /* this gets too long... + if (_eventType2 && (_eventType2 != _eventType)) + status += tr(", %1 Cost: %2") + .arg(_eventType2->longName()) + .arg(_data->prettySubCost(_eventType2)); + */ + } + else + status += tr("No event type selected"); + + /* Not working... should give group of selected function + + if (_groupType != ProfileContext::Function) { + status += QString(" - %1 '%2'") + .arg(ProfileContext::trTypeName(_groupType)) + .arg(_group ? _group->prettyName() : tr("(None)")); + } + */ + + _statusLabel->setText(status); +} + + +void QCGTopLevel::closeEvent(QCloseEvent* event) +{ + GlobalConfig::config()->saveOptions(); + + saveTraceSettings(); + saveCurrentState(QString::null); + + // 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; + + ConfigGroup* topConfig = ConfigStorage::group("TopWindow"); + topConfig->setValue("ForcePartDockVisible", _forcePartDock, false); + topConfig->setValue("State", saveState()); + topConfig->setValue("Geometry", saveGeometry()); + delete topConfig; + + event->accept(); +} + + +void QCGTopLevel::toggleSplitted() +{ + int count = _multiView->childCount(); + if (count<1) count = 1; + if (count>2) count = 2; + count = 3-count; + _multiView->setChildCount(count); + + _splittedToggleAction->setChecked(count>1); + _splitDirectionToggleAction->setEnabled(count>1); + _splitDirectionToggleAction->setChecked(_multiView->orientation() == + Qt::Horizontal); +} + +void QCGTopLevel::toggleSplitDirection() +{ + _multiView->setOrientation( _splitDirectionToggleAction->isChecked() ? + Qt::Horizontal : Qt::Vertical ); +} + + + +// this is called after a config change in the dialog +void QCGTopLevel::configChanged() +{ + // invalidate found/cached dirs of source files + if (_data) + _data->resetSourceDirs(); + + _partSelection->notifyChange(TraceItemView::configChanged); + _stackSelection->refresh(); + _functionSelection->notifyChange(TraceItemView::configChanged); + _multiView->notifyChange(TraceItemView::configChanged); +} + + + +void QCGTopLevel::activePartsChangedSlot(const TracePartList& list) +{ + if (!_data) return; + + if (!_data->activateParts(list)) { +// qDebug("QCGTopLevel::activePartsChangedSlot: No Change!"); + return; + } + _activeParts = list; + + _partSelection->set(list); + _stackSelection->refresh(); + _functionSelection->set(list); + _multiView->set(list); + + updateStatusBar(); +} + +void QCGTopLevel::partsHideSelectedSlotDelayed() +{ + QTimer::singleShot( 0, this, SLOT(partsHideSelectedSlot()) ); +} + +// this puts selected parts into hidden list, +// deselects them and makes the remaining parts selected +void QCGTopLevel::partsHideSelectedSlot() +{ + if (!_data) return; + + TracePartList newHidden, newActive; + foreach(TracePart* part, _data->parts()) { + if (_activeParts.contains(part) || + _hiddenParts.contains(part)) + newHidden.append(part); + else + newActive.append(part); + } + + _hiddenParts = newHidden; + _partSelection->hiddenPartsChangedSlot(_hiddenParts); + +#if 0 + _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); + _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); +#endif + + activePartsChangedSlot(newActive); +} + +void QCGTopLevel::partsUnhideAllSlotDelayed() +{ + QTimer::singleShot( 0, this, SLOT(partsUnhideAllSlot()) ); +} + +// this unhides all hidden parts. Does NOT change selection +void QCGTopLevel::partsUnhideAllSlot() +{ + if (!_data) return; + + _hiddenParts.clear(); + _partSelection->hiddenPartsChangedSlot(_hiddenParts); + +#if 0 + _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); + _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); +#endif +} + +void QCGTopLevel::forwardAboutToShow() +{ + QMenu *popup = _forwardAction->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + QAction* action; + + if (!hi) { + popup->addAction(tr("(No Stack)")); + return; + } + + hi = hi->next(); + if (!hi) { + popup->addAction(tr("(No next function)")); + return; + } + + int count = 1; + while (countfunction(); + if (!f) break; + + QString name = GlobalConfig::shortenSymbol(f->prettyName()); + + //qDebug("forward: Adding %s", name.toAscii()); + action = popup->addAction(name); + action->setData(count); + + hi = hi->next(); + count++; + } +} + +void QCGTopLevel::backAboutToShow() +{ + QMenu *popup = _backAction->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f; + QAction* action; + + if (!hi) { + popup->addAction(tr("(No Stack)")); + return; + } + + hi = hi->last(); + if (!hi) { + popup->addAction(tr("(No previous function)")); + return; + } + + int count = 1; + while (countfunction(); + if (!f) break; + + QString name = GlobalConfig::shortenSymbol(f->prettyName()); + + //qDebug("back: Adding %s", name.toAscii()); + action = popup->addAction(name); + action->setData(count); + + hi = hi->last(); + count++; + } +} + +void QCGTopLevel::upAboutToShow() +{ + QMenu *popup = _upAction->menu(); + + popup->clear(); + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + TraceFunction* f = hi ? hi->function() : 0; + QAction* action; + + if (!f) { + popup->addAction(tr("(No Stack)")); + return; + } + f = hi->stack()->caller(f, false); + if (!f) { + popup->addAction(tr("(No Function Up)")); + return; + } + + int count = 1; + while (countprettyName()); + + action = popup->addAction(name); + action->setData(count); + + f = hi->stack()->caller(f, false); + count++; + } +} + +void QCGTopLevel::forwardTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("forwardTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (count>1) { + b->goForward(); + count--; + } + _stackSelection->browserForward(); +} + +void QCGTopLevel::backTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("backTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + if (!b) return; + + while (count>1) { + b->goBack(); + count--; + } + _stackSelection->browserBack(); +} + +void QCGTopLevel::upTriggered(QAction* action) +{ + int count = action->data().toInt(0); + //qDebug("upTriggered: %d", count); + if( count <= 0) + return; + + StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; + HistoryItem* hi = b ? b->current() : 0; + if (!hi) return; + + TraceFunction* f = hi->function(); + + while (count>0 && f) { + f = hi->stack()->caller(f, false); + count--; + } + + //qDebug("upActivated: %s", f ? f->prettyName().toAscii() : "??" ); + if (f) + setFunction(f); +} + +void QCGTopLevel::showMessage(const QString& msg, int ms) +{ + if (_statusbar) + _statusbar->showMessage(msg, ms); +} + +void QCGTopLevel::showStatus(const QString& msg, int progress) +{ + static bool msgUpdateNeeded = true; + + if (!_statusbar) return; + + if (msg.isEmpty()) { + //reset status + if (_progressBar) { + _statusbar->removeWidget(_progressBar); + delete _progressBar; + _progressBar = 0; + } + _statusbar->clearMessage(); + _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->addPermanentWidget(_progressBar, 1); + _progressBar->show(); + msgUpdateNeeded = true; + } + + _progressStart.restart(); + + if (msgUpdateNeeded) { + _statusbar->showMessage(msg); + msgUpdateNeeded = false; + } + _progressBar->setValue(progress); + + // let the progress bar update itself + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); +} + +void QCGTopLevel::loadStart(const QString& filename) +{ + showStatus(QString("Loading %1").arg(filename), 0); + Logger::_filename = filename; +} + +void QCGTopLevel::loadFinished(const QString& msg) +{ + showStatus(QString::null, 0); + if (!msg.isEmpty()) + showMessage(QString("Error loading %1: %2").arg(_filename).arg(msg), + 2000); +} + +void QCGTopLevel::loadProgress(int progress) +{ + showStatus(QString("Loading %1").arg(_filename), progress); +} + +void QCGTopLevel::loadError(int line, const QString& msg) +{ + qCritical() << "Loading" << _filename + << ":" << line << ": " << msg; +} + +void QCGTopLevel::loadWarning(int line, const QString& msg) +{ + qWarning() << "Loading" << _filename + << ":" << line << ": " << msg; +} + +#include "moc_qcgtoplevel.cpp" diff --git a/kcachegrind/qcachegrind/qcgtoplevel.h b/kcachegrind/qcachegrind/qcgtoplevel.h new file mode 100644 index 00000000..0aa64753 --- /dev/null +++ b/kcachegrind/qcachegrind/qcgtoplevel.h @@ -0,0 +1,262 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer + + 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. +*/ + +/* + * QCachegrind top level window + */ + +#ifndef QCGTOPLEVEL_H +#define QCGTOPLEVEL_H + +#include +#include +#include +#include +#include + +#include "logger.h" +#include "traceitemview.h" +#include "tracedata.h" +#include "toplevelbase.h" + +class MultiView; +class QDockWidget; +class QLabel; +class QComboBox; +class QProgressBar; +class QMenu; + +class TraceData; +class MainWidget; +class PartSelection; +class FunctionSelection; +class StackSelection; +class TraceFunction; + +class QCGTopLevel : public QMainWindow, public Logger, public TopLevelBase +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "net.sf.qcachegrind") + +public: + QCGTopLevel(); + ~QCGTopLevel(); + + TraceData* data() { return _data; } + void setData(TraceData*); + + void createActions(); + void createDocks(); + void createMenu(); + void createToolbar(); + + void closeEvent(QCloseEvent*); + + ProfileContext::Type groupType() { return _groupType; } + EventType* eventType() { return _eventType; } + EventType* eventType2() { return _eventType2; } + TracePartList activeParts() { return _activeParts; } + TracePartList hiddenParts() { return _hiddenParts; } + + /* convenience functions for often used context menu items */ + void addEventTypeMenu(QMenu*,bool); + void addGoMenu(QMenu*); + + // Logger overwrites: notifications for file loading + virtual void loadStart(const QString& filename); + virtual void loadProgress(int progress); // 0 - 100 + virtual void loadWarning(int line, const QString& msg); + virtual void loadError(int line, const QString& msg); + virtual void loadFinished(const QString& msg); // msg could be error + +public slots: + void load(); + void load(QStringList files, bool addToRecentFiles = true); + void add(); + void add(QStringList files); + + // shows the main window before loading to see loading progress + void loadDelayed(QString file, bool addToRecentFiles = true); + void loadDelayed(QStringList files, bool addToRecentFiles = true); + + void exportGraph(); + void newWindow(); + void configure(QString page = QString::null); + void about(); + + // layouts + void layoutDuplicate(); + void layoutRemove(); + void layoutNext(); + void layoutPrevious(); + void layoutSave(); + void layoutRestore(); + void updateLayoutActions(); + + void updateStatusBar(); + void eventTypeSelected(const QString&); + void eventType2Selected(const QString&); + void groupTypeSelected(int); + void toggleSplitted(); + void toggleSplitDirection(); + void togglePartDock(); + void toggleStackDock(); + void toggleFunctionDock(); + void functionVisibilityChanged(bool); + void togglePercentage(); + void setPercentage(bool); + void setAbsoluteCost(); + void setRelativeCost(); + void toggleHideTemplates(); + void toggleExpanded(); + void toggleCycles(); + void recentFilesMenuAboutToShow(); + void recentFilesTriggered(QAction*); + void sidebarMenuAboutToShow(); + void forwardAboutToShow(); + void forwardTriggered(QAction*); + void backAboutToShow(); + void backTriggered(QAction*); + void upAboutToShow(); + void upTriggered(QAction*); + + bool setEventType(EventType*); + bool setEventType2(EventType*); + bool setEventType(QString); + bool setEventType2(QString); + bool setEventType(QAction*); + bool setEventType2(QAction*); + bool setGroupType(ProfileContext::Type); + 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 + * are not allowed to delete list entries. + */ + void setEventTypeDelayed(EventType*); + void setEventType2Delayed(EventType*); + void setGroupTypeDelayed(ProfileContext::Type); + void setGroupDelayed(TraceCostItem*); + void setTraceItemDelayed(CostItem*); + void partsHideSelectedSlotDelayed(); + void partsUnhideAllSlotDelayed(); + void goBack(); + void goForward(); + void goUp(); + void setDirectionDelayed(TraceItemView::Direction); + + /* SingleShot Slots (without parameters) for the delayed versions */ + void setEventTypeDelayed(); + void setEventType2Delayed(); + void setGroupTypeDelayed(); + void setGroupDelayed(); + void setTraceItemDelayed(); + void loadFilesDelayed(); + void setDirectionDelayed(); + + // configuration has changed + void configChanged(); + + //void refresh(); + + // progress in status bar, empty message disables progress display + void showStatus(const QString& msg, int progress); + void showMessage(const QString&, int msec); + +private: + void resetState(); + void createLayoutActions(); + void createMiscActions(); + void setupMainWidget(MainWidget*); + void setupPartSelection(PartSelection*); + void restoreCurrentState(const QString& postfix); + void saveCurrentState(const QString& postfix); + void saveTraceSettings(); + QString traceKey(); + void restoreTraceTypes(); + void restoreTraceSettings(); + + QStatusBar* _statusbar; + QLabel* _statusLabel; + QString _progressMsg; + QTime _progressStart; + QProgressBar* _progressBar; + + MultiView* _multiView; + Qt::Orientation _spOrientation; + bool _twoMainWidgets; + FunctionSelection* _functionSelection; + PartSelection* _partSelection; + StackSelection* _stackSelection; + QDockWidget *_partDock, *_stackDock, *_functionDock; + bool _forcePartDock; + + // menu/toolbar actions + QAction *_newAction, *_openAction, *_addAction, *_reloadAction; + QAction *_exportAction, *_dumpToggleAction, *_exitAction; + QAction *_sidebarMenuAction, *_recentFilesMenuAction; + QAction *_cyclesToggleAction, *_percentageToggleAction; + QAction *_expandedToggleAction, *_hideTemplatesToggleAction; + QAction *_splittedToggleAction, *_splitDirectionToggleAction; + QAction *_layoutNext, *_layoutPrev, *_layoutRemove, *_layoutDup; + QAction *_layoutRestore, *_layoutSave; + QAction *_upAction, *_forwardAction, *_backAction; + QAction *_configureAction, *_aboutAction, *_aboutQtAction; + QComboBox* _eventTypeBox; + + TraceFunction* _function; + const QObject* _lastSender; + + // trace data shown in this window + TraceData* _data; + // subcost types used for visualization + EventType* _eventType; + EventType* _eventType2; + QStringList _eventTypes; + // grouping of function list + ProfileContext::Type _groupType; + // selected group + TraceCostItem* _group; + // selected parts + TracePartList _activeParts; + // hidden parts + TracePartList _hiddenParts; + // layouts + int _layoutCurrent, _layoutCount; + // remember last file directory for new QFileDialogs + QString _lastFile; + + // for delayed slots + EventType* _eventTypeDelayed; + EventType* _eventType2Delayed; + ProfileContext::Type _groupTypeDelayed; + TraceCostItem* _groupDelayed; + CostItem* _traceItemDelayed; + QStringList _loadFilesDelayed; + bool _addToRecentFiles; + TraceItemView::Direction _directionDelayed; +}; + +#endif // QCGTOPLEVEL_H diff --git a/kcachegrind/qcachegrind/qtcolorbutton.cpp b/kcachegrind/qcachegrind/qtcolorbutton.cpp new file mode 100644 index 00000000..3279b344 --- /dev/null +++ b/kcachegrind/qcachegrind/qtcolorbutton.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copied from Qt 4.6.0 sources, directory tools/shared/qtgradienteditor +** with small modifications to allow compilation with Qt 4.4 and Qt 5.0 +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +****************************************************************************/ + +#include "qtcolorbutton.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtColorButtonPrivate +{ + QtColorButton *q_ptr; + Q_DECLARE_PUBLIC(QtColorButton) +public: + QColor m_color; +#ifndef QT_NO_DRAGANDDROP + QColor m_dragColor; + QPoint m_dragStart; + bool m_dragging; +#endif + bool m_backgroundCheckered; + + void slotEditColor(); + QColor shownColor() const; + QPixmap generatePixmap() const; +}; + +void QtColorButtonPrivate::slotEditColor() +{ + const QColor newColor = QColorDialog::getColor(m_color, q_ptr); + if (!newColor.isValid() || newColor == q_ptr->color()) + return; + q_ptr->setColor(newColor); + emit q_ptr->colorChanged(m_color); +} + +QColor QtColorButtonPrivate::shownColor() const +{ +#ifndef QT_NO_DRAGANDDROP + if (m_dragging) + return m_dragColor; +#endif + return m_color; +} + +QPixmap QtColorButtonPrivate::generatePixmap() const +{ + QPixmap pix(24, 24); + + int pixSize = 20; + QBrush br(shownColor()); + + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, shownColor()); + br = QBrush(pm); + + QPainter p(&pix); + int corr = 1; + QRect r = pix.rect().adjusted(corr, corr, -corr, -corr); + p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); + p.fillRect(r, br); + + p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr, + r.width() / 2, r.height() / 2, + QColor(shownColor().rgb())); + p.drawRect(pix.rect().adjusted(0, 0, -1, -1)); + + return pix; +} + +/////////////// + +QtColorButton::QtColorButton(QWidget *parent) + : QToolButton(parent), d_ptr(new QtColorButtonPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->m_dragging = false; + d_ptr->m_backgroundCheckered = true; + + setAcceptDrops(true); + + connect(this, SIGNAL(clicked()), this, SLOT(slotEditColor())); + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); +} + +QtColorButton::~QtColorButton() +{ + delete d_ptr; +} + +void QtColorButton::setColor(const QColor &color) +{ + if (d_ptr->m_color == color) + return; + d_ptr->m_color = color; + update(); +} + +QColor QtColorButton::color() const +{ + return d_ptr->m_color; +} + +void QtColorButton::setBackgroundCheckered(bool checkered) +{ + if (d_ptr->m_backgroundCheckered == checkered) + return; + d_ptr->m_backgroundCheckered = checkered; + update(); +} + +bool QtColorButton::isBackgroundCheckered() const +{ + return d_ptr->m_backgroundCheckered; +} + +void QtColorButton::paintEvent(QPaintEvent *event) +{ + QToolButton::paintEvent(event); + if (!isEnabled()) + return; + + const int pixSize = 10; + QBrush br(d_ptr->shownColor()); + if (d_ptr->m_backgroundCheckered) { + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor()); + br = QBrush(pm); + } + + QPainter p(this); + const int corr = 4; + QRect r = rect().adjusted(corr, corr, -corr, -corr); + p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); + p.fillRect(r, br); + + //const int adjX = qRound(r.width() / 4.0); + //const int adjY = qRound(r.height() / 4.0); + //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY), + // QColor(d_ptr->shownColor().rgb())); + /* + p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0), + QColor(d_ptr->shownColor().rgb())); + p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4), + QColor(d_ptr->shownColor().rgb())); + */ + /* + const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha()))); + p.setPen(frameColor0); + p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1)); + */ + + const QColor frameColor1(0, 0, 0, 26); + p.setPen(frameColor1); + p.drawRect(r.adjusted(1, 1, -2, -2)); + const QColor frameColor2(0, 0, 0, 51); + p.setPen(frameColor2); + p.drawRect(r.adjusted(0, 0, -1, -1)); +} + +void QtColorButton::mousePressEvent(QMouseEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + if (event->button() == Qt::LeftButton) + d_ptr->m_dragStart = event->pos(); +#endif + QToolButton::mousePressEvent(event); +} + +void QtColorButton::mouseMoveEvent(QMouseEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + if (event->buttons() & Qt::LeftButton && + (d_ptr->m_dragStart - event->pos()).manhattanLength() > QApplication::startDragDistance()) { + QMimeData *mime = new QMimeData; + mime->setColorData(color()); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(d_ptr->generatePixmap()); + setDown(false); + event->accept(); + drg->start(); + return; + } +#endif + QToolButton::mouseMoveEvent(event); +} + +#ifndef QT_NO_DRAGANDDROP +void QtColorButton::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mime = event->mimeData(); + if (!mime->hasColor()) + return; + + event->accept(); + d_ptr->m_dragColor = qvariant_cast(mime->colorData()); + d_ptr->m_dragging = true; + update(); +} + +void QtColorButton::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); + d_ptr->m_dragging = false; + update(); +} + +void QtColorButton::dropEvent(QDropEvent *event) +{ + event->accept(); + d_ptr->m_dragging = false; + if (d_ptr->m_dragColor == color()) + return; + setColor(d_ptr->m_dragColor); + emit colorChanged(color()); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qtcolorbutton.cpp" diff --git a/kcachegrind/qcachegrind/qtcolorbutton.h b/kcachegrind/qcachegrind/qtcolorbutton.h new file mode 100644 index 00000000..c89fe595 --- /dev/null +++ b/kcachegrind/qcachegrind/qtcolorbutton.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copied from Qt 4.6.0 sources, directory tools/shared/qtgradienteditor +** with small modifications to allow compilation with Qt 4.4 +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +****************************************************************************/ + +#ifndef QTCOLORBUTTON_H +#define QTCOLORBUTTON_H + +#include + +QT_BEGIN_NAMESPACE + +class QtColorButton : public QToolButton +{ + Q_OBJECT + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) +public: + QtColorButton(QWidget *parent = 0); + ~QtColorButton(); + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + QColor color() const; + +public slots: + + void setColor(const QColor &color); + +signals: + void colorChanged(const QColor &color); +protected: + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); +#endif +private: + class QtColorButtonPrivate* d_ptr; + Q_DECLARE_PRIVATE(QtColorButton) + Q_DISABLE_COPY(QtColorButton) + Q_PRIVATE_SLOT(d_func(), void slotEditColor()) +}; + +QT_END_NAMESPACE + +#endif diff --git a/kcachegrind/qcachegrind/sourcesettings.cpp b/kcachegrind/qcachegrind/sourcesettings.cpp new file mode 100644 index 00000000..540ca17a --- /dev/null +++ b/kcachegrind/qcachegrind/sourcesettings.cpp @@ -0,0 +1,212 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Source annotation directory settings config page + */ + +#include +#include + +#include "tracedata.h" +#include "sourcesettings.h" +#include "globalconfig.h" + +// +// SourceSettings +// + +SourceSettings::SourceSettings(TraceData* data, QWidget* parent) + : ConfigPage(parent, + QObject::tr("Source Annotation"), + QObject::tr("Directory Settings for Source Annotation")) +{ + ui.setupUi(this); + + ui.dirList->setRootIsDecorated(false); + + GlobalConfig* c = GlobalConfig::config(); + _always = tr("(always)"); + + QTreeWidgetItem* i; + QStringList::const_iterator sit = c->generalSourceDirs().constBegin(); + for(; sit != c->generalSourceDirs().constEnd(); ++sit ) { + QString d = (*sit); + if (d.isEmpty()) d = "/"; + i = new QTreeWidgetItem(); + i->setText(0, _always); + i->setText(1, d); + ui.dirList->addTopLevelItem(i); + } + + QStringList objItems(_always); + if (data) { + TraceObjectMap::Iterator oit; + for ( oit = data->objectMap().begin(); + oit != data->objectMap().end(); ++oit ) { + QString n = (*oit).name(); + if (n.isEmpty()) continue; + objItems << n; + + const QStringList& dirs = c->objectSourceDirs(n); + sit = dirs.constBegin(); + for(; sit != dirs.constEnd(); ++sit ) { + QString d = (*sit); + if (d.isEmpty()) d = "/"; + i = new QTreeWidgetItem(); + i->setText(0, n); + i->setText(1, d); + ui.dirList->addTopLevelItem(i); + } + } + } + + ui.objectBox->addItems(objItems); + ui.objectBox->setCurrentIndex(0); + + connect(ui.addDirButton, SIGNAL(clicked()), + this, SLOT(addClicked())); + connect(ui.deleteDirButton, SIGNAL(clicked()), + this, SLOT(deleteClicked())); + connect(ui.browseDirButton, SIGNAL(clicked()), + this, SLOT(browseClicked())); + connect(ui.dirList, + SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, + SLOT(dirListItemChanged(QTreeWidgetItem*,QTreeWidgetItem*))); + connect(ui.objectBox, + SIGNAL(currentIndexChanged(QString)), + this, SLOT(objectChanged(QString))); + connect(ui.dirEdit, SIGNAL(textChanged(QString)), + this, SLOT(dirEditChanged(QString))); + + _current = 0; + update(); +} + +void SourceSettings::activate(QString s) +{ + int idx = s.toInt(); + if ((idx==0) || (idx>ui.dirList->topLevelItemCount())) return; + + ui.dirList->setCurrentItem(ui.dirList->topLevelItem(idx-1)); + ui.dirEdit->setFocus(); +} + +void SourceSettings::update() +{ + if (!_current) { + ui.deleteDirButton->setEnabled(false); + ui.objectBox->setEnabled(false); + ui.dirEdit->setEnabled(false); + ui.browseDirButton->setEnabled(false); + return; + } + ui.deleteDirButton->setEnabled(true); + ui.objectBox->setEnabled(true); + ui.objectBox->setCurrentIndex(ui.objectBox->findText(_current->text(0))); + ui.dirEdit->setEnabled(true); + ui.dirEdit->setText(_current->text(1)); + ui.browseDirButton->setEnabled(true); +} + +void SourceSettings::addClicked() +{ + QTreeWidgetItem* i = new QTreeWidgetItem(); + i->setText(0, ui.objectBox->currentText()); + i->setText(1, tr("")); + ui.dirList->addTopLevelItem(i); + ui.dirList->setCurrentItem(i); +} + +void SourceSettings::deleteClicked() +{ + if (!_current) return; + + QTreeWidgetItem* i = _current; + // the deletion removes the item + delete _current; + // deletion can trigger a call to dirListItemChanged() ! + if (_current == i) { + _current = 0; + update(); + } +} + +void SourceSettings::browseClicked() +{ + QString d; + d = QFileDialog::getExistingDirectory(this, + tr("Choose Source Directory")); + if (!d.isEmpty()) + ui.dirEdit->setText(d); +} + +void SourceSettings::dirListItemChanged(QTreeWidgetItem* current, + QTreeWidgetItem*) +{ + _current = current; + update(); +} + +void SourceSettings::objectChanged(QString obj) +{ + if (!_current) return; + + _current->setText(0, obj); +} + +void SourceSettings::dirEditChanged(QString dir) +{ + if (!_current) return; + + _current->setText(1, dir); +} + +bool SourceSettings::check(QString& errorMsg, QString& errorItem) +{ + for(int idx=0; idx< ui.dirList->topLevelItemCount(); idx++) { + QTreeWidgetItem* item = ui.dirList->topLevelItem(idx); + QString dir = item->text(1); + if (QDir(dir).exists()) continue; + errorMsg = tr("Directory does not exist"); + errorItem = QString("%1").arg(idx+1); + return false; + } + return true; +} + +void SourceSettings::accept() +{ + GlobalConfig* c = GlobalConfig::config(); + + QHash dirs; + for(int idx=0; idx< ui.dirList->topLevelItemCount(); idx++) { + QTreeWidgetItem* item = ui.dirList->topLevelItem(idx); + dirs[item->text(0)] << item->text(1); + } + QHash::const_iterator oit = dirs.constBegin(); + for(;oit != dirs.constEnd(); ++oit) { + if (oit.key() == _always) + c->setGeneralSourceDirs(oit.value()); + else + c->setObjectSourceDirs(oit.key(), oit.value()); + } +} + +#include "moc_sourcesettings.cpp" diff --git a/kcachegrind/qcachegrind/sourcesettings.h b/kcachegrind/qcachegrind/sourcesettings.h new file mode 100644 index 00000000..9dacc2f1 --- /dev/null +++ b/kcachegrind/qcachegrind/sourcesettings.h @@ -0,0 +1,61 @@ +/* This file is part of KCachegrind. + Copyright (C) 2009 Josef Weidendorfer + + 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. +*/ + +/* + * Source annotation directory settings config page + */ + +#ifndef SOURCESETTINGS_H +#define SOURCESETTINGS_H + +#include "configpage.h" +#include "ui_sourcesettings.h" + +class TraceData; +class QTreeWidgetItem; + +class SourceSettings: public ConfigPage +{ + Q_OBJECT + +public: + SourceSettings(TraceData* data, QWidget* parent); + virtual ~SourceSettings() {} + + bool check(QString&, QString&); + void accept(); + void activate(QString s); + +public slots: + void addClicked(); + void deleteClicked(); + void browseClicked(); + void dirListItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); + void objectChanged(QString); + void dirEditChanged(QString); + +private: + void update(); + + Ui::SourceSettings ui; + QTreeWidgetItem* _current; + QString _always; +}; + + +#endif // SOURCESETTINGS_H diff --git a/kcachegrind/qcachegrind/sourcesettings.ui b/kcachegrind/qcachegrind/sourcesettings.ui new file mode 100644 index 00000000..e76f052d --- /dev/null +++ b/kcachegrind/qcachegrind/sourcesettings.ui @@ -0,0 +1,102 @@ + + + SourceSettings + + + + 0 + 0 + 400 + 300 + + + + + 0 + + + 0 + + + 0 + + + + + + ELF Object + + + + + Base Directory + + + + + + + + + + Add + + + + + + + Delete + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 16 + 49 + + + + + + + + + + ELF Object: + + + + + + + + + + Base Directory: + + + + + + + + + + Browse... + + + + + + + + diff --git a/kcachegrind/qcg.pro b/kcachegrind/qcg.pro new file mode 100644 index 00000000..da4e83d5 --- /dev/null +++ b/kcachegrind/qcg.pro @@ -0,0 +1,9 @@ +# Build file for QCachegrind using qmake +# This does not require KDE in any way (neither at build nor run time) + +lessThan(QT_VERSION, 4.4) { + error("QCachegrind requires Qt 4.4 or greater") +} + +TEMPLATE = subdirs +SUBDIRS = cgview qcachegrind diff --git a/kcachegrind/version.h.cmake b/kcachegrind/version.h.cmake new file mode 100644 index 00000000..c033b574 --- /dev/null +++ b/kcachegrind/version.h.cmake @@ -0,0 +1 @@ +#define KCACHEGRIND_VERSION "${KCACHEGRIND_VERSION}"