├── .github └── workflows │ └── ccpp.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── allocationTracker ├── .gitignore ├── Makefile ├── README.md └── ldlib.c ├── install-dependencies-profiler.sh ├── install-dependencies-viewer.sh ├── perfMemPlus ├── perfSqliteExport ├── README.md └── exportToSqlite.py ├── prepareDatabase ├── .gitignore ├── README.md ├── address2Line.cpp ├── address2Line.h ├── counterattributes.cpp ├── counterattributes.h ├── main.cpp └── prepareDatabase.pro ├── setPerfEventPermissions.sh └── viewer ├── .gitignore ├── README.md ├── abstracttimelinewidget.cpp ├── abstracttimelinewidget.h ├── allocationcallpathresolver.cpp ├── allocationcallpathresolver.h ├── analysismain.cpp ├── analysismain.h ├── analysismain.ui ├── analysismain.ui.orig ├── autoanalysis.cpp ├── autoanalysis.h ├── graphwindow.cpp ├── graphwindow.h ├── graphwindow.ui ├── guiutils.cpp ├── guiutils.h ├── initdb.h ├── main.cpp ├── memorycoherencywindow.cpp ├── memorycoherencywindow.h ├── memorycoherencywindow.ui ├── memorylevelwindow.cpp ├── memorylevelwindow.h ├── memorylevelwindow.ui ├── objectaccessedbyfunctionwindow.cpp ├── objectaccessedbyfunctionwindow.h ├── objectaccessedbyfunctionwindow.ui ├── pdfwriter.cpp ├── pdfwriter.h ├── percentdelegate.cpp ├── percentdelegate.h ├── runHeadless.sh ├── sqlutils.cpp ├── sqlutils.h ├── timelineaxiswidget.cpp ├── timelineaxiswidget.h ├── timelinewidget.cpp ├── timelinewidget.h ├── timelinewindow.cpp ├── timelinewindow.h ├── timelinewindow.ui ├── treeitem.cpp ├── treeitem.h ├── treemodel.cpp ├── treemodel.h └── viewer.pro /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: run build scripts 12 | run: | 13 | sudo cp /etc/apt/sources.list /etc/apt/sources.list~ 14 | sudo sed -Ei 's/^# deb-src /deb-src /' /etc/apt/sources.list 15 | sudo apt-get update 16 | sudo ./install-dependencies-viewer.sh 17 | sudo ./install-dependencies-profiler.sh 18 | make 19 | tar -cvf perfMemPlus.tar viewer/viewer allocationTracker/ldlib.so prepareDatabase/prepareDatabase perfSqliteExport/exportToSqlite.py perfMemPlus perf setPerfEventPermissions.sh 20 | - name: Upload a Build Artifact 21 | uses: actions/upload-artifact@v2 22 | with: 23 | # Artifact name 24 | name: perfMemPlus 25 | # A file, directory or wildcard pattern that describes what to upload 26 | path: perfMemPlus.tar 27 | 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build-viewer-* 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all viewer profiler allocationTracker prepareDatabase clean 2 | 3 | all: profiler viewer 4 | profiler : allocationTracker prepareDatabase perf 5 | 6 | viewer: 7 | cd viewer && qmake viewer.pro 8 | cd viewer && $(MAKE) 9 | 10 | allocationTracker: 11 | cd allocationTracker && $(MAKE) 12 | 13 | prepareDatabase: 14 | cd prepareDatabase && qmake prepareDatabase.pro 15 | cd prepareDatabase && $(MAKE) 16 | 17 | perf: 18 | apt-get source linux 19 | cd linux-*/tools/perf && $(MAKE) 20 | cp linux-*/tools/perf/perf $(CURDIR) && rm -r -f linux-*/ && rm -f linux_* 21 | 22 | clean: 23 | rm -f perf 24 | cd viewer && $(MAKE) clean 25 | cd allocationTracker && $(MAKE) clean 26 | cd prepareDatabase && $(MAKE) clean -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Perf Mem Plus 2 | =============== 3 | Tool for memory performance analysis based on Linux perf. 4 | A paper about it was published at ISC 2019: Helm, C and Taura, K "PerfMemPlus: A Tool for Automatic Discovery of Memory Performance Problems." 5 | 6 | 7 | Installation 8 | ===== 9 | Required dependencies on Ubuntu can be installed with install-dependencies-viewer.sh and install-dependencies-profiler.sh. 10 | Those script are designed for Ubuntu 18.04 and may require changes on other systems. 11 | 12 | To build the tool run make in the root directory of this project. By default both the profier and viewer are built. 13 | Use "make profiler" to build only the profiler. Use "make viewer" to build only the viewer. 14 | 15 | 16 | 17 | Usage 18 | ===== 19 | perfMemPlus 20 | 21 | * -c \ (optional, default = 2000) 22 | A higher value means less samples taken and less overhead. 23 | It is recommended to start with the default and then change to a higher value 24 | in case the overhead in terms of runtime or resulting file size is too large. 25 | If there are not enought samples for a meaningful analysis choose a lower value. 26 | 27 | 28 | * -o \< output file\> (optional, default = ./perf.db) 29 | Existing files will be overwritten without notice. 30 | 31 | * -a \< min allocation size to record \> (optional, default = 0) 32 | This setting can help to reduce the overhead in case the applicaiton under test 33 | makes many small memory allocaitons that are not to be considered for performance evaluation. 34 | It can result in longer runtime of the application and a long time to prepare the database after the application itself is finished. 35 | Check the size of the /tmp/*.allocationData files when deciding wether to set this parameter 36 | 37 | 38 | * Application under test with parameters 39 | 40 | -------------------------------------------------------------------------------- /allocationTracker/.gitignore: -------------------------------------------------------------------------------- 1 | ldlib.o 2 | ldlib.so 3 | -------------------------------------------------------------------------------- /allocationTracker/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-Wall -g -O3 2 | LDFLAGS=-lnuma 3 | 4 | .PHONY: all clean 5 | all: ldlib.so 6 | 7 | ldlib.so: ldlib.c 8 | g++ -fPIC ${CFLAGS} -c ldlib.c 9 | g++ -shared -Wl,-soname,libpmalloc.so -o ldlib.so ldlib.o -ldl -lpthread 10 | 11 | clean: 12 | rm -f *.o *.so 13 | 14 | -------------------------------------------------------------------------------- /allocationTracker/README.md: -------------------------------------------------------------------------------- 1 | Allocation tracker 2 | =============== 3 | 4 | Collects information about dynamically allocated data. 5 | 6 | Based on the memprof library https://github.com/Memprof/library 7 | 8 | Usage: 9 | 10 | LD_PRELOAD=./ldlib.so 11 | 12 | Notes 13 | ===== 14 | * The library will write text files in /tmp at the end of the run. Make sure that you have the rights to write in /tmp. 15 | 16 | * By default, the library uses backtrace() to collect callchains. If your application is configured to use frame pointers (i.e., compiled with -fno-omit-frame-pointers), then you can enable frame pointers; it will speed up the data collection (see ldlib.c). 17 | -------------------------------------------------------------------------------- /allocationTracker/ldlib.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | #define __USE_GNU 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define ARR_SIZE 550000 /* Max number of malloc per core */ 21 | #define MAX_TID 512 /* Max number of tids to profile */ 22 | 23 | #define USE_FRAME_POINTER 0 /* Use Frame Pointers to compute the stack trace (faster) */ 24 | #define CALLCHAIN_SIZE 9 /* stack trace length */ 25 | #define RESOLVE_SYMBS 1 /* Resolve symbols at the end of the execution; quite costly */ 26 | /* If this takes too much time, you can deactivate it or stop memprof before doing it */ 27 | 28 | #define NB_ALLOC_TO_IGNORE 0 /* Ignore the first X allocations. */ 29 | 30 | #if NB_ALLOC_TO_IGNORE > 0 31 | static int __thread nb_allocs = 0; 32 | #endif 33 | 34 | extern char *program_invocation_short_name; 35 | 36 | static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 37 | static int __thread pid; 38 | static int __thread tid; 39 | static int __thread tid_index; 40 | static int tids[MAX_TID]; 41 | static size_t AllocationMinSize = 0; 42 | 43 | struct log { 44 | uint64_t rdt; 45 | void *addr; 46 | size_t size; 47 | long entry_type; // 0 free 1 malloc >=100 mmap 48 | int cpu; 49 | unsigned int pid; 50 | 51 | size_t callchain_size; 52 | void* callchain_strings[CALLCHAIN_SIZE]; 53 | }; 54 | struct log *log_arr[MAX_TID]; 55 | static size_t log_index[MAX_TID]; 56 | 57 | void __attribute__((constructor)) m_init(void); 58 | 59 | #ifdef __x86_64__ 60 | #define rdtscll(val) { \ 61 | unsigned int __a,__d; \ 62 | asm volatile("rdtsc" : "=a" (__a), "=d" (__d)); \ 63 | (val) = ((unsigned long)__a) | (((unsigned long)__d)<<32); \ 64 | } 65 | 66 | #else 67 | #define rdtscll(val) __asm__ __volatile__("rdtsc" : "=A" (val)) 68 | #endif 69 | 70 | static unsigned long long get_nsecs(void) { 71 | struct timespec ts; 72 | clock_gettime(CLOCK_MONOTONIC, &ts); 73 | return ts.tv_sec * 1000000000ULL + ts.tv_nsec; 74 | } 75 | 76 | static void *(*libc_malloc)(size_t); 77 | static void (*libc_free)(void *); 78 | static void *(*libc_calloc)(size_t, size_t); 79 | static void *(*libc_realloc)(void *, size_t); 80 | static void *(*libc_mmap)(void *, size_t, int, int, int, off_t); 81 | static void *(*libc_mmap64)(void *, size_t, int, int, int, off_t); 82 | static int (*libc_munmap)(void *, size_t); 83 | static void *(*libc_memalign)(size_t, size_t); 84 | static int (*libc_posix_memalign)(void **, size_t, size_t); 85 | static void *(*libc_numa_alloc_onnode)(size_t, size_t); 86 | static void *(*libc_numa_alloc_interleaved)(size_t); 87 | 88 | static __attribute__((unused)) int in_first_dlsym = 0; 89 | static char empty_data[32]; 90 | 91 | FILE* open_file(int tid) { 92 | char buff[125]; 93 | sprintf(buff, "/tmp/%d.allocationData", tid); 94 | 95 | FILE *dump = fopen(buff, "a+"); 96 | if(!dump) { 97 | fprintf(stderr, "open %s failed\n", buff); 98 | exit(-1); 99 | } 100 | return dump; 101 | } 102 | 103 | int _getpid() { 104 | if(pid) 105 | return pid; 106 | return pid = getpid(); 107 | } 108 | 109 | struct log *get_log() { 110 | if(strcmp(program_invocation_short_name,"perf") == 0 || 111 | strcmp(program_invocation_short_name,"basename") == 0 || 112 | strcmp(program_invocation_short_name,"dirname") == 0 || 113 | strcmp(program_invocation_short_name,"cat") == 0 || 114 | strcmp(program_invocation_short_name,"mkdir") == 0 || 115 | strcmp(program_invocation_short_name,"rm") == 0 || 116 | strcmp(program_invocation_short_name,"date") == 0 || 117 | strcmp(program_invocation_short_name,"time") == 0 || 118 | strcmp(program_invocation_short_name,"tee") == 0 || 119 | strcmp(program_invocation_short_name,"cp") == 0 || 120 | strcmp(program_invocation_short_name,"mv") == 0 || 121 | strcmp(program_invocation_short_name,"bash") == 0) 122 | { 123 | // Exclude tracking of some system tools that often run with apps 124 | // Most likely there is no interest in profiling 125 | // Reduces overhead because allocations of these apps are not logged 126 | return NULL; 127 | } 128 | 129 | #if NB_ALLOC_TO_IGNORE > 0 130 | nb_allocs++; 131 | if(nb_allocs < NB_ALLOC_TO_IGNORE) 132 | return NULL; 133 | #endif 134 | 135 | int i; 136 | if(!tid) { 137 | tid = (int) syscall(186); //64b only 138 | pthread_mutex_lock(&lock); 139 | for(i = 1; i < MAX_TID; i++) { 140 | if(tids[i] == 0) { 141 | tids[i] = tid; 142 | tid_index = i; 143 | break; 144 | } 145 | } 146 | if(tid_index == 0) { 147 | fprintf(stderr, "Too many threads!\n"); 148 | exit(-1); 149 | } 150 | pthread_mutex_unlock(&lock); 151 | } 152 | if(!log_arr[tid_index]) 153 | log_arr[tid_index] = (struct log*) libc_malloc(sizeof(*log_arr[tid_index]) * ARR_SIZE); 154 | if(log_index[tid_index] >= ARR_SIZE) 155 | return NULL; 156 | 157 | struct log *l = &log_arr[tid_index][log_index[tid_index]]; 158 | //printf("%d %d \n", tid_index, (int)log_index[tid_index]); 159 | log_index[tid_index]++; 160 | return l; 161 | } 162 | 163 | 164 | static int __thread _in_trace = 0; 165 | #define get_bp(bp) asm("movq %%rbp, %0" : "=r" (bp) :) 166 | 167 | struct stack_frame { 168 | struct stack_frame *next_frame; 169 | unsigned long return_address; 170 | }; 171 | 172 | int get_trace(size_t* size, void** strings) { 173 | if(_in_trace) 174 | return 1; 175 | _in_trace = 1; 176 | 177 | #if USE_FRAME_POINTER 178 | int i; 179 | struct stack_frame *frame; 180 | get_bp(frame); 181 | for(i = 0; i < CALLCHAIN_SIZE; i++) { 182 | strings[i] = (void*)frame->return_address; 183 | *size = i + 1; 184 | frame = frame->next_frame; 185 | if(!frame) 186 | break; 187 | } 188 | #else 189 | *size = backtrace(strings, CALLCHAIN_SIZE); 190 | #endif 191 | 192 | _in_trace = 0; 193 | return 0; 194 | } 195 | 196 | 197 | extern "C" void *malloc(size_t sz) { 198 | if(!libc_malloc) 199 | m_init(); 200 | void *addr = libc_malloc(sz); 201 | if(sz < AllocationMinSize) 202 | return addr; 203 | if(!_in_trace) { 204 | struct log *log_arr = get_log(); 205 | if(log_arr) { 206 | log_arr->rdt = get_nsecs(); 207 | log_arr->addr = addr; 208 | log_arr->size = sz; 209 | log_arr->entry_type = 1; 210 | log_arr->cpu = sched_getcpu(); 211 | log_arr->pid = _getpid(); 212 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 213 | } 214 | } 215 | return addr; 216 | } 217 | 218 | extern "C" void *memalign(size_t align, size_t sz) { 219 | void *addr = libc_memalign(align, sz); 220 | if(sz < AllocationMinSize) 221 | return addr; 222 | if(!_in_trace) { 223 | struct log *log_arr = get_log(); 224 | if(log_arr) { 225 | log_arr->rdt = get_nsecs(); 226 | log_arr->addr = addr; 227 | log_arr->size = sz; 228 | log_arr->entry_type = 1; 229 | log_arr->cpu = sched_getcpu(); 230 | log_arr->pid = _getpid(); 231 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 232 | } 233 | } 234 | return addr; 235 | } 236 | 237 | extern "C" int posix_memalign(void **ptr, size_t align, size_t sz) { 238 | int ret = libc_posix_memalign(ptr, align, sz); 239 | if(sz < AllocationMinSize) 240 | return ret; 241 | if(!_in_trace) { 242 | struct log *log_arr = get_log(); 243 | if(log_arr) { 244 | log_arr->rdt = get_nsecs(); 245 | log_arr->addr = *ptr; 246 | log_arr->size = sz; 247 | log_arr->entry_type = 1; 248 | log_arr->cpu = sched_getcpu(); 249 | log_arr->pid = _getpid(); 250 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 251 | } 252 | } 253 | return ret; 254 | } 255 | 256 | extern "C" void free(void *p) { 257 | if(p == 0) { 258 | libc_free(p); 259 | return; 260 | } 261 | struct log *log_arr = get_log(); 262 | if(log_arr) { 263 | log_arr->rdt = get_nsecs(); 264 | log_arr->addr = p; 265 | log_arr->size = 0; 266 | log_arr->entry_type = 0; 267 | log_arr->cpu = sched_getcpu(); 268 | log_arr->pid = _getpid(); 269 | } 270 | //if(!pthread_mutex_trylock(&lock)) { 271 | //fprintf(dump, "%lu pid %d cpu %d size %d addr %lx type %d\n", log_arr.rdt, log_arr.pid, log_arr.cpu, 0, p, log_arr.entry_type); 272 | //pthread_mutex_unlock(&lock); 273 | 274 | libc_free(p); 275 | } 276 | 277 | extern "C" void *calloc(size_t nmemb, size_t size) { 278 | void *addr; 279 | if(!libc_calloc) { 280 | memset(empty_data, 0, sizeof(*empty_data)); 281 | addr = empty_data; 282 | } else { 283 | addr = libc_calloc(nmemb, size); 284 | } 285 | if(size < AllocationMinSize) 286 | return addr; 287 | if(!_in_trace && libc_calloc) { 288 | struct log *log_arr = get_log(); 289 | if(log_arr) { 290 | log_arr->rdt = get_nsecs(); 291 | log_arr->addr = addr; 292 | log_arr->size = nmemb * size; 293 | log_arr->entry_type = 1; 294 | log_arr->cpu = sched_getcpu(); 295 | log_arr->pid = _getpid(); 296 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 297 | } 298 | } 299 | 300 | return addr; 301 | } 302 | 303 | extern "C" void *realloc(void *ptr, size_t size) { 304 | void *addr = libc_realloc(ptr, size); 305 | 306 | if(size < AllocationMinSize) 307 | return addr; 308 | if(!_in_trace) { 309 | struct log *log_arr = get_log(); 310 | if(log_arr) { 311 | log_arr->rdt = get_nsecs(); 312 | log_arr->addr = addr; 313 | log_arr->size = size; 314 | log_arr->entry_type = 1; 315 | log_arr->cpu = sched_getcpu(); 316 | log_arr->pid = _getpid(); 317 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 318 | } 319 | } 320 | 321 | return addr; 322 | } 323 | 324 | void *operator new(size_t sz) throw(std::bad_alloc) { 325 | return malloc(sz); 326 | } 327 | 328 | void *operator new(size_t sz, const std::nothrow_t &) throw() { 329 | return malloc(sz); 330 | } 331 | 332 | void *operator new[](size_t sz) throw(std::bad_alloc) { 333 | return malloc(sz); 334 | } 335 | 336 | void *operator new[](size_t sz, const std::nothrow_t &) throw() { 337 | return malloc(sz); 338 | } 339 | 340 | void operator delete(void *ptr) { 341 | free(ptr); 342 | } 343 | 344 | void operator delete[](void *ptr) { 345 | free(ptr); 346 | } 347 | 348 | extern "C" void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { 349 | void *addr = libc_mmap(start, length, prot, flags, fd, offset); 350 | 351 | if(length < AllocationMinSize) 352 | return addr; 353 | if(!_in_trace) { 354 | struct log *log_arr = get_log(); 355 | if(log_arr) { 356 | log_arr->rdt = get_nsecs(); 357 | log_arr->addr = addr; 358 | log_arr->size = length; 359 | log_arr->entry_type = flags + 100; 360 | log_arr->cpu = sched_getcpu(); 361 | log_arr->pid = _getpid(); 362 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 363 | } 364 | } 365 | 366 | return addr; 367 | } 368 | 369 | extern "C" void *mmap64(void *start, size_t length, int prot, int flags, int fd, off_t offset) { 370 | void *addr = libc_mmap64(start, length, prot, flags, fd, offset); 371 | 372 | if(length < AllocationMinSize) 373 | return addr; 374 | if(!_in_trace) { 375 | struct log *log_arr = get_log(); 376 | if(log_arr) { 377 | log_arr->rdt = get_nsecs(); 378 | log_arr->addr = addr; 379 | log_arr->size = length * 4 * 1024; 380 | log_arr->entry_type = flags + 100; 381 | log_arr->cpu = sched_getcpu(); 382 | log_arr->pid = _getpid(); 383 | get_trace(&log_arr->callchain_size, log_arr->callchain_strings); 384 | } 385 | } 386 | 387 | return addr; 388 | } 389 | 390 | extern "C" int munmap(void *start, size_t length) { 391 | int addr = libc_munmap(start, length); 392 | /*struct log log_arr; 393 | rdtscll(log_arr.rdt); 394 | log_arr.addr = start; 395 | log_arr.size = length; 396 | log_arr.entry_type = 2; 397 | log_arr.cpu = sched_getcpu(); 398 | log_arr.pid = _getpid(); 399 | */ 400 | return addr; 401 | } 402 | 403 | int __thread bye_done = 0; 404 | void 405 | __attribute__((destructor)) 406 | bye(void) { 407 | if(bye_done) 408 | return; 409 | bye_done = 1; 410 | 411 | unsigned int i, j, k; 412 | for(i = 1; i < MAX_TID; i++) { 413 | if(tids[i] == 0) 414 | break; 415 | FILE *dump = open_file(tids[i]); 416 | for(j = 0; j < log_index[i]; j++) { 417 | struct log *l = &log_arr[i][j]; 418 | if(l->entry_type != 0) { 419 | fprintf(dump, "callchain\n"); 420 | } 421 | // k=2, we skip get_trace and the function (malloc, ...) that called it 422 | #if RESOLVE_SYMBS 423 | _in_trace = 1; 424 | char **strings = backtrace_symbols (l->callchain_strings, l->callchain_size); 425 | for (k = 2; k < l->callchain_size; k++) { 426 | fprintf (dump, "%*.*s%s\n", (int)k-2,(int)k-2,"",strings[k]); 427 | } 428 | libc_free(strings); 429 | #else 430 | for (k = 2; k < l->callchain_size; k++) { 431 | fprintf (dump, "%*.*s [%p]\n", (int)k-2,(int)k-2,"",l->callchain_strings[k]); 432 | } 433 | #endif 434 | fprintf(dump, "%lu pid %d cpu %d size %llu addr %llx type %d\n", l->rdt, l->pid, l->cpu, (unsigned long long)l->size, (unsigned long long)l->addr, (int)l->entry_type); 435 | } 436 | fclose(dump); 437 | } 438 | } 439 | 440 | void 441 | __attribute__((constructor)) 442 | m_init(void) { 443 | libc_calloc = (void * ( *)(size_t, size_t)) dlsym(RTLD_NEXT, "calloc"); 444 | libc_malloc = (void * ( *)(size_t)) dlsym(RTLD_NEXT, "malloc"); 445 | libc_realloc = (void * ( *)(void *, size_t)) dlsym(RTLD_NEXT, "realloc"); 446 | libc_free = (void ( *)(void *)) dlsym(RTLD_NEXT, "free"); 447 | libc_mmap = (void * ( *)(void *, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); 448 | libc_munmap = (int ( *)(void *, size_t)) dlsym(RTLD_NEXT, "munmap"); 449 | libc_mmap64 = (void * ( *)(void *, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64"); 450 | libc_memalign = (void * ( *)(size_t, size_t)) dlsym(RTLD_NEXT, "memalign"); 451 | libc_posix_memalign = (int ( *)(void **, size_t, size_t)) dlsym(RTLD_NEXT, "posix_memalign"); 452 | const char* s = getenv("ALLOCATION_MIN_SIZE"); 453 | if (s != nullptr) 454 | { 455 | AllocationMinSize = atol(s); 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /install-dependencies-profiler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #install dependencies for qt based applications 4 | #install dependencies for building perf with required modules 5 | apt-get install g++ make qtbase5-dev qt5-default flex bison libelf-dev libiberty-dev libnuma-dev libunwind-dev elfutils libdw-dev python-dev binutils-dev libbfd-dev linux-tools-$(uname -r) 6 | 7 | -------------------------------------------------------------------------------- /install-dependencies-viewer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #install dependencies for qt based applications 4 | apt-get install g++ make qtbase5-dev qt5-default libqt5charts5-dev 5 | # libqt5charts5-dev is not available as a package on Ubuntu 16.04 6 | # qt needs to be manually installed in this case 7 | -------------------------------------------------------------------------------- /perfMemPlus: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #functions 4 | usage() 5 | { 6 | echo "usage perfMemPlus -o output -c samplerate -a allocationMinSize -h help -- application" 7 | } 8 | 9 | #default argument values 10 | sampleRate=2000 11 | filename=./perf.db 12 | perf=`dirname $0`/perf 13 | allocationMinSize=0 14 | l1MissLatency=0 15 | dramBandwidth=0 16 | sampleWrite=0 17 | 18 | #argument parsing 19 | while [ "$1" != "" ]; do 20 | case $1 in 21 | -o | --output ) shift 22 | filename=$1 23 | ;; 24 | -c | --samplerate ) shift 25 | sampleRate=$1 26 | ;; 27 | -a | --allocationMinSize) shift 28 | allocationMinSize=$1 29 | ;; 30 | --dramBandwidth) 31 | dramBandwidth=1 32 | ;; 33 | --l1MissLatency) 34 | l1MissLatency=1 35 | ;; 36 | -h | --help ) usage 37 | exit 38 | ;; 39 | * ) break 40 | esac 41 | shift 42 | done 43 | 44 | cycleSampleRate=$(($sampleRate*10000)) 45 | l1MissSampleRate=$(($sampleRate*500)) 46 | offcoreSampleRate=$(($sampleRate*2)) 47 | writeSampleRate=$(($sampleRate*100)) 48 | 49 | 50 | rm /tmp/*.allocationData 51 | export ALLOCATION_MIN_SIZE=$allocationMinSize 52 | 53 | eventString="cpu/mem-loads,ldlat=1,period=$sampleRate/P" 54 | if [ $dramBandwidth = 1 ] && [ $l1MissLatency = 1 ] 55 | then 56 | echo "Measurement of dramBandwidth and l1MissLatency at the same time is not suppored" 57 | elif [ $dramBandwidth = 0 ] && [ $l1MissLatency = 0 ] 58 | then 59 | eventString+=",cpu/mem-stores,period=$writeSampleRate/P,cpu/cpu-cycles,period=$cycleSampleRate/P,cpu/instructions,period=$cycleSampleRate/P" 60 | elif [ $dramBandwidth = 1 ] 61 | then 62 | eventString+=",cpu/offcore_response.all_reads.llc_miss.local_dram,period=$offcoreSampleRate,call-graph=no/D,cpu/offcore_response.all_reads.llc_miss.remote_dram,period=$offcoreSampleRate,call-graph=no/D" 63 | elif [ $l1MissLatency = 1 ] 64 | then 65 | eventString+=",cpu/l1d_pend_miss.pending,period=$l1MissSampleRate,call-graph=no/D,cpu/mem_load_uops_retired.l1_miss,period=$sampleRate,call-graph=no/D,cpu/mem_load_uops_retired.hit_lfb,period=$sampleRate,call-graph=no/D" 66 | fi 67 | 68 | #$perf record --sample-cpu -d -W -e "$eventString" -g -k CLOCK_MONOTONIC -o /tmp/perf.data -- "$@" 69 | 70 | LD_PRELOAD=`dirname $0`/allocationTracker/ldlib.so $perf record --sample-cpu -d -W -e "$eventString" -g -k CLOCK_MONOTONIC -o /tmp/perf.data -- "$@" 71 | allocSize=$(du -ch /tmp/*.allocationData | tail -1) 72 | echo "Captured $allocSize of allocation data" 73 | $perf script -i /tmp/perf.data -s `dirname $0`/perfSqliteExport/exportToSqlite.py /tmp/perf.db -c 74 | cmdline="$@" 75 | addArg="" 76 | if [ $dramBandwidth = 1 ] 77 | then 78 | addArg="--dramBandwidth" 79 | fi 80 | if [ $l1MissLatency = 1 ] 81 | then 82 | addArg="--l1MissLatency" 83 | fi 84 | `dirname $0`/prepareDatabase/prepareDatabase /tmp/perf.db -c $sampleRate -a $allocationMinSize -l "$cmdline" "$addArg" 85 | cp /tmp/perf.db $filename 86 | rm /tmp/perf.db 87 | 88 | -------------------------------------------------------------------------------- /perfSqliteExport/README.md: -------------------------------------------------------------------------------- 1 | Python script to export recorded perf sampling data to a sqlite database. 2 | 3 | It is using the perf scripting interface. 4 | Tested with perf/kernel version 4.8. Older version are not compatible with the export script. 5 | Requires perf with enabled python scripting support. 6 | On a standard ubuntu the perf that comes as part of the linux-tools package does 7 | not have python scripting support. 8 | You will have to build and install perf by doing the following: 9 | 10 | 1. Install python on your system 11 | 2. Download and build perf: 12 | 13 | sudo apt-get install linux-source 14 | 15 | tar jxf /usr/src/linux-source-.tar.bz2 16 | 17 | cd linux-source-/tools/perf/ 18 | 19 | make 20 | 21 | make install 22 | 23 | The make command will show if python support will be enabled in this build. 24 | 25 | Perf will be installed at ~/bin/perf by default. 26 | 27 | Use the export: 28 | perf script -s export-to-sqlite.py databaseName.db 29 | 30 | 31 | -------------------------------------------------------------------------------- /prepareDatabase/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | prepareDatabase 74 | -------------------------------------------------------------------------------- /prepareDatabase/README.md: -------------------------------------------------------------------------------- 1 | Prepare Database 2 | =============== 3 | 4 | Reads allocation tracker files and inserts the data to the exisitng perf database and creates useful views. 5 | 6 | 7 | Installation 8 | ===== 9 | It requires QT 5.9 and qmake. 10 | 11 | For compilation do the following steps: 12 | 13 | qmake prepareDatabase.pro 14 | 15 | make qmake\_all 16 | 17 | make 18 | 19 | Usage 20 | ===== 21 | prepareDatabase \ 22 | 23 | Assumes that allocation tracker files are stored at /tmp 24 | 25 | -------------------------------------------------------------------------------- /prepareDatabase/address2Line.cpp: -------------------------------------------------------------------------------- 1 | #include "address2Line.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | QString Address2Line::addr2line(const QString& exe, const QString& address) 9 | { 10 | static QMap addr2lineProcesses; 11 | auto it = addr2lineProcesses.find(exe); 12 | if(it != addr2lineProcesses.end()) 13 | { 14 | auto process = *it; 15 | QString addr = QLatin1String("0x") % address % QLatin1Char('\n'); 16 | process->write(addr.toLatin1()); 17 | process->waitForReadyRead(100); 18 | auto result = process->readAllStandardOutput(); 19 | return QString(result); 20 | } 21 | else 22 | { 23 | QProcess* process = new QProcess; 24 | QString file = QString("sh -c \"addr2line -p -f -C -i -e ") % exe % "\""; 25 | process->start(file); 26 | process->waitForStarted(100); 27 | addr2lineProcesses.insert(exe,process); 28 | return addr2line(exe,address); 29 | } 30 | } 31 | 32 | Address2Line::LineInfo Address2Line::getLineInfo(const QString &exe, const QString &address) 33 | { 34 | return parseLineInfo(addr2line(exe,address)); 35 | } 36 | 37 | QString Address2Line::stringRefJoin(const QVector& stringRefList) 38 | { 39 | QString mergedString = QStringLiteral(""); 40 | if(stringRefList.empty()) 41 | { 42 | return mergedString; 43 | } 44 | for(auto ref : stringRefList) 45 | { 46 | mergedString += ref.toString() + QStringLiteral("\n"); 47 | } 48 | mergedString.chop(1); 49 | return mergedString; 50 | } 51 | 52 | Address2Line::LineInfo Address2Line::parseLineInfo(const QString &text) 53 | { 54 | LineInfo linfo; 55 | QVector addr2lineLines = text.splitRef('\n',QString::SkipEmptyParts); 56 | if(!addr2lineLines.empty()) 57 | { 58 | QVector addr2lineParts = addr2lineLines.first().split(QStringLiteral(" at "),QString::SkipEmptyParts); 59 | if(!addr2lineParts.empty() && addr2lineParts.first() != QStringLiteral("") && addr2lineParts.first() != QStringLiteral("?? ??:0")) 60 | { 61 | //addr2line can get useful data 62 | linfo.function = addr2lineParts.first().toString(); 63 | addr2lineParts.removeFirst(); 64 | 65 | auto fileAndLine = addr2lineParts.first().split(QLatin1String(":")); 66 | linfo.file = fileAndLine.first().toString(); 67 | linfo.line = fileAndLine.at(1).toInt(); 68 | addr2lineLines.remove(0); 69 | linfo.inlinedBy = stringRefJoin(addr2lineLines); 70 | return linfo; 71 | } 72 | } 73 | linfo.file = QLatin1String(""); 74 | linfo.function = QLatin1String(""); 75 | return linfo; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /prepareDatabase/address2Line.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDRESS2LINE_H 2 | #define ADDRESS2LINE_H 3 | 4 | #include 5 | 6 | class Address2Line 7 | { 8 | public: 9 | struct LineInfo 10 | { 11 | QString function; 12 | QString file; 13 | int line = -1; 14 | QString inlinedBy; 15 | }; 16 | static LineInfo getLineInfo(const QString& exe, const QString& address); 17 | 18 | private: 19 | static LineInfo parseLineInfo(const QString &text); 20 | static QString addr2line(const QString &exe, const QString &address); 21 | static QString stringRefJoin(const QVector &stringRefList); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /prepareDatabase/counterattributes.cpp: -------------------------------------------------------------------------------- 1 | #include "counterattributes.h" 2 | #include "iostream" 3 | #include 4 | 5 | CounterAttributes::CounterAttributes(QSqlDatabase &db) 6 | { 7 | this->db = db; 8 | } 9 | 10 | void CounterAttributes::setNodeMapping(const QHash > &mapping) 11 | { 12 | this->nodeToCpus = mapping; 13 | } 14 | 15 | QStringList CounterAttributes::createFlatEventList(const EventList& eventGroupList) const 16 | { 17 | QStringList l; 18 | { 19 | for(auto eventPair : eventGroupList) 20 | { 21 | l.append(eventPair); 22 | } 23 | } 24 | return l; 25 | } 26 | 27 | CounterAttributes::EventAttribute CounterAttributes::getEventAttributes(const QString &event) const 28 | { 29 | EventAttribute eAttr; 30 | if(event.startsWith("offcore",Qt::CaseInsensitive)) 31 | { 32 | eAttr.offcore = true; 33 | } 34 | return eAttr; 35 | } 36 | 37 | CounterAttributes::EventAttribute CounterAttributes::getEventAttributes(const unsigned long long event) const 38 | { 39 | auto name = eventIdToName[event]; 40 | return getEventAttributes(name); 41 | } 42 | 43 | CounterAttributes::EventAttribute CounterAttributes::getEventAttributes(const QList& eventList ) const 44 | { 45 | auto eAttrPrev = getEventAttributes(eventList.first()); 46 | for(auto e : eventList) 47 | { 48 | auto eAttr = getEventAttributes(e); 49 | if(eAttr != eAttrPrev) 50 | { 51 | throw "Unmatching event attributes in group"; 52 | } 53 | eAttrPrev = eAttr; 54 | } 55 | return eAttrPrev; 56 | } 57 | CounterAttributes::EventAttribute CounterAttributes::getEventAttributes(const QList>& eventList ) const 58 | { 59 | auto eAttrPrev = getEventAttributes(eventList.first().first); 60 | for(auto e : eventList) 61 | { 62 | auto eAttr = getEventAttributes(e.first); 63 | if(eAttr != eAttrPrev) 64 | { 65 | throw "Unmatching event attributes in group"; 66 | } 67 | eAttrPrev = eAttr; 68 | } 69 | return eAttrPrev; 70 | } 71 | 72 | void CounterAttributes::createEventIdMap(const EventList& consideredEvents) 73 | { 74 | QSqlQuery q("select id from selected_events where name like ? "); 75 | auto l = createFlatEventList(consideredEvents); 76 | for(auto eventName : l) 77 | { 78 | 79 | q.bindValue(0,"cpu/"+eventName+"%"); 80 | if(q.exec() && q.next()) 81 | { 82 | auto id = q.value(0).toULongLong(); 83 | eventIdToName.insert(id,eventName); 84 | eventNameToId.insert(eventName,id); 85 | } 86 | } 87 | } 88 | 89 | void CounterAttributes::createTable(EventList events) 90 | { 91 | for(auto& event : events) 92 | { 93 | event.prepend("'"); 94 | event.append("'"); 95 | } 96 | events += {"l1HitRate","lfbHitRate","l2HitRate","l3HitRate","localDramHitRate"}; 97 | auto columnStr = events.join(" REAL, "); 98 | QString createStatement = "create table counterSamples ( \ 99 | id INTEGER PRIMARY KEY UNIQUE, "; 100 | createStatement += columnStr + ")"; 101 | 102 | db.exec("drop table if exists counterSamples"); 103 | if(db.lastError().isValid()) 104 | { 105 | throw(db.lastError().text()); 106 | } 107 | db.exec(createStatement); 108 | if(db.lastError().isValid()) 109 | { 110 | throw(db.lastError().text() + createStatement); 111 | } 112 | } 113 | 114 | void CounterAttributes::createMetricViews(const EventList& list) 115 | { 116 | db.exec("drop view if exists counterMetrics"); 117 | QString createStatement = "create view counterMetrics as "; 118 | bool execute = false; 119 | if(list.contains("l1d_pend_miss.pending") && list.contains("mem_load_uops_retired.l1_miss") && list.contains("mem_load_uops_retired.hit_lfb")) 120 | { 121 | createStatement+= "select id, \"l1d_pend_miss.pending\" / (\"mem_load_uops_retired.l1_miss\" + \"mem_load_uops_retired.hit_lfb\") as 'loadMissRealLatency',"; 122 | createStatement+= " ((\"l1d_pend_miss.pending\" / (\"mem_load_uops_retired.l1_miss\" + \"mem_load_uops_retired.hit_lfb\")) - l2HitRate * 12 - l3HitRate * 33) / (localDramHitRate) as 'loadMissRealDramLatency'"; 123 | createStatement+= " from counterSamples"; 124 | execute = true; 125 | } 126 | else if(list.contains("l1d_pend_miss.pending") && list.contains("mem_load_uops_retired.l1_miss") && !list.contains("mem_load_uops_retired.hit_lfb")) 127 | { 128 | createStatement+= "select id, \"l1d_pend_miss.pending\" / \"mem_load_uops_retired.l1_miss\" as 'l1MissLatency' from counterSamples"; 129 | execute = true; 130 | } 131 | else if(list.contains("offcore_response.all_reads.llc_miss.local_dram") && list.contains("offcore_response.all_reads.llc_miss.remote_dram")) 132 | { 133 | createStatement+= "select id, \"offcore_response.all_reads.llc_miss.local_dram\" * 64 / 1000 as 'localDramBandwidth', \"offcore_response.all_reads.llc_miss.remote_dram\" * 64 / 1000 as 'remoteDramBandwidth' from counterSamples"; 134 | execute = true; 135 | } 136 | if(execute) 137 | { 138 | db.exec(createStatement); 139 | if(db.lastError().isValid()) 140 | { 141 | throw(db.lastError().text()); 142 | } 143 | } 144 | } 145 | 146 | template 147 | QString CounterAttributes::listToString(const QList& list) const 148 | { 149 | QStringList stringList; 150 | std::transform(list.begin(),list.end(),std::back_inserter(stringList),[](unsigned long long i){return QString::number(i);}); 151 | auto str = stringList.join(","); 152 | return str; 153 | } 154 | 155 | void CounterAttributes::createIndexes() 156 | { 157 | db.exec("create index if not exists samples_time_cpu_threadId_evselId on samples(time,cpu,thread_id,evsel_id,period)"); 158 | db.commit(); 159 | } 160 | 161 | void CounterAttributes::writeSampleIds() 162 | { 163 | db.transaction(); 164 | QSqlQuery q("insert into counterSamples (id) \ 165 | select id from samples where evsel_id = \ 166 | (select id from selected_events where name like '%mem-loads%') ",db); 167 | q.exec(); 168 | db.commit(); 169 | } 170 | 171 | void CounterAttributes::updateCounterSamples(unsigned long long eventId, const QList& cpu, unsigned long long startTime, unsigned long long endTime, double value) 172 | { 173 | auto eventName = eventIdToName[eventId]; 174 | auto cpuStr = listToString(cpu); 175 | QSqlQuery q(db); 176 | q.prepare("update counterSamples set '" + eventName + "' = ? where \ 177 | id in (select id from samples where time > ? and time <= ? and cpu in (" + cpuStr + "))"); 178 | q.bindValue(0,value); 179 | q.bindValue(1,startTime); 180 | q.bindValue(2,endTime); 181 | q.exec(); 182 | if(q.lastError().isValid()) 183 | { 184 | throw(q.lastError().text() + q.lastQuery()); 185 | } 186 | } 187 | 188 | void CounterAttributes::updateCounterSamples(unsigned long long eventId, const unsigned long long cpu, const unsigned long long threadId, unsigned long long startTime, unsigned long long endTime, double value, QHash hitRates) 189 | { 190 | auto eventName = eventIdToName[eventId]; 191 | BufferEntry& b = buffer[eventName]; 192 | b.values << value; 193 | b.startTimes << startTime; 194 | b.endTimes << endTime; 195 | b.cpus << cpu; 196 | b.threads << threadId; 197 | b.l1HitRates << hitRates["L1"]; 198 | b.lfbHitRates << hitRates["LFB"]; 199 | b.l2HitRates << hitRates["L2"]; 200 | b.l3HitRates << hitRates["L3"]; 201 | b.localDramHitRates << hitRates["Local DRAM"]; 202 | } 203 | 204 | void CounterAttributes::writeCounterSamples() 205 | { 206 | for(auto eventName : buffer.keys() ) 207 | { 208 | QSqlQuery q(db); 209 | q.prepare("update counterSamples set '" + eventName + "' = ?, \ 210 | l1HitRate = ?, lfbHitRate = ?, l2HitRate = ?, l3HitRate = ?, localDramHitRate = ? \ 211 | where id in (select id from samples where time > ? and time <= ? and cpu = ? and thread_id = ?)"); 212 | auto& b = buffer[eventName]; 213 | q.addBindValue(b.values); 214 | q.addBindValue(b.l1HitRates); 215 | q.addBindValue(b.lfbHitRates); 216 | q.addBindValue(b.l2HitRates); 217 | q.addBindValue(b.l3HitRates); 218 | q.addBindValue(b.localDramHitRates); 219 | q.addBindValue(b.startTimes); 220 | q.addBindValue(b.endTimes); 221 | q.addBindValue(b.cpus); 222 | q.addBindValue(b.threads); 223 | if(!q.execBatch()) 224 | { 225 | throw(q.lastError().text() + q.lastQuery()); 226 | } 227 | } 228 | buffer.clear(); 229 | } 230 | 231 | unsigned long long CounterAttributes::getFirstTimestamp(const unsigned long long cpu, const unsigned long long threadId) const 232 | { 233 | static QMap,unsigned long long> cache; 234 | if(cache.contains({cpu,threadId})) 235 | { 236 | return cache.value({cpu,threadId}); 237 | } 238 | else 239 | { 240 | QSqlQuery q; 241 | q.setForwardOnly(true); 242 | // Limit the minimum search to the first 20 entires to speed up the query 243 | // Entires are usually sorted by time but it is not guaranteed 244 | q.prepare("select min(time) from (select time from samples where time != 0 and cpu = ? and thread_id = ? limit 20)"); 245 | q.bindValue(0,cpu); 246 | q.bindValue(1,threadId); 247 | q.exec(); 248 | if(q.next()) 249 | { 250 | auto val = q.value(0).toULongLong(); 251 | cache.insert({cpu,threadId},val); 252 | return val; 253 | } 254 | else 255 | { 256 | throw(q.lastError().text() + q.lastQuery()); 257 | } 258 | } 259 | } 260 | 261 | unsigned long long CounterAttributes::getFirstTimestamp(QList cpus) const 262 | { 263 | auto cpuStr = listToString(cpus); 264 | QSqlQuery q(db); 265 | q.prepare("select min(time) from (select time from samples where time != 0 and cpu in (" + cpuStr + ") limit 20)" ); 266 | q.exec(); 267 | if(q.next()) 268 | { 269 | return q.value(0).toULongLong(); 270 | } 271 | else 272 | { 273 | throw(q.lastError().text() + q.lastQuery()); 274 | } 275 | } 276 | 277 | QList> CounterAttributes::getUsedCpuThreadPairs() const 278 | { 279 | QSqlQuery q("select cpu, thread_id from samples group by thread_id,cpu having count(*) > 10 and count(distinct evsel_id) > ?",db); 280 | q.bindValue(0,eventIdToName.keys().length()); 281 | q.exec(); 282 | QList> pairs; 283 | while (q.next()) 284 | { 285 | pairs.append({q.value(0).toULongLong(),q.value(1).toULongLong()}); 286 | } 287 | if(pairs.empty()) 288 | { 289 | throw (q.lastError().text() + q.lastQuery()); 290 | } 291 | return pairs; 292 | } 293 | 294 | double CounterAttributes::calculateRatePerMs(unsigned long long counterValue, unsigned long long timeInterval) 295 | { 296 | auto rate = static_cast(counterValue) * pow(10,6) / static_cast(timeInterval); 297 | return rate; 298 | } 299 | 300 | void CounterAttributes::updateIntervalsForCpus(const QList& cpus, const unsigned long long eventId) 301 | { 302 | auto cpuStr = listToString(cpus); 303 | QSqlQuery q("select time,period from samples where evsel_id = ? and cpu in (" + cpuStr + ")"); 304 | q.setForwardOnly(true); 305 | q.bindValue(0,eventId); 306 | q.exec(); 307 | unsigned int counter = 0; 308 | unsigned long long sum = 0; 309 | unsigned long long timeBegin = getFirstTimestamp(cpus); 310 | auto time = timeBegin; 311 | while(q.next()) 312 | { 313 | auto period = q.value(1).toULongLong(); 314 | time = q.value(0).toULongLong(); 315 | sum+=period; 316 | counter++; 317 | if(counter == 1000) 318 | { 319 | auto rate = calculateRatePerMs(sum,time-timeBegin); 320 | updateCounterSamples(eventId,cpus,timeBegin,time,rate); 321 | counter = 0; 322 | sum = 0; 323 | timeBegin = time; 324 | } 325 | } 326 | auto rate = calculateRatePerMs(sum,time-timeBegin); 327 | updateCounterSamples(eventId,cpus,timeBegin,time,rate); 328 | } 329 | 330 | void CounterAttributes::updateIntervalsForCpuAndThread(const unsigned long long cpu, const unsigned long long thread, const unsigned long long eventId) 331 | { 332 | static QSqlQuery q("select time,period from samples where evsel_id = ? and cpu = ? and thread_id = ?",db); 333 | q.setForwardOnly(true); 334 | q.bindValue(1,cpu); 335 | q.bindValue(2,thread); 336 | q.bindValue(0,eventId); 337 | q.exec(); 338 | unsigned int counter = 0; 339 | unsigned long long sum = 0; 340 | unsigned long long timeBegin = getFirstTimestamp(cpu,thread); 341 | auto time = timeBegin; 342 | 343 | while(q.next()) 344 | { 345 | auto period = q.value(1).toULongLong(); 346 | time = q.value(0).toULongLong(); 347 | sum+=period; 348 | counter++; 349 | if(counter == 1000) 350 | { 351 | auto rate = calculateRatePerMs(sum,time-timeBegin); 352 | auto hitRates = getCacheHitRates(cpu,thread,timeBegin,time); 353 | updateCounterSamples(eventId,cpu,thread,timeBegin,time,rate,hitRates); 354 | counter = 0; 355 | sum = 0; 356 | timeBegin = time; 357 | } 358 | } 359 | auto hitRates = getCacheHitRates(cpu,thread,timeBegin,time); 360 | auto rate = calculateRatePerMs(sum,time-timeBegin); 361 | updateCounterSamples(eventId,cpu,thread,timeBegin,time,rate,hitRates); 362 | } 363 | 364 | QHash CounterAttributes::getCacheHitRates(const unsigned long long cpu, const unsigned long long thread, const unsigned long long timeBegin,const unsigned long long timeEnd) const 365 | { 366 | QString sAllAccess(R"(select count(*) from samples where evsel_id = 367 | (select id from selected_events where name like '%mem-loads%') and cpu = ? and thread_id = ? and time > ? and time <= ? and memory_level != 1 )"); 368 | QString sCacheAccess(sAllAccess + " and memory_level = (select id from memory_levels where name = ? )"); 369 | QSqlQuery qAllAcces(sAllAccess); 370 | QSqlQuery qCacheAccess(sCacheAccess); 371 | QSqlQuery qL2OrL3ByLfb(sAllAccess + "and memory_level = 2 and weight <= 275"); // (select min(weight) * 1.1 from samples where memory_level = 16)"); 372 | QSqlQuery qLocalDramByLfb(sAllAccess + "and memory_level = 2 and weight > 275"); // (select min(weight) * 1.1 from samples where memory_level = 16)"); 373 | static QVector memories = {"LFB", "L2", "L3", "Local DRAM"}; 374 | qAllAcces.bindValue(0,cpu); 375 | qAllAcces.bindValue(1,thread); 376 | qAllAcces.bindValue(2,timeBegin); 377 | qAllAcces.bindValue(3,timeEnd); 378 | qAllAcces.exec(); 379 | double numAccess = 0.0; 380 | QHash result; 381 | for(auto s : memories) 382 | { 383 | result.insert(s,0.0); 384 | } 385 | if(qAllAcces.next()) 386 | { 387 | numAccess = qAllAcces.value(0).toDouble(); 388 | if(numAccess == 0.0) 389 | { 390 | return result; 391 | } 392 | else 393 | { 394 | for(auto s : memories) 395 | { 396 | qCacheAccess.bindValue(0,cpu); 397 | qCacheAccess.bindValue(1,thread); 398 | qCacheAccess.bindValue(2,timeBegin); 399 | qCacheAccess.bindValue(3,timeEnd); 400 | qCacheAccess.bindValue(4,s); 401 | qCacheAccess.exec(); 402 | if(qCacheAccess.next()) 403 | { 404 | auto v = qCacheAccess.value(0).toDouble(); 405 | if(s == "L2") 406 | { 407 | qL2OrL3ByLfb.bindValue(0,cpu); 408 | qL2OrL3ByLfb.bindValue(1,thread); 409 | qL2OrL3ByLfb.bindValue(2,timeBegin); 410 | qL2OrL3ByLfb.bindValue(3,timeEnd); 411 | qL2OrL3ByLfb.exec(); 412 | if(qL2OrL3ByLfb.next()) 413 | { 414 | v += qL2OrL3ByLfb.value(0).toDouble(); 415 | } 416 | else 417 | { 418 | throw(qL2OrL3ByLfb.lastError().text() + qL2OrL3ByLfb.lastQuery()); 419 | } 420 | 421 | } 422 | else if(s == "Local DRAM") 423 | { 424 | qLocalDramByLfb.bindValue(0,cpu); 425 | qLocalDramByLfb.bindValue(1,thread); 426 | qLocalDramByLfb.bindValue(2,timeBegin); 427 | qLocalDramByLfb.bindValue(3,timeEnd); 428 | qLocalDramByLfb.exec(); 429 | if(qLocalDramByLfb.next()) 430 | { 431 | v += qLocalDramByLfb.value(0).toDouble(); 432 | } 433 | else 434 | { 435 | throw(qLocalDramByLfb.lastError().text() + qLocalDramByLfb.lastQuery()); 436 | } 437 | } 438 | v = v / numAccess; 439 | result[s] = v; 440 | } 441 | else 442 | { 443 | throw(qCacheAccess.lastError().text() + qCacheAccess.lastQuery()); 444 | } 445 | } 446 | } 447 | } 448 | else 449 | { 450 | throw(qAllAcces.lastError().text() + qAllAcces.lastQuery()); 451 | } 452 | return result; 453 | } 454 | 455 | void CounterAttributes::updateIntervals(const EventList& consideredEvents) 456 | { 457 | this->eventList = consideredEvents; 458 | try { 459 | createTable(consideredEvents); 460 | createEventIdMap(consideredEvents); 461 | createIndexes(); 462 | writeSampleIds(); 463 | auto cpuThreadPairs = getUsedCpuThreadPairs(); 464 | for(auto event : eventNameToId.keys()) 465 | { 466 | auto eventId = eventNameToId.value(event); 467 | auto eAttr = getEventAttributes(event); 468 | if(eAttr.offcore == true) 469 | { 470 | for(auto node : nodeToCpus.keys()) 471 | { 472 | auto cpus = nodeToCpus[node]; 473 | updateIntervalsForCpus(cpus,eventId); 474 | } 475 | } 476 | else 477 | { 478 | for(auto p : cpuThreadPairs) 479 | { 480 | updateIntervalsForCpuAndThread(p.first,p.second,eventId); 481 | } 482 | } 483 | } 484 | 485 | db.transaction(); 486 | writeCounterSamples(); 487 | db.commit(); 488 | createMetricViews(eventList); 489 | } 490 | catch (QString& s) 491 | { 492 | std::cout << "Error in CounterAttributes: " << s.toStdString(); 493 | } 494 | } 495 | 496 | bool CounterAttributes::EventAttribute::operator !=(const CounterAttributes::EventAttribute &other) const 497 | { 498 | return !(*this == other); 499 | } 500 | 501 | bool CounterAttributes::EventAttribute::operator==(const CounterAttributes::EventAttribute &other) const 502 | { 503 | return this->offcore == other.offcore; 504 | } 505 | -------------------------------------------------------------------------------- /prepareDatabase/counterattributes.h: -------------------------------------------------------------------------------- 1 | #ifndef COUNTERATTRIBUTES_H 2 | #define COUNTERATTRIBUTES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class CounterAttributes 9 | { 10 | public: 11 | 12 | typedef QList EventList; 13 | 14 | CounterAttributes(QSqlDatabase& db); 15 | void setNodeMapping(const QHash>& cpus); 16 | void updateIntervals(const EventList &consideredEvents); 17 | 18 | 19 | private: 20 | struct EventAttribute 21 | { 22 | bool offcore = false; 23 | bool operator!=(const EventAttribute& other) const; 24 | bool operator==(const EventAttribute& other) const; 25 | }; 26 | 27 | struct BufferEntry 28 | { 29 | QVariantList values; 30 | QVariantList startTimes; 31 | QVariantList endTimes; 32 | QVariantList cpus; 33 | QVariantList threads; 34 | QVariantList l1HitRates; 35 | QVariantList lfbHitRates; 36 | QVariantList l2HitRates; 37 | QVariantList l3HitRates; 38 | QVariantList localDramHitRates; 39 | }; 40 | 41 | QSqlDatabase db; 42 | QHash eventIdToName; 43 | QHash eventNameToId; 44 | EventList eventList; 45 | QHash cpuToNode; 46 | QHash> nodeToCpus; 47 | QHash buffer; 48 | 49 | void createEventIdMap(const EventList& consideredEvents); 50 | void createTable(EventList events); 51 | QHash calcTimeSumInterval(unsigned long long startTime, QList consdiredEvents, const unsigned long long cpu, const unsigned long long threadId, unsigned long long& maxEndTime); 52 | bool containsAllKeys(QHash map, QList keys); 53 | void writeSampleIds(); 54 | void updateCounterSamples(unsigned long long eventId, const unsigned long long cpu, const unsigned long long threadId, unsigned long long startTime, unsigned long long endTime, double value, QHash hitRates); 55 | unsigned long long getRawCounterValue(unsigned long long eventId, const unsigned long long cpu, const unsigned long long threadId, unsigned long long startTime, unsigned long long endTime); 56 | template 57 | QString listToString(const QList &list) const; 58 | bool isLast(const unsigned long long startTime, const QList &consideredEvents) const; 59 | unsigned long long getFirstTimestamp(const unsigned long long cpu, const unsigned long long threadId) const; 60 | unsigned long long getFirstTimestamp(QList cpus) const; 61 | unsigned long long adjustCounterValue(unsigned long long raw, unsigned long long activeTime, unsigned long long totalTime) const; 62 | void createIndexes(); 63 | bool allAtLeast(QHash map, unsigned long value); 64 | QList getUsedCpus() const; 65 | QList getUsedThreadIds() const; 66 | unsigned long long nextIntervalBegin(const unsigned long long cpu, const unsigned long long threadId, const QList &eventIds, unsigned long long endTime) const; 67 | double calculateRatePerMs(unsigned long long counterValue, unsigned long long timeInterval); 68 | QStringList createFlatEventList(const EventList &eventList) const; 69 | EventAttribute getEventAttributes(const QString& event) const; 70 | EventAttribute getEventAttributes(const unsigned long long event) const; 71 | 72 | 73 | CounterAttributes::EventAttribute getEventAttributes(const QList &eventList) const; 74 | CounterAttributes::EventAttribute getEventAttributes(const QList > &eventList) const; 75 | QHash calcTimeSumInterval(unsigned long long startTime, QSqlQuery &q, QList consideredEvents, unsigned long long &maxEndTime); 76 | unsigned long long nextIntervalBegin(const QList &cpu, const QList &eventIds, unsigned long long endTime) const; 77 | QHash calcTimeSumInterval(unsigned long long startTime, QList consideredEvents, const QList &cpu, unsigned long long &maxEndTime); 78 | QHash calcTimeSumInterval(unsigned long long startTime, QList consideredEvents, QSqlQuery& orderedEventsQuery, unsigned long long& maxEndTime); 79 | unsigned long long getRawCounterValue(unsigned long long eventId, const QList &cpu, unsigned long long startTime, unsigned long long endTime); 80 | void updateCounterSamples(unsigned long long eventId, const QList &cpu, unsigned long long startTime, unsigned long long endTime, double value); 81 | void updateIntervalsForCpus(const QList &cpus, const unsigned long long eventId); 82 | void updateIntervalsForCpuAndThread(const unsigned long long cpu, const unsigned long long thread, const unsigned long long eventId); 83 | QList getEventIds() const; 84 | QList > getUsedCpuThreadPairs() const; 85 | void writeCounterSamples(); 86 | void createMetricViews(const EventList &list); 87 | QHash getCacheHitRates(const unsigned long long cpu, const unsigned long long thread, const unsigned long long timeBegin, const unsigned long long timeEnd) const; 88 | }; 89 | 90 | #endif // COUNTERATTRIBUTES_H 91 | -------------------------------------------------------------------------------- /prepareDatabase/prepareDatabase.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | QT += sql core 3 | 4 | CONFIG += c++11 console 5 | CONFIG -= app_bundle 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any feature of Qt which as been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if you use deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += main.cpp \ 19 | address2Line.cpp \ 20 | counterattributes.cpp 21 | 22 | HEADERS += \ 23 | address2Line.h \ 24 | counterattributes.h 25 | -------------------------------------------------------------------------------- /setPerfEventPermissions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo sh -c "echo -1 > /proc/sys/kernel/perf_event_paranoid" 3 | -------------------------------------------------------------------------------- /viewer/.gitignore: -------------------------------------------------------------------------------- 1 | viewer.pro.user 2 | moc_* 3 | *.o 4 | ui_* 5 | Makefile 6 | viewer 7 | .qmake.stash 8 | 9 | -------------------------------------------------------------------------------- /viewer/README.md: -------------------------------------------------------------------------------- 1 | PerfMemPlus Viewer 2 | =============== 3 | 4 | A tool to view the results of a profiling session 5 | 6 | Installation 7 | ===== 8 | It requires QT 5.9 and qmake. 9 | 10 | The program depens on the comamnd "addr2line" to be available on the system where it is run 11 | 12 | For compilation do the following steps: 13 | 14 | qmake viewer.pro 15 | 16 | make qmake\_all 17 | 18 | make 19 | 20 | Usage 21 | ===== 22 | Optional parameters for: 23 | 24 | path to database containing PerfMemPlus profiling data 25 | 26 | path to executable which is analysed. This is required for showing more detailed allocation call stacks 27 | 28 | -------------------------------------------------------------------------------- /viewer/abstracttimelinewidget.cpp: -------------------------------------------------------------------------------- 1 | #include "abstracttimelinewidget.h" 2 | #include 3 | #include 4 | #include 5 | 6 | AbstractTimelineWidget::AbstractTimelineWidget(QWidget *parent) : QWidget(parent) 7 | { 8 | painter = new QPainter(); 9 | } 10 | 11 | AbstractTimelineWidget::~AbstractTimelineWidget() 12 | { 13 | delete painter; 14 | } 15 | 16 | void AbstractTimelineWidget::paintBackground(QPaintEvent *event) 17 | { 18 | auto background = QBrush(QColor(Qt::white)); 19 | painter->fillRect(event->rect(),background); 20 | } 21 | 22 | unsigned long long AbstractTimelineWidget::scale(const unsigned long long x) 23 | { 24 | double scale = (double) this->size().width() / (double) maxValue; 25 | unsigned long long drawPos = std::round((double)x * scale); 26 | return drawPos; 27 | } 28 | 29 | QString AbstractTimelineWidget::decodeTime(const unsigned long long value) 30 | { 31 | auto us = value / 1000; 32 | auto ms = us / 1000; 33 | auto s = ms / 1000; 34 | auto min = s / 60; 35 | QString text; 36 | if(min != 0) 37 | { 38 | text = QString::number(min) + "min "; 39 | s = s % 60; 40 | text += QString::number(s) + "s "; 41 | } 42 | else if(s != 0) 43 | { 44 | text = QString::number(s) + "s "; 45 | ms = ms % 1000; 46 | text += QString::number(ms) + "ms "; 47 | } 48 | else if (ms != 0) 49 | { 50 | text = QString::number(ms) + "ms "; 51 | us = us % 1000; 52 | text += QString::number(us) + "us "; 53 | } 54 | else if(us != 0) 55 | { 56 | text = QString::number(us) + "us "; 57 | auto ns = value % 1000; 58 | text += QString::number(ns) + "ns "; 59 | } 60 | else 61 | { 62 | text = QString::number(value) + "ns"; 63 | } 64 | return text; 65 | } 66 | 67 | void AbstractTimelineWidget::setAxis(unsigned long long min, unsigned long long max) 68 | { 69 | this->minValue = min; 70 | this->maxValue = max; 71 | } 72 | 73 | void AbstractTimelineWidget::setMaxValue(const unsigned long long value) 74 | { 75 | this->maxValue = value; 76 | } 77 | -------------------------------------------------------------------------------- /viewer/abstracttimelinewidget.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTTIMELINEWIDGET_H 2 | #define ABSTRACTTIMELINEWIDGET_H 3 | 4 | #include 5 | 6 | class QPaintEvent; 7 | 8 | class AbstractTimelineWidget : public QWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit AbstractTimelineWidget(QWidget *parent = nullptr); 13 | virtual ~AbstractTimelineWidget(); 14 | void setMaxValue(const unsigned long long value); 15 | void setAxis(unsigned long long min, unsigned long long max); 16 | 17 | protected: 18 | unsigned long long maxValue = 1; 19 | unsigned long long minValue = 0; 20 | QPainter* painter; 21 | virtual void paintEvent(QPaintEvent* event) = 0; 22 | QString decodeTime(const unsigned long long value); 23 | unsigned long long scale(const unsigned long long x); 24 | void paintBackground(QPaintEvent *event); 25 | 26 | signals: 27 | 28 | public slots: 29 | }; 30 | 31 | #endif // ABSTRACTTIMELINEWIDGET_H 32 | -------------------------------------------------------------------------------- /viewer/allocationcallpathresolver.cpp: -------------------------------------------------------------------------------- 1 | #include "allocationcallpathresolver.h" 2 | #include 3 | #include 4 | #include 5 | 6 | bool AllocationCallPathResolver::columnExists(const QString& tableName, const QString& columnName) const 7 | { 8 | auto db = QSqlDatabase::database(); 9 | static QMap resultCache; 10 | if(resultCache.contains(db.databaseName())) 11 | { 12 | return resultCache[db.databaseName()]; 13 | } 14 | else 15 | { 16 | QSqlQuery tableInfo("PRAGMA table_info(" + tableName + ")"); 17 | tableInfo.exec(); 18 | while(tableInfo.next()) 19 | { 20 | if(tableInfo.value("name") == columnName) 21 | { 22 | resultCache.insert(db.databaseName(),true); 23 | return true; 24 | } 25 | } 26 | resultCache.insert(db.databaseName(),false); 27 | return false; 28 | } 29 | } 30 | 31 | template 32 | void AllocationCallPathResolver::addCallstackEntry(long long id, T* parent) const 33 | { 34 | QSqlQuery getCallstackEntry; 35 | if(columnExists("allocation_symbols","inlinedBy")) 36 | { 37 | getCallstackEntry.prepare("select parent_id, \ 38 | (select short_name from dsos where id = (select dso_id from allocation_symbols where id = allocation_symbol_id)) as dso, \ 39 | (select name from allocation_symbols where id = allocation_symbol_id) as name, \ 40 | (select printf('%x',ip) from allocation_symbols where id = allocation_symbol_id) as address, \ 41 | (select file from allocation_symbols where id = allocation_symbol_id) as file,\ 42 | (select line from allocation_symbols where id = allocation_symbol_id) as line,\ 43 | (select inlinedBy from allocation_symbols where id = allocation_symbol_id) as inlinedBy\ 44 | from allocation_call_paths where id = ?"); 45 | } 46 | else 47 | { 48 | getCallstackEntry.prepare("select parent_id, \ 49 | (select short_name from dsos where id = (select dso_id from allocation_symbols where id = allocation_symbol_id)) as dso, \ 50 | (select name from allocation_symbols where id = allocation_symbol_id) as name, \ 51 | (select printf('%x',ip) from allocation_symbols where id = allocation_symbol_id) as address, \ 52 | (select file from allocation_symbols where id = allocation_symbol_id) as file,\ 53 | (select line from allocation_symbols where id = allocation_symbol_id) as line\ 54 | from allocation_call_paths where id = ?"); 55 | } 56 | getCallstackEntry.bindValue(0,id); 57 | getCallstackEntry.exec(); 58 | if(getCallstackEntry.next()) 59 | { 60 | long long parentId = getCallstackEntry.value(0).toLongLong(); 61 | QString dso = getCallstackEntry.value(1).toString(); 62 | QString function = getCallstackEntry.value(2).toString(); 63 | QString ip = getCallstackEntry.value(3).toString(); 64 | QString file = getCallstackEntry.value(4).toString(); 65 | QString line = getCallstackEntry.value(5).toString(); 66 | QString inlinedBy = getCallstackEntry.value(6).toString(); 67 | auto item = new QTreeWidgetItem(parent); 68 | item->setText(0,dso); 69 | item->setText(1,function); 70 | item->setText(2,file + line + '\n' + inlinedBy); 71 | item->setExpanded(true); 72 | if(parentId != 0) 73 | { 74 | addCallstackEntry(parentId,item); 75 | } 76 | } 77 | else 78 | { 79 | return; 80 | } 81 | } 82 | 83 | void AllocationCallPathResolver::writeAllocationCallpathFromCallpathId(const int callpathId, QTreeWidget* treeWidget) const 84 | { 85 | addCallstackEntry(callpathId,treeWidget); 86 | } 87 | 88 | void AllocationCallPathResolver::writeAllocationCallpath(const int allocationId, QTreeWidget* treeWidget) const 89 | { 90 | QSqlQuery getCallpathId; 91 | getCallpathId.prepare("select call_path_id from allocations where id = ?"); 92 | getCallpathId.bindValue(0,allocationId); 93 | getCallpathId.exec(); 94 | long long callPathId = -1; 95 | if(getCallpathId.next()) 96 | { 97 | callPathId = getCallpathId.value(0).toLongLong(); 98 | addCallstackEntry(callPathId,treeWidget); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /viewer/allocationcallpathresolver.h: -------------------------------------------------------------------------------- 1 | #ifndef ALLOCATIONCALLPATHRESOLVER_H 2 | #define ALLOCATIONCALLPATHRESOLVER_H 3 | 4 | #include 5 | #include 6 | 7 | class AllocationCallPathResolver 8 | { 9 | public: 10 | AllocationCallPathResolver() = default; 11 | void writeAllocationCallpath(const int allocationId, QTreeWidget *treeWidget) const; 12 | void writeAllocationCallpathFromCallpathId(const int callpathId, QTreeWidget *treeWidget) const; 13 | private: 14 | template 15 | void addCallstackEntry(long long id, T *parent) const; 16 | bool columnExists(const QString &tableName, const QString &columnName) const; 17 | }; 18 | 19 | #endif // ALLOCATIONCALLPATHRESOLVER_H 20 | -------------------------------------------------------------------------------- /viewer/analysismain.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYSISMAIN_H 2 | #define ANALYSISMAIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Ui { 9 | class AnalysisMain; 10 | } 11 | 12 | struct BandwidthResult; 13 | class TreeItem; 14 | class Result; 15 | 16 | 17 | class AnalysisMain : public QMainWindow 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit AnalysisMain(QWidget *parent = nullptr); 23 | ~AnalysisMain(); 24 | void setDbPath(const QString& path, const bool headless = false); 25 | 26 | void dragEnterEvent(QDragEnterEvent *event); 27 | void dropEvent(QDropEvent *event); 28 | 29 | 30 | public slots: 31 | 32 | private slots: 33 | void objectsTableRowChanged(); 34 | void objectsCallPathTableRowChanged(); 35 | void functionsTableRowChanged(); 36 | void on_actionOpen_Database_triggered(); 37 | void on_functionsCachePushButton_clicked(); 38 | void on_showCacheObjectspushButton_clicked(); 39 | void on_showObjectsAccessedByPushButton_clicked(); 40 | void on_exportToPdfPushButton_clicked(); 41 | void on_exportToPdfPushButton_2_clicked(); 42 | void on_showAccessTimelinePushButton_clicked(); 43 | void on_checkBox_stateChanged(int arg1); 44 | void on_showCacheCoherencyPushButton_clicked(); 45 | void on_showObjectCacheCoherencyPushButton_clicked(); 46 | void on_timeAccessDiagramButton_clicked(); 47 | void on_timeAccessObjectsDiagramPushButton_clicked(); 48 | void on_runPushButton_clicked(); 49 | void on_exportToPdfPushButton_3_clicked(); 50 | void displayCallstack(); 51 | 52 | private: 53 | Ui::AnalysisMain *ui; 54 | QSqlRelationalTableModel *modelFunctions; 55 | QSqlRelationalTableModel *modelObjects; 56 | QItemSelectionModel *modelObjectsSelectionModel; 57 | QSqlRelationalTableModel *modelObjectsAllocationSites; 58 | QItemSelectionModel *modelObjectsAllocationSitesSelectionModel; 59 | QString dbPath; 60 | QFutureWatcher* queryCallstack; 61 | QFutureWatcher>* queryAutoAnalysis; 62 | 63 | void showError(const QSqlError &err, const bool headless); 64 | void loadDatabase(const QString& path, const bool headless = false); 65 | void showAllocationCallpath(const int allocationId); 66 | int getColumnCount(QTableView *tv) const; 67 | int getColumnCount(QTreeWidget *tv) const; 68 | void createViews(); 69 | void changeModel(QTableView *view, QAbstractItemModel *model, QItemSelectionModel *selectionModel); 70 | QStringList getAllocationIdsFromCallPathIds(const QStringList &allocationIds) const; 71 | void showAllocationCallpathFromCallPathId(const int callpathId); 72 | QStringList getSelectedFunctions() const; 73 | QStringList getSelectedObjects() const; 74 | QPair getFileAndLineOfCallpathId(int id); 75 | QString getShortFileAndLineOfCallpathId(int id); 76 | void showBandwidthResults(const BandwidthResult& r,const auto& objectItem); 77 | void showCallpath(const QString& functionName); 78 | TreeItem *printCallstack(QString fName); 79 | void printCallstackEntry(long long id, long long level, QVector &callStack); 80 | QString space(long long level) const; 81 | void displayAutoAnalysisResult(); 82 | void sqlitePerformanceSettings(); 83 | }; 84 | 85 | #endif // ANALYSISMAIN_H 86 | -------------------------------------------------------------------------------- /viewer/analysismain.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AnalysisMain 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1224 10 | 588 11 | 12 | 13 | 14 | true 15 | 16 | 17 | PerfMemPlus Viewer 18 | 19 | 20 | 21 | 32 22 | 32 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | 39 | true 40 | 41 | 42 | 0 43 | 44 | 45 | 46 | Functions 47 | 48 | 49 | 50 | 51 | 52 | Qt::Vertical 53 | 54 | 55 | 56 | QAbstractItemView::NoEditTriggers 57 | 58 | 59 | QAbstractItemView::ExtendedSelection 60 | 61 | 62 | QAbstractItemView::SelectRows 63 | 64 | 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Selected Functions 75 | 76 | 77 | Qt::AlignBottom|Qt::AlignHCenter 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Qt::Horizontal 90 | 91 | 92 | 93 | 40 94 | 20 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Export To PDF 103 | 104 | 105 | 106 | 107 | 108 | 109 | Show Time/Address Diagram 110 | 111 | 112 | 113 | 114 | 115 | 116 | Show Objects Accessed 117 | 118 | 119 | 120 | 121 | 122 | 123 | Show Cache Coherency Statistic 124 | 125 | 126 | 127 | 128 | 129 | 130 | Show Cache Usage 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Objects 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | Qt::Horizontal 150 | 151 | 152 | 153 | 40 154 | 20 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Group By Allocation Site 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Qt::Vertical 172 | 173 | 174 | 175 | 176 | 0 177 | 0 178 | 179 | 180 | 181 | 182 | 16777215 183 | 16777215 184 | 185 | 186 | 187 | QAbstractItemView::NoEditTriggers 188 | 189 | 190 | QAbstractItemView::ExtendedSelection 191 | 192 | 193 | QAbstractItemView::SelectRows 194 | 195 | 196 | true 197 | 198 | 199 | false 200 | 201 | 202 | 203 | 204 | 205 | 16777215 206 | 16777215 207 | 208 | 209 | 210 | Qt::ScrollBarAsNeeded 211 | 212 | 213 | Qt::ScrollBarAsNeeded 214 | 215 | 216 | QAbstractScrollArea::AdjustToContents 217 | 218 | 219 | QAbstractItemView::NoEditTriggers 220 | 221 | 222 | 0 223 | 224 | 225 | 0 226 | 227 | 228 | false 229 | 230 | 231 | false 232 | 233 | 234 | 3 235 | 236 | 237 | false 238 | 239 | 240 | 241 | DSO 242 | 243 | 244 | 245 | 246 | Function 247 | 248 | 249 | 250 | 251 | File and Line 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | Selected Objects 261 | 262 | 263 | Qt::AlignBottom|Qt::AlignHCenter 264 | 265 | 266 | 267 | 268 | 269 | Qt::Horizontal 270 | 271 | 272 | 273 | 40 274 | 20 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Export to PDF 283 | 284 | 285 | 286 | 287 | 288 | 289 | Show Time/Address Diagram 290 | 291 | 292 | 293 | 294 | 295 | 296 | Show Access Timeline 297 | 298 | 299 | 300 | 301 | 302 | 303 | Show Cache Coherency Statistic 304 | 305 | 306 | 307 | 308 | 309 | 310 | Show Cache Usage 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | Auto Analysis 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | Qt::Horizontal 330 | 331 | 332 | 333 | 40 334 | 20 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | Run Analysis 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | Performance Problems 353 | 354 | 355 | 356 | 357 | Severity 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | Qt::Horizontal 368 | 369 | 370 | 371 | 40 372 | 20 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | Export to PDF 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 0 396 | 0 397 | 1224 398 | 22 399 | 400 | 401 | 402 | true 403 | 404 | 405 | 406 | File 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | Open Database 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | -------------------------------------------------------------------------------- /viewer/analysismain.ui.orig: -------------------------------------------------------------------------------- 1 | 2 | 3 | AnalysisMain 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1224 10 | 588 11 | 12 | 13 | 14 | true 15 | 16 | 17 | PerfMemPlus Viewer 18 | 19 | 20 | 21 | 32 22 | 32 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 0 30 | 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | 39 | true 40 | 41 | 42 | <<<<<<< HEAD 43 | 2 44 | ======= 45 | 0 46 | >>>>>>> refs/heads/master 47 | 48 | 49 | 50 | Functions 51 | 52 | 53 | 54 | 55 | 56 | Qt::Vertical 57 | 58 | 59 | 60 | QAbstractItemView::NoEditTriggers 61 | 62 | 63 | QAbstractItemView::ExtendedSelection 64 | 65 | 66 | QAbstractItemView::SelectRows 67 | 68 | 69 | true 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Selected Functions 78 | 79 | 80 | Qt::AlignBottom|Qt::AlignHCenter 81 | 82 | 83 | true 84 | 85 | 86 | false 87 | 88 | 89 | 90 | 91 | 92 | Qt::Horizontal 93 | 94 | 95 | 96 | 40 97 | 20 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Export To PDF 106 | 107 | 108 | 109 | 110 | 111 | 112 | Show Time/Address Diagram 113 | 114 | 115 | 116 | 117 | 118 | 119 | Show Objects Accessed 120 | 121 | 122 | 123 | 124 | 125 | 126 | Show Cache Coherency Statistic 127 | 128 | 129 | 130 | 131 | 132 | 133 | Show Cache Usage 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | Objects 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Qt::Horizontal 153 | 154 | 155 | 156 | 40 157 | 20 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | Group By Allocation Site 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | Qt::Vertical 175 | 176 | 177 | 178 | 179 | 0 180 | 0 181 | 182 | 183 | 184 | 185 | 16777215 186 | 16777215 187 | 188 | 189 | 190 | QAbstractItemView::NoEditTriggers 191 | 192 | 193 | QAbstractItemView::ExtendedSelection 194 | 195 | 196 | QAbstractItemView::SelectRows 197 | 198 | 199 | true 200 | 201 | 202 | false 203 | 204 | 205 | 206 | 207 | 208 | 16777215 209 | 16777215 210 | 211 | 212 | 213 | Qt::ScrollBarAlwaysOff 214 | 215 | 216 | Qt::ScrollBarAlwaysOn 217 | 218 | 219 | QAbstractScrollArea::AdjustToContents 220 | 221 | 222 | QAbstractItemView::NoEditTriggers 223 | 224 | 225 | 0 226 | 227 | 228 | 0 229 | 230 | 231 | false 232 | 233 | 234 | false 235 | 236 | 237 | 3 238 | 239 | 240 | true 241 | 242 | 243 | 244 | DSO 245 | 246 | 247 | 248 | 249 | Function 250 | 251 | 252 | 253 | 254 | File and Line 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | Selected Objects 264 | 265 | 266 | Qt::AlignBottom|Qt::AlignHCenter 267 | 268 | 269 | 270 | 271 | 272 | Qt::Horizontal 273 | 274 | 275 | 276 | 40 277 | 20 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | Export to PDF 286 | 287 | 288 | 289 | 290 | 291 | 292 | Show Time/Address Diagram 293 | 294 | 295 | 296 | 297 | 298 | 299 | Show Access Timeline 300 | 301 | 302 | 303 | 304 | 305 | 306 | Show Cache Coherency Statistic 307 | 308 | 309 | 310 | 311 | 312 | 313 | Show Cache Usage 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | Auto Analysis 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | Qt::Horizontal 333 | 334 | 335 | 336 | 40 337 | 20 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | Run Analysis 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | Performance Problems 356 | 357 | 358 | 359 | 360 | Severity 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | Qt::Horizontal 371 | 372 | 373 | 374 | 40 375 | 20 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | Export to PDF 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 0 399 | 0 400 | 1224 401 | 22 402 | 403 | 404 | 405 | true 406 | 407 | 408 | 409 | File 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | Open Database 420 | 421 | 422 | 423 | 424 | Set Executable 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | -------------------------------------------------------------------------------- /viewer/autoanalysis.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTOANALYSIS_H 2 | #define AUTOANALYSIS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class QSqlQuery; 9 | 10 | struct BandwidthResult 11 | { 12 | QString memory; 13 | float latency = 0; 14 | bool problem = false; 15 | bool lowSampleCount = false; 16 | float parallelReaders = 0; 17 | }; 18 | 19 | struct ClAccess { 20 | long long time; 21 | long long adr; 22 | int tid; 23 | int allocationId; 24 | long long ip; 25 | bool operator==(const ClAccess& other) const 26 | { 27 | return this->time == other.time && this->adr == other.adr && this->tid == other.tid && this->allocationId == other.allocationId && this->ip == other.ip; 28 | } 29 | }; 30 | 31 | struct Result 32 | { 33 | QString function = ""; 34 | int allocationId = -1; 35 | int callPathId = -1; 36 | bool falseSharing = false; 37 | unsigned int hitmCount = 0; 38 | bool trueSharing = false; 39 | float functionExectimePercent = 0; 40 | float objectLatencyPercent = 0; 41 | float hitmPercent = 0; 42 | QVector> interObjectFalseSharing; 43 | QVector> interObjectTrueSharing; 44 | QVector> intraObjectFalseSharing; 45 | QVector> intraObjectTrueSharing; 46 | BandwidthResult dramBandwidth; 47 | BandwidthResult remoteDramBandwidth; 48 | }; 49 | 50 | 51 | class AutoAnalysis 52 | { 53 | public: 54 | AutoAnalysis(); 55 | ~AutoAnalysis(); 56 | QList run(); 57 | void loadSettings(const QString& path = "settings.conf"); 58 | QList runFalseSharingDetection(); 59 | QList runDramContentionDetection(); 60 | 61 | private: 62 | unsigned int sampleCountLimit = 0; 63 | unsigned int hitmLimit = 0; 64 | QHash latencyLimit \ 65 | {{QStringLiteral("L1"),0}, {QStringLiteral("L2"),0}, {QStringLiteral("L3"),0}, \ 66 | {QStringLiteral("Local DRAM"),0}, {QStringLiteral("Remote DRAM (1 hop)"),0}, \ 67 | {QStringLiteral("Remote Cache (1 hops)"),0}}; 68 | 69 | 70 | QList runDetection(std::function analyze); 71 | QList getOffendingFunctions() const; 72 | QList getMostAccessedObjectsOfFunction(const int symbolId) const; 73 | void createIndexes() const; 74 | unsigned int getNumberOfHitmInFunction(const int symbolId, const int allocationId = -1) const; 75 | QString symbolIdToFuctionName(const int symbolId) const; 76 | QString allocationLimitString(const int allocationdId) const; 77 | bool isObjectWrittenByMultipleThreads(const int allocationId) const; 78 | float getLatencyLimit(const QString &memory) const; 79 | unsigned int getSampleCountInMemory(const int symbolId, const int allocationId, const QString &memory) const; 80 | float getLatencyInMemory(const int symbolId, const int allocationId, const QString &memory) const; 81 | BandwidthResult checkBandwidth(const QString &memory, const int symbolId, const int allocationId) const; 82 | float getAvgTimeBetweenMemoryReadSamples(const int symbolId, const int allocationId) const; 83 | float getNumberOfParallelReaders(const int symbolId, const int allocationId) const; 84 | QList runDetection(std::function analyze); 85 | Result detectDramContention(const int symbolId, const int allocation, const Result &rIn) const; 86 | Result detectFalseSharing(const int symbolId, const int allocation, const Result &rIn) const; 87 | unsigned int getSampleCount(const int symbolId, const int allocationId) const; 88 | }; 89 | 90 | #endif // AUTOANALYSIS_H 91 | -------------------------------------------------------------------------------- /viewer/graphwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "graphwindow.h" 2 | #include "ui_graphwindow.h" 3 | #include 4 | #include "pdfwriter.h" 5 | #include "sqlutils.h" 6 | 7 | QT_CHARTS_USE_NAMESPACE 8 | 9 | GraphWindow::GraphWindow(const QString& dbPath, QWidget *parent) : 10 | QDialog(parent), 11 | ui(new Ui::GraphWindow) 12 | { 13 | this->setWindowFlags(Qt::Window); 14 | ui->setupUi(this); 15 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 16 | chart = new QChart(); 17 | chartView = new QChartView(chart,this); 18 | ui->centralLayout->addWidget(chartView); 19 | axisY = new QValueAxis(chartView); 20 | axisX = new QValueAxis(chartView); 21 | } 22 | 23 | GraphWindow::~GraphWindow() 24 | { 25 | delete ui; 26 | } 27 | 28 | void GraphWindow::populateComboBoxWithThreads() 29 | { 30 | auto tids = getThreadIds(); 31 | ui->comboBox->blockSignals(true); 32 | ui->comboBox->clear(); 33 | for(auto tid : tids) 34 | { 35 | ui->comboBox->addItem(tid); 36 | } 37 | ui->comboBox->addItem("All Threads"); 38 | ui->comboBox->setCurrentText("All Threads"); 39 | ui->comboBox->blockSignals(false); 40 | } 41 | 42 | QVector GraphWindow::getThreadIds() const 43 | { 44 | QSqlQuery getThreadIdsQuery; 45 | if(!functions.empty() && !allocations.empty()) 46 | { 47 | auto sqlFunctions = SqlUtils::makeSqlStringFunctions(functions); 48 | auto sqlAllocations = SqlUtils::makeSqlStringObjects(allocations); 49 | getThreadIdsQuery.prepare("select (select tid from threads where id = thread_id) as \"tid\" from samples where \ 50 | symbol_id in (select id from symbols where name = " + sqlFunctions + " ) and \ 51 | allocation_id in ( " + sqlAllocations + ") order by tid asc"); 52 | } 53 | else if(!functions.empty() && allocations.empty()) 54 | { 55 | auto sqlFunctions = SqlUtils::makeSqlStringFunctions(functions); 56 | getThreadIdsQuery.prepare("select (select tid from threads where id = thread_id) as \"tid\" from samples where \ 57 | symbol_id in (select id from symbols where name = " + sqlFunctions + " ) order by tid asc"); 58 | } 59 | else if(functions.empty() && !allocations.empty()) 60 | { 61 | auto sqlAllocations = SqlUtils::makeSqlStringObjects(allocations); 62 | getThreadIdsQuery.prepare("select distinct (select tid from threads where id = thread_id) as \"tid\" from samples where \ 63 | allocation_id in ( " + sqlAllocations + ") order by tid asc"); 64 | } 65 | else 66 | { 67 | getThreadIdsQuery.prepare("select (select tid from threads where id = thread_id) as \"tid\" from samples order by tid asc"); 68 | } 69 | getThreadIdsQuery.exec(); 70 | QVector tids; 71 | while(getThreadIdsQuery.next()) 72 | { 73 | tids.append(getThreadIdsQuery.value(0).toString()); 74 | } 75 | return tids; 76 | } 77 | 78 | QVector GraphWindow::getTimeAddressData(const QString& threadId) const 79 | { 80 | QVector values; 81 | values.reserve(100000); 82 | QSqlQuery getTimeAddressQuery; 83 | QString tidQueryPart = ""; 84 | if(threadId != "") 85 | { 86 | tidQueryPart = "thread_id = (select id from threads where tid = " + threadId + ") and"; 87 | } 88 | if(!functions.empty() && !allocations.empty()) 89 | { 90 | auto sqlFunctions = SqlUtils::makeSqlStringFunctions(functions); 91 | auto sqlAllocations = SqlUtils::makeSqlStringObjects(allocations); 92 | getTimeAddressQuery.prepare("select time - (select min(time) from samples where id != 0),to_ip,thread_id from samples where id != 0 and \ 93 | memory_opcode in (select id from memory_opcodes where name = \"Load\" or name = \"Store\") and \ 94 | " + tidQueryPart + "\ 95 | symbol_id in (select id from symbols where name = " + sqlFunctions + " ) and \ 96 | allocation_id in ( " + sqlAllocations + ") order by time asc"); 97 | } 98 | else if(!functions.empty() && allocations.empty()) 99 | { 100 | auto sqlFunctions = SqlUtils::makeSqlStringObjects(functions); 101 | getTimeAddressQuery.prepare("select time - (select min(time) from samples where id != 0),to_ip,thread_id from samples where id != 0 and \ 102 | memory_opcode in (select id from memory_opcodes where name = \"Load\" or name = \"Store\") and \ 103 | " + tidQueryPart + "\ 104 | symbol_id in (select id from symbols where name = " + sqlFunctions + " ) order by time asc"); 105 | } 106 | else if(functions.empty() && !allocations.empty()) 107 | { 108 | auto sqlAllocations = SqlUtils::makeSqlStringObjects(allocations); 109 | getTimeAddressQuery.prepare("select time - (select min(time) from samples where id != 0),to_ip,thread_id from samples where id != 0 and \ 110 | memory_opcode in (select id from memory_opcodes where name = \"Load\" or name = \"Store\") and \ 111 | " + tidQueryPart + "\ 112 | allocation_id in ( " + sqlAllocations + ") order by time asc"); 113 | } 114 | else 115 | { 116 | getTimeAddressQuery.prepare("select time - (select min(time) from samples where id != 0),to_ip,thread_id from samples where id != 0 and \ 117 | " + tidQueryPart + "\ 118 | memory_opcode in (select id from memory_opcodes where name = \"Load\" or name = \"Store\") \ 119 | order by time asc limit 1000"); 120 | } 121 | getTimeAddressQuery.setForwardOnly(true); 122 | getTimeAddressQuery.exec(); 123 | while(getTimeAddressQuery.next()) 124 | { 125 | values.push_back(PointType(getTimeAddressQuery.value(0).toULongLong(),getTimeAddressQuery.value(1).toULongLong())); 126 | } 127 | return values; 128 | } 129 | 130 | void GraphWindow::setAxisY(const QVector& values, QChartView *chartView) 131 | { 132 | axisY->setTitleText("Address"); 133 | auto minAddressPoint = std::min_element(values.begin(),values.end(),[](auto lhs, auto rhs){return lhs.second < rhs.second;}); 134 | axisY->setMin(minAddressPoint->second); 135 | auto maxAddressPoint = std::max_element(values.begin(),values.end(),[](auto lhs, auto rhs){return lhs.second < rhs.second;}); 136 | axisY->setMax(maxAddressPoint->second); 137 | axisY->setLabelFormat("%#x"); 138 | chartView->chart()->setAxisY(axisY); 139 | minPoint.second = minAddressPoint->second; 140 | maxPoint.second = maxAddressPoint->second; 141 | } 142 | 143 | void GraphWindow::setAxisX(const QVector& values, QChartView *chartView) 144 | { 145 | axisX->setMin(0); 146 | auto maxTimePoint = std::max_element(values.begin(),values.end(),[](auto lhs, auto rhs){return lhs.first < rhs.first;}); 147 | auto time = decodeTime(maxTimePoint->first); 148 | axisX->setMax(time.first); 149 | axisX->setTitleText("Time [" + time.second + "]"); 150 | chartView->chart()->setAxisX(axisX); 151 | minPoint.first = 0; 152 | maxPoint.first = maxTimePoint->first; 153 | } 154 | 155 | void GraphWindow::setPointStyle(QScatterSeries* series, const QColor& c = Qt::black) 156 | { 157 | QPen pen; 158 | pen.setWidth(1); 159 | series->setPen(pen); 160 | QBrush brush; 161 | brush.setStyle(Qt::SolidPattern); 162 | brush.setColor(c); 163 | series->setBrush(brush); 164 | series->setMarkerSize(2); 165 | } 166 | 167 | void GraphWindow::showEvent(QShowEvent *event) 168 | { 169 | if(event->spontaneous() == false) 170 | { 171 | //if show event is cuased by this application, not by OS 172 | draw(); 173 | } 174 | } 175 | 176 | QColor GraphWindow::getRandomColor(const QList& prevCol) const 177 | { 178 | QList brushScale; 179 | static const int init = 1; 180 | constexpr double golden_ratio = 0.618033988749895 * 256; 181 | double h = init + golden_ratio * prevCol.count(); 182 | auto hInt = static_cast(h) % 256; 183 | auto c = QColor::fromHsv(hInt, 235, 235, 255); 184 | return c; 185 | } 186 | 187 | void GraphWindow::draw(const QString& threadId) 188 | { 189 | chart->removeAllSeries(); 190 | 191 | if(threadId == "") 192 | { 193 | QMap tidColorMap; 194 | auto tids = getThreadIds(); 195 | for(auto tid : tids) 196 | { 197 | auto series = new QScatterSeries(chart); 198 | series->setUseOpenGL(true); 199 | series->setName(tid); 200 | auto values = getTimeAddressData(tid); 201 | if(!values.empty()) 202 | { 203 | if(tidColorMap.contains(tid)) 204 | { 205 | setPointStyle(series,tidColorMap.value(tid)); 206 | } 207 | else 208 | { 209 | auto c = getRandomColor(tidColorMap.values()); 210 | tidColorMap.insert(tid,c); 211 | setPointStyle(series,c); 212 | } 213 | } 214 | for(auto&& item : values) 215 | { 216 | *series << QPointF(item.first,item.second); 217 | } 218 | if(!axisSet) 219 | { 220 | chart->createDefaultAxes(); 221 | setAxisX(values, chartView); 222 | setAxisY(values, chartView); 223 | axisSet = true; 224 | } 225 | chart->addSeries(series); 226 | } 227 | } 228 | else 229 | { 230 | auto series = new QScatterSeries(chart); 231 | series->setUseOpenGL(true); 232 | series->setName("Thread Id " + threadId); 233 | setPointStyle(series); 234 | auto values = getTimeAddressData(threadId); 235 | for(auto&& item : values) 236 | { 237 | *series << QPointF(item.first,item.second); 238 | } 239 | if(!axisSet) 240 | { 241 | chart->createDefaultAxes(); 242 | setAxisX(values, chartView); 243 | setAxisY(values, chartView); 244 | axisSet = true; 245 | } 246 | chart->addSeries(series); 247 | } 248 | QPen invPen; 249 | invPen.setWidth(0); 250 | invPen.setColor(Qt::transparent); 251 | QBrush invBrush; 252 | invBrush.setColor(Qt::transparent); 253 | auto invSer = new QScatterSeries(chart); 254 | invSer->setUseOpenGL(true); 255 | invSer->setPen(invPen); 256 | invSer->setBrush(invBrush); 257 | *invSer << QPointF(minPoint.first,minPoint.second); 258 | *invSer << QPointF(maxPoint.first,maxPoint.second); 259 | chart->addSeries(invSer); 260 | invSer->setVisible(false); 261 | } 262 | 263 | void GraphWindow::setFunctions(const QStringList &functions) 264 | { 265 | this->functions = functions; 266 | populateComboBoxWithThreads(); 267 | } 268 | 269 | void GraphWindow::setAllocations(const QStringList &allocations) 270 | { 271 | this->allocations = allocations; 272 | populateComboBoxWithThreads(); 273 | } 274 | 275 | QPair GraphWindow::decodeTime(const unsigned long long value) const 276 | { 277 | auto us = value / 1000; 278 | auto ms = us / 1000; 279 | auto s = ms / 1000; 280 | auto min = s / 60; 281 | QString text; 282 | unsigned long long outValue = 0; 283 | if(min != 0) 284 | { 285 | text = "s"; 286 | outValue = s + s%60; 287 | } 288 | else if(s != 0) 289 | { 290 | text = "ms"; 291 | outValue = ms + ms % 1000; 292 | } 293 | else if (ms != 0) 294 | { 295 | text = "us"; 296 | outValue = us + us % 1000; 297 | } 298 | else if(us != 0) 299 | { 300 | text = "ns"; 301 | outValue = value; 302 | } 303 | else 304 | { 305 | text = "ns"; 306 | outValue = value; 307 | } 308 | return QPair(outValue,text); 309 | } 310 | 311 | void GraphWindow::on_comboBox_currentIndexChanged(const QString &arg1) 312 | { 313 | if(arg1 == "All Threads") 314 | { 315 | draw(); 316 | } 317 | else 318 | { 319 | draw(arg1); 320 | } 321 | } 322 | 323 | void GraphWindow::on_exportToPdfPushButton_clicked() 324 | { 325 | PdfWriter p; 326 | p.writeWidgetToPdf(chartView); 327 | } 328 | -------------------------------------------------------------------------------- /viewer/graphwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHWINDOW_H 2 | #define GRAPHWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | QT_CHARTS_USE_NAMESPACE 14 | 15 | namespace Ui { 16 | class GraphWindow; 17 | } 18 | 19 | class GraphWindow : public QDialog 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit GraphWindow(const QString &dbPath, QWidget *parent = 0) ; 25 | ~GraphWindow(); 26 | 27 | void setAllocations(const QStringList &allocations); 28 | void setFunctions(const QStringList& functions); 29 | 30 | public slots: 31 | 32 | private slots: 33 | void on_comboBox_currentIndexChanged(const QString &arg1); 34 | 35 | void on_exportToPdfPushButton_clicked(); 36 | 37 | private: 38 | Ui::GraphWindow *ui; 39 | typedef QPair PointType; 40 | QStringList functions; 41 | QStringList allocations; 42 | QChart *chart; 43 | QChartView *chartView; 44 | QValueAxis* axisY; 45 | QValueAxis* axisX; 46 | PointType minPoint; 47 | PointType maxPoint; 48 | bool axisSet = false; 49 | QVector getTimeAddressData(const QString& threadId = "") const; 50 | void setAxisY(const QVector& values, QChartView *chartView); 51 | void setAxisX(const QVector& values, QChartView *chartView); 52 | QPair decodeTime(const unsigned long long value) const; 53 | void populateComboBoxWithThreads(); 54 | void draw(const QString& threadId = ""); 55 | void setPointStyle(QScatterSeries* s, const QColor& c); 56 | virtual void showEvent(QShowEvent *event) override; 57 | QVector getThreadIds() const; 58 | QColor getRandomColor(const QList &prevCol) const; 59 | }; 60 | 61 | #endif // GRAPHWINDOW_H 62 | -------------------------------------------------------------------------------- /viewer/graphwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | GraphWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 640 10 | 480 11 | 12 | 13 | 14 | Time Address Diagram 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Qt::Horizontal 23 | 24 | 25 | 26 | 40 27 | 20 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Selected Thread: 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Qt::Horizontal 53 | 54 | 55 | 56 | 40 57 | 20 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Export to PDF 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /viewer/guiutils.cpp: -------------------------------------------------------------------------------- 1 | #include "guiutils.h" 2 | #include 3 | #include 4 | 5 | int GuiUtils::getColumnCount(QTableView* tv) 6 | { 7 | return tv->model()->columnCount(); 8 | } 9 | 10 | int GuiUtils::getColumnCount(QTreeWidget* tv) 11 | { 12 | return tv->columnCount(); 13 | } 14 | 15 | int GuiUtils::getColumnCount(QTreeView* tv) 16 | { 17 | return tv->model()->columnCount(); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /viewer/guiutils.h: -------------------------------------------------------------------------------- 1 | #ifndef GUIUTILS_H 2 | #define GUIUTILS_H 3 | 4 | class QTableView; 5 | class QTreeWidget; 6 | class QTreeView; 7 | 8 | class GuiUtils 9 | { 10 | private: 11 | static int getColumnCount(QTableView *tv); 12 | static int getColumnCount(QTreeWidget *tv); 13 | static int getColumnCount(QTreeView *tv); 14 | 15 | public: 16 | GuiUtils() = default; 17 | 18 | template 19 | static void resizeColumnsToContents(T* widget, const int columnSizeLimit = 600) 20 | { 21 | for(int i = 0; i < getColumnCount(widget); i++) 22 | { 23 | widget->resizeColumnToContents(i); 24 | if(widget->columnWidth(i)> columnSizeLimit) 25 | { 26 | widget->setColumnWidth(i,columnSizeLimit); 27 | } 28 | } 29 | } 30 | 31 | }; 32 | 33 | #endif // GUIUTILS_H 34 | -------------------------------------------------------------------------------- /viewer/initdb.h: -------------------------------------------------------------------------------- 1 | #ifndef INITDB_H 2 | #define INITDB_H 3 | 4 | /**************************************************************************** 5 | ** 6 | ** Copyright (C) 2016 The Qt Company Ltd. 7 | ** Contact: https://www.qt.io/licensing/ 8 | ** 9 | ** This file is part of the demonstration applications of the Qt Toolkit. 10 | ** 11 | ** $QT_BEGIN_LICENSE:BSD$ 12 | ** Commercial License Usage 13 | ** Licensees holding valid commercial Qt licenses may use this file in 14 | ** accordance with the commercial license agreement provided with the 15 | ** Software or, alternatively, in accordance with the terms contained in 16 | ** a written agreement between you and The Qt Company. For licensing terms 17 | ** and conditions see https://www.qt.io/terms-conditions. For further 18 | ** information use the contact form at https://www.qt.io/contact-us. 19 | ** 20 | ** BSD License Usage 21 | ** Alternatively, you may use this file under the terms of the BSD license 22 | ** as follows: 23 | ** 24 | ** "Redistribution and use in source and binary forms, with or without 25 | ** modification, are permitted provided that the following conditions are 26 | ** met: 27 | ** * Redistributions of source code must retain the above copyright 28 | ** notice, this list of conditions and the following disclaimer. 29 | ** * Redistributions in binary form must reproduce the above copyright 30 | ** notice, this list of conditions and the following disclaimer in 31 | ** the documentation and/or other materials provided with the 32 | ** distribution. 33 | ** * Neither the name of The Qt Company Ltd nor the names of its 34 | ** contributors may be used to endorse or promote products derived 35 | ** from this software without specific prior written permission. 36 | ** 37 | ** 38 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 39 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 40 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 41 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 42 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 43 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 44 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 45 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 46 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 47 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 48 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 49 | ** 50 | ** $QT_END_LICENSE$ 51 | ** 52 | ****************************************************************************/ 53 | 54 | #include 55 | 56 | QSqlError initDb(const QString& path) 57 | { 58 | QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); 59 | db.setDatabaseName(path); 60 | if (!db.open()) 61 | { 62 | return db.lastError(); 63 | } 64 | return QSqlError(); 65 | } 66 | 67 | #endif // INITDB_H 68 | -------------------------------------------------------------------------------- /viewer/main.cpp: -------------------------------------------------------------------------------- 1 | #include "analysismain.h" 2 | #include 3 | #include "autoanalysis.h" 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | 9 | QApplication a(argc, argv); 10 | 11 | QApplication::setApplicationName("PerfMemPlus Viewer"); 12 | QApplication::setApplicationVersion("1.0"); 13 | QCommandLineParser parser; 14 | parser.setApplicationDescription("View the results of a PerfMemPlus profiling session"); 15 | parser.addHelpOption(); 16 | parser.addPositionalArgument("dbPath","Path to database file"); 17 | QCommandLineOption falseSharingAnalysisOpt("falseSharing","Runs the false sharing detection and prints the results without showing the GUI"); 18 | parser.addOption(falseSharingAnalysisOpt); 19 | QCommandLineOption dramContetionAnalysisOpt("dramContention","Runs the DRAM contention detection and prints the results without showing the GUI"); 20 | parser.addOption(dramContetionAnalysisOpt); 21 | QCommandLineOption configPathOpt("config","path to the configuration file","file"); 22 | parser.addOption(configPathOpt); 23 | parser.process(a); 24 | AnalysisMain w; 25 | 26 | auto arguments = parser.positionalArguments(); 27 | QString dbPath = ""; 28 | auto configPath = parser.value(configPathOpt); 29 | auto headless = parser.isSet((falseSharingAnalysisOpt)) | parser.isSet((dramContetionAnalysisOpt)); 30 | if(arguments.size() > 0) 31 | { 32 | dbPath = arguments.first(); 33 | w.setDbPath(dbPath,headless); 34 | } 35 | 36 | if(headless && dbPath == "") 37 | { 38 | qDebug() << "No input file set in automatic mode"; 39 | a.exit(1); 40 | } 41 | if(parser.isSet(falseSharingAnalysisOpt)) 42 | { 43 | AutoAnalysis aa; 44 | if(configPath!="") 45 | { 46 | aa.loadSettings(configPath); 47 | } 48 | auto results = aa.runFalseSharingDetection(); 49 | QTextStream out(stdout); 50 | out << "function,allocationId,intraObjectTrueSharing,interObjectTrueSharing,intraObjectFalseSharing,interObjectFalseSharing,hitmCount\n"; 51 | for(auto r : results) 52 | { 53 | if(r.falseSharing == true || r.trueSharing == true) 54 | { 55 | auto calcDiff = [](const QPair& p){return llabs(p.first.time - p.second.time);}; 56 | out << r.function << ',' << r.callPathId << ','; 57 | out << !r.intraObjectTrueSharing.empty() << ',' << !r.interObjectTrueSharing.empty() << ','; 58 | out << !r.intraObjectFalseSharing.empty() << ',' << !r.interObjectFalseSharing.empty() << ','; 59 | out << r.hitmCount << '\n'; 60 | /* 61 | for (auto d : r.interObjectTrueSharing) { 62 | out << "interObjectTrueSharing" << ',' << calcDiff(d) << '\n'; 63 | } 64 | for (auto d : r.interObjectFalseSharing) { 65 | out << "interObjectFalseSharing" << ',' << calcDiff(d) << '\n'; 66 | } 67 | for (auto d : r.intraObjectTrueSharing) { 68 | out << "intraObjectTrueSharing" << ',' << calcDiff(d) << '\n'; 69 | } 70 | for (auto d : r.intraObjectFalseSharing) { 71 | out << "intraObjectFalseSharing" << ',' << calcDiff(d) << ',' << d.first.ip << ',' << d.second.ip << ',' << d.first.adr << ',' << d.second.adr << ',' << d.first.allocationId << ',' << d.first.tid << '\n'; 72 | out << "intraObjectFalseSharing" << ',' << d.first.time << ',' << d.second.time << ',' << d.first.tid << ',' << d.second.tid << '\n'; 73 | } 74 | 75 | */ 76 | } 77 | } 78 | } 79 | if(parser.isSet(dramContetionAnalysisOpt)) 80 | { 81 | AutoAnalysis aa; 82 | if(configPath!="") 83 | { 84 | aa.loadSettings(configPath); 85 | } 86 | auto results = aa.runDramContentionDetection(); 87 | QTextStream out(stdout); 88 | out << "function,callPathId,"; 89 | out << "localDramProblem,localDramLowSampleCount,localDramParallelReaders,localDramLatency,"; 90 | out << "remoteDramProblem,remoteDramLowSampleCount,remoteDramParallelReaders,remoteDramLatency\n"; 91 | for(auto r : results) 92 | { 93 | if(r.dramBandwidth.problem || r.remoteDramBandwidth.problem) 94 | { 95 | out << r.function << ',' << r.callPathId << ','; 96 | out << r.dramBandwidth.problem << ',' << r.dramBandwidth.lowSampleCount << ',' << r.dramBandwidth.parallelReaders << ',' << r.dramBandwidth.latency << ','; 97 | out << r.remoteDramBandwidth.problem << ',' << r.remoteDramBandwidth.lowSampleCount << ',' << r.remoteDramBandwidth.parallelReaders << ',' << r.remoteDramBandwidth.latency << '\n'; 98 | } 99 | } 100 | } 101 | if(!headless) // GUI mode 102 | { 103 | w.show(); 104 | if(dbPath != "") 105 | { 106 | w.setWindowTitle("PerfMemPlus Viewer - " + dbPath); 107 | } 108 | return a.exec(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /viewer/memorycoherencywindow.cpp: -------------------------------------------------------------------------------- 1 | #include "memorycoherencywindow.h" 2 | 3 | #include "ui_memorycoherencywindow.h" 4 | #include "pdfwriter.h" 5 | #include "sqlutils.h" 6 | #include "guiutils.h" 7 | 8 | MemoryCoherencyWindow::~MemoryCoherencyWindow() 9 | { 10 | delete ui; 11 | } 12 | 13 | MemoryCoherencyWindow::MemoryCoherencyWindow(const QString& dbPath, QWidget* parent) : 14 | QDialog(parent), 15 | ui(new Ui::MemoryCoherencyWindow) 16 | { 17 | this->setWindowFlags(Qt::Window); 18 | ui->setupUi(this); 19 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 20 | model = new QSqlQueryModel(this); 21 | } 22 | 23 | void MemoryCoherencyWindow::setObjects(const QStringList& items) 24 | { 25 | this->items = items; 26 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 27 | model->setQuery("select \ 28 | allocation_id, \ 29 | coalesce(\"HITM count\",0) as \"HITM count\", \ 30 | \"average latency\", \ 31 | \"HITM average latency\" from \ 32 | (select allocation_id, \ 33 | avg(weight) as \"average latency\" \ 34 | from samples \ 35 | where allocation_id in (" + sqlItems + " ) \ 36 | group by allocation_id) a \ 37 | left outer join \ 38 | (select allocation_id, \ 39 | count (*) as \"HITM count\", \ 40 | avg(weight) as \"HITM average latency\" \ 41 | from samples \ 42 | where allocation_id in (" + sqlItems + " ) \ 43 | and memory_snoop = (select id from memory_snoop where name = \"Snoop Hit Modified\") \ 44 | group by allocation_id) b using(allocation_id) \ 45 | order by \"HITM count\" desc"); 46 | 47 | if (model->lastError().isValid()) 48 | { 49 | qDebug() << model->lastError(); 50 | } 51 | model->setHeaderData(0,Qt::Horizontal, "Allocation Id"); 52 | model->setHeaderData(1,Qt::Horizontal, "HITM Count"); 53 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 54 | model->setHeaderData(3,Qt::Horizontal, "HITM Average Latency"); 55 | ui->tableView->setModel(model); 56 | GuiUtils::resizeColumnsToContents(ui->tableView); 57 | ui->tableView->show(); 58 | } 59 | 60 | void MemoryCoherencyWindow::setCallpaths(const QStringList &items) 61 | { 62 | this->items = items; 63 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 64 | model->setQuery("select \ 65 | a.call_path_id, \ 66 | coalesce(\"HITM count\",0) as \"HITM count\", \ 67 | \"average latency\", \ 68 | \"HITM average latency\" from \ 69 | (select allocations.call_path_id, \ 70 | avg(weight) as \"average latency\" \ 71 | from samples inner join allocations on allocations.id = samples.allocation_id\ 72 | where allocations.call_path_id in (" + sqlItems + " ) and \ 73 | evsel_id = (select id from selected_events where name like 'cpu/mem-loads%') \ 74 | group by allocations.call_path_id) a \ 75 | left outer join \ 76 | (select allocations.call_path_id, \ 77 | count (*) as \"HITM count\", \ 78 | avg(weight) as \"HITM average latency\" \ 79 | from samples inner join allocations on allocations.id = samples.allocation_id\ 80 | where allocations.call_path_id in (" + sqlItems + " ) \ 81 | and memory_snoop = (select id from memory_snoop where name = \"Snoop Hit Modified\") and \ 82 | evsel_id = (select id from selected_events where name like 'cpu/mem-loads%') \ 83 | group by allocations.call_path_id) b on a.call_path_id = b.call_path_id \ 84 | order by \"HITM count\" desc"); 85 | 86 | if (model->lastError().isValid()) 87 | { 88 | qDebug() << model->lastError(); 89 | } 90 | model->setHeaderData(0,Qt::Horizontal, "Callpath Id"); 91 | model->setHeaderData(1,Qt::Horizontal, "HITM Count"); 92 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 93 | model->setHeaderData(3,Qt::Horizontal, "HITM Average Latency"); 94 | ui->tableView->setModel(model); 95 | GuiUtils::resizeColumnsToContents(ui->tableView); 96 | ui->tableView->show(); 97 | 98 | } 99 | 100 | void MemoryCoherencyWindow::setFunctions(QStringList items) 101 | { 102 | auto sqlItems = SqlUtils::makeSqlStringFunctions(items); 103 | 104 | model->setQuery("select \ 105 | (select name from symbols where id = symbol_id) as \"function\", \ 106 | coalesce(\"HITM count\",0) as \"HITM count\", \ 107 | \"average latency\", \ 108 | \"HITM average latency\" from \ 109 | (select symbol_id, \ 110 | avg(weight) as \"average latency\" \ 111 | from samples \ 112 | where symbol_id in (select id from symbols where name = " + sqlItems + " ) \ 113 | group by symbol_id) a \ 114 | left outer join \ 115 | (select symbol_id, \ 116 | count (*) as \"HITM count\", \ 117 | avg(weight) as \"HITM average latency\" \ 118 | from samples \ 119 | where symbol_id in (select id from symbols where name = " + sqlItems + " ) \ 120 | and memory_snoop = (select id from memory_snoop where name = \"Snoop Hit Modified\") \ 121 | group by symbol_id) b using(symbol_id) \ 122 | order by \"HITM count\" desc"); 123 | 124 | if (model->lastError().isValid()) 125 | { 126 | qDebug() << model->lastError(); 127 | } 128 | model->setHeaderData(0,Qt::Horizontal, "Function"); 129 | model->setHeaderData(1,Qt::Horizontal, "HITM Count"); 130 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 131 | model->setHeaderData(3,Qt::Horizontal, "HITM Average Latency"); 132 | ui->tableView->setModel(model); 133 | GuiUtils::resizeColumnsToContents(ui->tableView); 134 | ui->tableView->show(); 135 | } 136 | 137 | void MemoryCoherencyWindow::on_exportToPdfPushButton_clicked() 138 | { 139 | PdfWriter p; 140 | p.writeTableToPdf(ui->tableView); 141 | } 142 | -------------------------------------------------------------------------------- /viewer/memorycoherencywindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORYCOHERENCYWINDOW_H 2 | #define MEMORYCOHERENCYWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class MemoryCoherencyWindow; 9 | } 10 | 11 | class MemoryCoherencyWindow : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit MemoryCoherencyWindow(const QString& dbPath, QWidget *parent = 0); 17 | ~MemoryCoherencyWindow(); 18 | 19 | void setFunctions(QStringList items); 20 | void setObjects(const QStringList &items); 21 | void setCallpaths(const QStringList &items); 22 | 23 | private slots: 24 | void on_exportToPdfPushButton_clicked(); 25 | 26 | private: 27 | Ui::MemoryCoherencyWindow *ui; 28 | QSqlQueryModel* model; 29 | QStringList items; 30 | }; 31 | 32 | #endif // MEMORYCOHERENCYWINDOW_H 33 | -------------------------------------------------------------------------------- /viewer/memorycoherencywindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MemoryCoherencyWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 640 10 | 480 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 2 23 | 24 | 25 | 26 | 27 | 0 28 | 250 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | QAbstractItemView::NoEditTriggers 39 | 40 | 41 | QAbstractItemView::SelectRows 42 | 43 | 44 | true 45 | 46 | 47 | false 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Qt::Horizontal 57 | 58 | 59 | 60 | 40 61 | 20 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Export to PDF 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /viewer/memorylevelwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "memorylevelwindow.h" 2 | #include "ui_memorylevelwindow.h" 3 | #include "percentdelegate.h" 4 | #include "pdfwriter.h" 5 | #include "sqlutils.h" 6 | #include "guiutils.h" 7 | 8 | MemoryLevelWindow::MemoryLevelWindow(QWidget *parent) : 9 | QDialog(parent), 10 | ui(new Ui::MemoryLevelWindow) 11 | { 12 | this->setWindowFlags(Qt::Window); 13 | ui->setupUi(this); 14 | 15 | selectAll.prepare("select (select name from memory_levels where id = memory_level) as lvl, count (*) as \"count\", \ 16 | avg(weight) as \"average latency\", \ 17 | count(*) * 100.0 / (select count(*) from samples where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") ) as \"hit rate %\" \ 18 | from samples \ 19 | where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 20 | group by lvl order by count desc"); 21 | QSqlQueryModel* model = new QSqlQueryModel(this); 22 | model->setQuery(selectAll); 23 | model->setHeaderData(0,Qt::Horizontal, "Memory Level"); 24 | model->setHeaderData(1,Qt::Horizontal, "Count"); 25 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 26 | model->setHeaderData(3,Qt::Horizontal, "Hit Rate"); 27 | ui->memoryLevelTableView->setModel(model); 28 | ui->memoryLevelTableView->show(); 29 | 30 | } 31 | 32 | MemoryLevelWindow::MemoryLevelWindow(QStringList items, const MemoryLevelWindow::LimitType fo, const QString& dbPath, QWidget* parent) : 33 | QDialog(parent), 34 | ui(new Ui::MemoryLevelWindow) 35 | { 36 | this->setWindowFlags(Qt::Window); 37 | ui->setupUi(this); 38 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 39 | 40 | this->functionsObjects = fo; 41 | ui->selectedItemsListWidget->addItems(items); 42 | QSqlQueryModel* model = new QSqlQueryModel; 43 | if(fo == Functions) 44 | { 45 | this->functionItems = items; 46 | ui->label->setText("Selected\nfunctions"); 47 | auto sqlItems = SqlUtils::makeSqlStringFunctions(items); 48 | model->setQuery("select (select name from memory_levels where id = memory_level) as lvl, count (*) as \"count\", \ 49 | avg(weight) as \"average latency\", \ 50 | count(*) * 100.0 / (select count(*) from samples where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 51 | and symbol_id in (select id from symbols where name = " + sqlItems + " ) )as \"hit rate %\" \ 52 | from samples \ 53 | where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 54 | and symbol_id in (select id from symbols where name = " + sqlItems + " ) \ 55 | group by lvl order by memory_level asc"); 56 | printRemoteMemoryAccessFunctions(items); 57 | printRemoteMemoryAccessFunctionsInclCache(items); 58 | } 59 | else if(fo == Objects) 60 | { 61 | this->objectItems = items; 62 | ui->label->setText("Selected\nobjects"); 63 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 64 | auto inclNull = SqlUtils::makeIncludeNullStatement(items); 65 | model->setQuery("select (select name from memory_levels where id = memory_level) as lvl, count (*) as \"count\", \ 66 | avg(weight) as \"average latency\", \ 67 | count(*) * 100.0 / (select count(*) from samples where (allocation_id in (" + sqlItems + ") " + inclNull + ") and \ 68 | evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") ) as \"hit rate %\" from samples \ 69 | where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 70 | and (allocation_id in (" + sqlItems + ") " + inclNull + ") group by lvl order by memory_level asc"); 71 | 72 | printRemoteMemoryAccessObjects(items); 73 | printRemoteMemoryAccessObjectsInclCache(items); 74 | } 75 | if (model->lastError().isValid()) 76 | { 77 | qDebug() << model->lastError(); 78 | } 79 | model->setHeaderData(0,Qt::Horizontal, "Memory Level"); 80 | model->setHeaderData(1,Qt::Horizontal, "Count"); 81 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 82 | model->setHeaderData(3,Qt::Horizontal, "Hit Rate"); 83 | ui->memoryLevelTableView->setModel(model); 84 | ui->memoryLevelTableView->setItemDelegateForColumn(3,new PercentDelegate(ui->memoryLevelTableView)); 85 | GuiUtils::resizeColumnsToContents(ui->memoryLevelTableView); 86 | 87 | ui->memoryLevelTableView->show(); 88 | } 89 | 90 | MemoryLevelWindow::MemoryLevelWindow(QStringList functionItems, const QStringList& objectItems, const QString& dbPath, QWidget *parent) : 91 | QDialog(parent), 92 | ui(new Ui::MemoryLevelWindow) 93 | { 94 | this->setWindowFlags(Qt::Window); 95 | ui->setupUi(this); 96 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 97 | 98 | this->functionItems = functionItems; 99 | this->objectItems = objectItems; 100 | ui->selectedItemsListWidget->addItems(functionItems); 101 | ui->selectedItemsListWidget->addItems(objectItems); 102 | QSqlQueryModel* model = new QSqlQueryModel(this); 103 | 104 | ui->label->setText("Selected\nfunctions\nand\nobjects"); 105 | 106 | auto inclNull = SqlUtils::makeIncludeNullStatement(objectItems); 107 | auto sqlFunctionItems = SqlUtils::makeSqlStringFunctions(functionItems); 108 | auto sqlObjectItems = SqlUtils::makeSqlStringObjects(objectItems); 109 | 110 | model->setQuery("select (select name from memory_levels where id = memory_level) as lvl, count (*) as \"count\", \ 111 | avg(weight) as \"average latency\", \ 112 | count(*) * 100.0 / (select count(*) from samples where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 113 | and symbol_id in (select id from symbols where name = " + sqlFunctionItems + " ) and \ 114 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") )as \"hit rate %\" \ 115 | from samples \ 116 | where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 117 | and symbol_id in (select id from symbols where name = " + sqlFunctionItems + " ) and \ 118 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") \ 119 | group by lvl order by memory_level asc"); 120 | 121 | if (model->lastError().isValid()) 122 | { 123 | qDebug() << model->lastError(); 124 | } 125 | model->setHeaderData(0,Qt::Horizontal, "Memory Level"); 126 | model->setHeaderData(1,Qt::Horizontal, "Count"); 127 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 128 | model->setHeaderData(3,Qt::Horizontal, "Hit Rate"); 129 | ui->memoryLevelTableView->setModel(model); 130 | ui->memoryLevelTableView->setItemDelegateForColumn(3,new PercentDelegate(ui->memoryLevelTableView)); 131 | GuiUtils::resizeColumnsToContents(ui->memoryLevelTableView); 132 | 133 | printRemoteMemoryAccessFunctionsObjects(functionItems,objectItems); 134 | printRemoteMemoryAccessFunctionsObjectsInclCache(functionItems,objectItems); 135 | 136 | ui->memoryLevelTableView->show(); 137 | } 138 | 139 | MemoryLevelWindow::~MemoryLevelWindow() 140 | { 141 | delete ui; 142 | } 143 | 144 | void MemoryLevelWindow::printRemoteMemoryAccessFunctions(const QStringList& items) 145 | { 146 | auto sqlItems = SqlUtils::makeSqlStringFunctions(items); 147 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 148 | select ( \ 149 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 150 | memory_level = (select id from memory_levels where name like \"Local DRAM%\") and \ 151 | symbol_id in (select id from symbols where name = " + sqlItems + " \ 152 | ) ) as numLocal, \ 153 | ( \ 154 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 155 | memory_level = (select id from memory_levels where name like \"Remote DRAM%\") and \ 156 | symbol_id in (select id from symbols where name = " + sqlItems + " \ 157 | ) ) as numRemote )"); 158 | ui->remoteAccessNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 159 | } 160 | 161 | void MemoryLevelWindow::printRemoteMemoryAccessFunctionsInclCache(const QStringList& items) 162 | { 163 | auto sqlItems = SqlUtils::makeSqlStringFunctions(items); 164 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 165 | select ( \ 166 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 167 | memory_level in (select id from memory_levels where name like \"Local DRAM%\" or name like \"L3\") and \ 168 | symbol_id in (select id from symbols where name = " + sqlItems + " \ 169 | ) ) as numLocal, \ 170 | ( \ 171 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 172 | memory_level in (select id from memory_levels where name like \"Remote DRAM%\" or name like \"Remote Cache%\") and \ 173 | symbol_id in (select id from symbols where name = " + sqlItems + " \ 174 | ) ) as numRemote )"); 175 | ui->remoteAccessInclCacheNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 176 | } 177 | 178 | void MemoryLevelWindow::printRemoteMemoryAccessObjects(QStringList items) 179 | { 180 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 181 | auto inclNull = SqlUtils::makeIncludeNullStatement(items); 182 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 183 | select ( \ 184 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 185 | memory_level = (select id from memory_levels where name like \"Local DRAM%\") and \ 186 | (allocation_id in (" + sqlItems + ") " + inclNull + ") \ 187 | ) as numLocal, \ 188 | ( \ 189 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 190 | memory_level = (select id from memory_levels where name like \"Remote DRAM%\") and \ 191 | (allocation_id in (" + sqlItems + ") " + inclNull + ") \ 192 | ) as numRemote \ 193 | )"); 194 | ui->remoteAccessNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 195 | } 196 | 197 | 198 | void MemoryLevelWindow::printRemoteMemoryAccessObjectsInclCache(QStringList items) 199 | { 200 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 201 | auto inclNull = SqlUtils::makeIncludeNullStatement(items); 202 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 203 | select ( \ 204 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 205 | memory_level in (select id from memory_levels where name like \"Local DRAM%\" or name like \"L3\") and \ 206 | (allocation_id in (" + sqlItems + ") " + inclNull + ") \ 207 | ) as numLocal, \ 208 | ( \ 209 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 210 | memory_level in (select id from memory_levels where name like \"Remote DRAM%\" or name like \"Remote Cache%\") and \ 211 | (allocation_id in (" + sqlItems + ") " + inclNull + ") \ 212 | ) as numRemote \ 213 | )"); 214 | ui->remoteAccessInclCacheNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 215 | } 216 | 217 | void MemoryLevelWindow::printRemoteMemoryAccessFunctionsObjects(const QStringList& functionItems, QStringList objectItems) 218 | { 219 | auto sqlObjectItems = SqlUtils::makeSqlStringObjects(objectItems); 220 | auto inclNull = SqlUtils::makeIncludeNullStatement(objectItems); 221 | auto sqlFunctionItems = SqlUtils::makeSqlStringFunctions(functionItems); 222 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 223 | select ( \ 224 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 225 | memory_level = (select id from memory_levels where name like \"Local DRAM%\") and \ 226 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") and \ 227 | symbol_id in (select id from symbols where name = " + sqlFunctionItems + ") \ 228 | ) as numLocal, \ 229 | ( \ 230 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 231 | memory_level = (select id from memory_levels where name like \"Remote DRAM%\") and \ 232 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") and \ 233 | symbol_id in (select id from symbols where name = " + sqlFunctionItems + ") \ 234 | ) as numRemote \ 235 | )"); 236 | ui->remoteAccessNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 237 | } 238 | 239 | void MemoryLevelWindow::printRemoteMemoryAccessFunctionsObjectsInclCache(const QStringList& functionItems, QStringList objectItems) 240 | { 241 | auto sqlObjectItems = SqlUtils::makeSqlStringObjects(objectItems); 242 | auto inclNull = SqlUtils::makeIncludeNullStatement(objectItems); 243 | auto sqlFunctionItems = SqlUtils::makeSqlStringFunctions(functionItems); 244 | auto result = executeSingleResultQuery("select 100.0 * numRemote / (numLocal + numRemote) as remote from ( \ 245 | select ( \ 246 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 247 | memory_level = (select id from memory_levels where name like \"Local DRAM%\" or name like \"L3%\") and \ 248 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") and \ 249 | symbol_id in (select id from symbols where name = " + sqlFunctionItems + ") \ 250 | ) as numLocal, \ 251 | ( \ 252 | select count(*) from samples where evsel_id = (select id from selected_events where name like \"%load%\") and \ 253 | memory_level = (select id from memory_levels where name like \"Remote DRAM%\" or name like \"Remote Cache%\") and \ 254 | (allocation_id in (" + sqlObjectItems + ") " + inclNull + ") and \ 255 | symbol_id in (select id from symbols where name = " + sqlFunctionItems + ") \ 256 | ) as numRemote \ 257 | )"); 258 | ui->remoteAccessInclCacheNumberLabel->setText(QString::number(result.toDouble(),'f',2) + "%"); 259 | } 260 | 261 | QVariant MemoryLevelWindow::executeSingleResultQuery(const QString& query) const 262 | { 263 | QSqlQuery q(query); 264 | q.exec(); 265 | if(q.lastError().isValid()) 266 | { 267 | qDebug() << query << q.lastError(); 268 | } 269 | if(q.next()) 270 | { 271 | auto result = q.value(0); 272 | if(!result.isNull()) 273 | { 274 | return result; 275 | } 276 | } 277 | return QVariant((float)0); 278 | } 279 | 280 | void MemoryLevelWindow::on_exportToPdfPushButton_clicked() 281 | { 282 | PdfWriter p; 283 | p.writeTableToPdf(ui->memoryLevelTableView); 284 | } 285 | 286 | 287 | -------------------------------------------------------------------------------- /viewer/memorylevelwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORYLEVELWINDOW_H 2 | #define MEMORYLEVELWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class MemoryLevelWindow; 9 | } 10 | 11 | class MemoryLevelWindow : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | enum LimitType 17 | { 18 | Functions, 19 | Objects 20 | }; 21 | explicit MemoryLevelWindow(QWidget *parent = 0); 22 | explicit MemoryLevelWindow(QStringList items, const LimitType fo, const QString& dbPath, QWidget *parent = 0 ); 23 | explicit MemoryLevelWindow(QStringList functionItems, const QStringList& objectItems, const QString& dbPath, QWidget *parent = 0 ); 24 | ~MemoryLevelWindow(); 25 | 26 | private slots: 27 | void on_exportToPdfPushButton_clicked(); 28 | 29 | private: 30 | Ui::MemoryLevelWindow *ui; 31 | QSqlQuery selectAll; 32 | QSqlQuery selectLimitFunction; 33 | QSqlQuery selectLimitObject; 34 | QStringList functionItems; 35 | QStringList objectItems; 36 | LimitType functionsObjects; 37 | 38 | void printRemoteMemoryAccessFunctions(const QStringList &items); 39 | void printRemoteMemoryAccessFunctionsInclCache(const QStringList &items); 40 | void printRemoteMemoryAccessObjects(QStringList items); 41 | void printRemoteMemoryAccessObjectsInclCache(QStringList items); 42 | void printRemoteMemoryAccessFunctionsObjects(const QStringList &functionItems, QStringList objectItems); 43 | void printRemoteMemoryAccessFunctionsObjectsInclCache(const QStringList &functionItems, QStringList objectItems); 44 | QVariant executeSingleResultQuery(const QString &query) const; 45 | }; 46 | 47 | #endif // MEMORYLEVELWINDOW_H 48 | -------------------------------------------------------------------------------- /viewer/memorylevelwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MemoryLevelWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 578 10 | 411 11 | 12 | 13 | 14 | Cache and Memory 15 | 16 | 17 | 18 | 19 | 20 | Qt::Vertical 21 | 22 | 23 | 24 | 25 | 26 | 27 | Selected 28 | Items 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 0 37 | 1 38 | 39 | 40 | 41 | 42 | 0 43 | 0 44 | 45 | 46 | 47 | QAbstractItemView::NoEditTriggers 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 0 57 | 2 58 | 59 | 60 | 61 | 62 | 0 63 | 250 64 | 65 | 66 | 67 | 68 | 0 69 | 0 70 | 71 | 72 | 73 | QAbstractItemView::NoEditTriggers 74 | 75 | 76 | QAbstractItemView::SelectRows 77 | 78 | 79 | false 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 0 93 | 0 94 | 95 | 96 | 97 | <html><head/><body><p>Remote Memory Access (DRAM only):</p></body></html> 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 0 106 | 0 107 | 108 | 109 | 110 | 0 % 111 | 112 | 113 | 114 | 115 | 116 | 117 | Remote Memory Access (Including LLC): 118 | 119 | 120 | 121 | 122 | 123 | 124 | 0 % 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Export to PDF 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /viewer/objectaccessedbyfunctionwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "objectaccessedbyfunctionwindow.h" 2 | #include "ui_objectaccessedbyfunctionwindow.h" 3 | #include "percentdelegate.h" 4 | #include "allocationcallpathresolver.h" 5 | #include "memorylevelwindow.h" 6 | #include "pdfwriter.h" 7 | #include "sqlutils.h" 8 | #include "guiutils.h" 9 | 10 | ObjectAccessedByFunctionWindow::ObjectAccessedByFunctionWindow(QStringList items, const QString& dbPath, QWidget *parent) : 11 | QDialog(parent), 12 | ui(new Ui::ObjectAccessedByFunctionWindow) 13 | { 14 | this->setWindowFlags(Qt::Window); 15 | ui->setupUi(this); 16 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 17 | this->dbPath = dbPath; 18 | 19 | this->items = items; 20 | ui->selectedItemsListWidget->addItems(items); 21 | model = new QSqlQueryModel(this); 22 | ui->label->setText("Selected\nfunctions"); 23 | auto sqlItems = SqlUtils::makeSqlStringFunctions(items); 24 | 25 | model->setQuery("select allocation_id, 100 * count (*) / \ 26 | (select count(*) from samples where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 27 | and symbol_id in (select id from symbols where name = " + sqlItems + " ) ), \ 28 | avg(weight) as \"average latency\", \ 29 | 100 * sum(weight) / (select sum(weight) from samples where evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 30 | and symbol_id in (select id from symbols where name = " + sqlItems + " ) ) \ 31 | from samples where \ 32 | evsel_id = (select id from selected_events where name like \"cpu/mem-loads%\") \ 33 | and symbol_id in (select id from symbols where name = " + sqlItems + " ) \ 34 | group by allocation_id"); 35 | if (model->lastError().isValid()) 36 | { 37 | qDebug() << model->lastError(); 38 | return; 39 | } 40 | model->setHeaderData(0,Qt::Horizontal, "Object"); 41 | model->setHeaderData(1,Qt::Horizontal, "Samples %"); 42 | model->setHeaderData(2,Qt::Horizontal, "Average Latency"); 43 | model->setHeaderData(3,Qt::Horizontal, "Latency %"); 44 | 45 | QSortFilterProxyModel* m=new QSortFilterProxyModel(this); 46 | m->setDynamicSortFilter(true); 47 | m->setSourceModel(model); 48 | ui->objectsTableView->setModel(m); 49 | 50 | ui->objectsTableView->setItemDelegateForColumn(3,new PercentDelegate(this)); 51 | ui->objectsTableView->setItemDelegateForColumn(1,new PercentDelegate(this)); 52 | GuiUtils::resizeColumnsToContents(ui->objectsTableView); 53 | ui->objectsTableView->show(); 54 | connect(ui->objectsTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection ,QItemSelection)), 55 | this, SLOT(objectsTableRowChanged())); 56 | } 57 | 58 | 59 | ObjectAccessedByFunctionWindow::~ObjectAccessedByFunctionWindow() 60 | { 61 | delete ui; 62 | } 63 | 64 | void ObjectAccessedByFunctionWindow::objectsTableRowChanged() 65 | { 66 | auto selectedRows = ui->objectsTableView->selectionModel()->selectedRows(0); 67 | if(selectedRows.count() == 1) 68 | { 69 | auto id = selectedRows.first().data(Qt::DisplayRole).toInt(); 70 | AllocationCallPathResolver acpr; 71 | ui->callPathTreeWidget->clear(); 72 | acpr.writeAllocationCallpath(id,ui->callPathTreeWidget); 73 | GuiUtils::resizeColumnsToContents(ui->callPathTreeWidget); 74 | } 75 | else 76 | { 77 | ui->callPathTreeWidget->clear(); 78 | } 79 | } 80 | 81 | void ObjectAccessedByFunctionWindow::on_showCacheUsagePushButton_clicked() 82 | { 83 | auto indexList = ui->objectsTableView->selectionModel()->selectedRows(0); 84 | if(indexList.empty() == false) 85 | { 86 | QStringList sl; 87 | for(auto item : indexList) 88 | { 89 | sl.push_back(item.data(Qt::DisplayRole).toString()); 90 | } 91 | MemoryLevelWindow* mlw = new MemoryLevelWindow(items,sl,dbPath,this); 92 | mlw->show(); 93 | } 94 | } 95 | 96 | void ObjectAccessedByFunctionWindow::on_exportToPdfPushButton_clicked() 97 | { 98 | PdfWriter p(dbPath); 99 | p.writeTableToPdf(ui->objectsTableView); 100 | } 101 | -------------------------------------------------------------------------------- /viewer/objectaccessedbyfunctionwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECTACCESSEDBYFUNCTIONWINDOW_H 2 | #define OBJECTACCESSEDBYFUNCTIONWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class ObjectAccessedByFunctionWindow; 9 | } 10 | 11 | class ObjectAccessedByFunctionWindow : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit ObjectAccessedByFunctionWindow(QWidget *parent = 0); 17 | explicit ObjectAccessedByFunctionWindow(QStringList items, const QString &dbPath, QWidget *parent = 0); 18 | ~ObjectAccessedByFunctionWindow(); 19 | 20 | private slots: 21 | void objectsTableRowChanged(); 22 | 23 | void on_showCacheUsagePushButton_clicked(); 24 | 25 | void on_exportToPdfPushButton_clicked(); 26 | 27 | private: 28 | Ui::ObjectAccessedByFunctionWindow *ui; 29 | QStringList items; 30 | QString dbPath; 31 | QSqlQueryModel* model; 32 | template 33 | void resizeColumnsToContents(T *widget); 34 | }; 35 | 36 | #endif // OBJECTACCESSEDBYFUNCTIONWINDOW_H 37 | -------------------------------------------------------------------------------- /viewer/objectaccessedbyfunctionwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ObjectAccessedByFunctionWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 528 10 | 772 11 | 12 | 13 | 14 | Objects Accessed By Functions 15 | 16 | 17 | 18 | 19 | 20 | Qt::Vertical 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 16777215 29 | 100 30 | 31 | 32 | 33 | TextLabel 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | QAbstractItemView::NoEditTriggers 45 | 46 | 47 | QAbstractItemView::SelectRows 48 | 49 | 50 | true 51 | 52 | 53 | false 54 | 55 | 56 | 57 | 58 | Qt::ScrollBarAsNeeded 59 | 60 | 61 | Qt::ScrollBarAlwaysOn 62 | 63 | 64 | QAbstractItemView::NoEditTriggers 65 | 66 | 67 | 0 68 | 69 | 70 | false 71 | 72 | 73 | false 74 | 75 | 76 | 3 77 | 78 | 79 | 80 | DSO 81 | 82 | 83 | 84 | 85 | Function 86 | 87 | 88 | 89 | 90 | File and Line 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Qt::Horizontal 102 | 103 | 104 | 105 | 40 106 | 20 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Export to PDF 115 | 116 | 117 | 118 | 119 | 120 | 121 | Show Cache Usage 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /viewer/pdfwriter.cpp: -------------------------------------------------------------------------------- 1 | #include "pdfwriter.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | PdfWriter::PdfWriter(const QString& dbPath) 11 | { 12 | this->dbPath = dbPath; 13 | fd = new QFileDialog(); 14 | } 15 | 16 | PdfWriter::~PdfWriter() 17 | { 18 | delete fd; 19 | } 20 | 21 | void PdfWriter::writeTableToPdf(QTableView* view, const QString& path) 22 | { 23 | QTableView tmpView; 24 | tmpView.setModel(view->model()); 25 | for(int i = 0; i < view->model()->columnCount(); i++) 26 | { 27 | tmpView.setItemDelegateForColumn(i,view->itemDelegateForColumn(i)); 28 | } 29 | tmpView.resizeColumnsToContents(); 30 | tmpView.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 31 | tmpView.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 32 | tmpView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 33 | tmpView.setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); 34 | tmpView.verticalHeader()->hide(); 35 | 36 | for(int i = 0; i < tmpView.model()->rowCount(); i++) 37 | { 38 | tmpView.setRowHidden(i,true); 39 | tmpView.setColumnWidth(i,view->columnWidth(i)); 40 | } 41 | for(auto row : view->selectionModel()->selectedRows()) 42 | { 43 | tmpView.setRowHidden(row.row(),false); 44 | } 45 | writeWidgetToPdf(&tmpView,path); 46 | } 47 | 48 | void PdfWriter::writeWidgetToPdf(QWidget* w, const QString& path) 49 | { 50 | QPicture pic; 51 | QPainter painter(&pic); 52 | w->render(&painter); 53 | painter.end(); 54 | QPdfWriter pdfWriter(path); 55 | QPageLayout layout; 56 | layout.setOrientation(QPageLayout::Landscape); 57 | QPageSize ps(QSize(pic.heightMM()+1,pic.widthMM()+1),QPageSize::Millimeter,"",QPageSize::ExactMatch); 58 | layout.setPageSize(ps); 59 | pdfWriter.setPageLayout(layout); 60 | painter.begin(&pdfWriter); 61 | painter.drawPicture(QPointF(0,0),pic); 62 | painter.end(); 63 | } 64 | 65 | void PdfWriter::setupSavePdfFileDialog() 66 | { 67 | fd->setWindowTitle("Select pdf output file"); 68 | QFileInfo fi(dbPath); 69 | fd->setDirectory(fi.absoluteDir()); 70 | fd->setAcceptMode(QFileDialog::AcceptSave); 71 | fd->setNameFilter("PDF (*.pdf)"); 72 | } 73 | 74 | void PdfWriter::writeWidgetToPdf(QWidget* w) 75 | { 76 | setupSavePdfFileDialog(); 77 | if(fd->exec()) 78 | { 79 | auto path = fd->selectedFiles().first(); 80 | writeWidgetToPdf(w,path); 81 | } 82 | } 83 | 84 | void PdfWriter::writeTableToPdf(QTableView* view) 85 | { 86 | setupSavePdfFileDialog(); 87 | if(fd->exec()) 88 | { 89 | auto path = fd->selectedFiles().first(); 90 | writeTableToPdf(view,path); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /viewer/pdfwriter.h: -------------------------------------------------------------------------------- 1 | #ifndef PDFWRITER_H 2 | #define PDFWRITER_H 3 | 4 | #include 5 | 6 | class QFileDialog; 7 | class QWidget; 8 | class QTableView; 9 | 10 | class PdfWriter 11 | { 12 | public: 13 | PdfWriter(const QString &dbPath = ""); 14 | ~PdfWriter(); 15 | void writeTableToPdf(QTableView *view, const QString &path); 16 | void writeTableToPdf(QTableView *view); 17 | void writeWidgetToPdf(QWidget *w, const QString &path); 18 | void writeWidgetToPdf(QWidget *w); 19 | 20 | private: 21 | QFileDialog* fd; 22 | QString dbPath; 23 | void setupSavePdfFileDialog(); 24 | }; 25 | 26 | #endif // PDFWRITER_H 27 | -------------------------------------------------------------------------------- /viewer/percentdelegate.cpp: -------------------------------------------------------------------------------- 1 | #include "percentdelegate.h" 2 | #include 3 | #include 4 | #include 5 | 6 | PercentDelegate::PercentDelegate(QObject *parent) : QStyledItemDelegate(parent) 7 | { 8 | } 9 | 10 | float PercentDelegate::relativeCost(const QModelIndex &index) const 11 | { 12 | bool ok = false; 13 | float cost = index.data(Qt::DisplayRole).toFloat(&ok); 14 | cost = cost / 100; 15 | return ok ? cost : 0; 16 | } 17 | 18 | QString PercentDelegate::displayText(const QModelIndex &index, const QLocale &locale) const 19 | { 20 | if(index.data().canConvert()) 21 | { 22 | return QString::number(index.data().toDouble(),'f',2); 23 | } 24 | return index.data().toString(); 25 | } 26 | 27 | void PercentDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 28 | { 29 | QStyleOptionViewItem opt(option); 30 | initStyleOption(&opt, index); 31 | 32 | QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 33 | 34 | // Draw controls, but no text. 35 | opt.text.clear(); 36 | style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); 37 | 38 | painter->save(); 39 | 40 | // Draw bar. 41 | float ratio = qBound(0.0f, relativeCost(index), 1.0f); 42 | QRect barRect = opt.rect; 43 | barRect.setWidth(static_cast(std::round(opt.rect.width() * ratio))); 44 | painter->setPen(Qt::NoPen); 45 | painter->setBrush(QBrush(QColor::fromRgba(0xf0fff080),Qt::SolidPattern)); 46 | painter->drawRect(barRect); 47 | 48 | // Draw text. 49 | QLocale loc = opt.locale; 50 | loc.setNumberOptions(0); 51 | const QString text = displayText(index, loc); 52 | const QBrush &textBrush = (option.state & QStyle::State_Selected ? opt.palette.highlightedText() : opt.palette.text()); 53 | painter->setBrush(Qt::NoBrush); 54 | painter->setPen(textBrush.color()); 55 | painter->drawText(opt.rect, Qt::AlignVCenter | Qt::AlignLeft, text); 56 | 57 | painter->restore(); 58 | } 59 | 60 | QSize PercentDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 61 | { 62 | QStyleOptionViewItem opt(option); 63 | initStyleOption(&opt, index); 64 | 65 | const QString text = displayText(index, opt.locale); 66 | const QSize size = QSize(option.fontMetrics.width(text), 67 | option.fontMetrics.height()); 68 | return size; 69 | } 70 | -------------------------------------------------------------------------------- /viewer/percentdelegate.h: -------------------------------------------------------------------------------- 1 | #ifndef PERCENTDELEGATE_H 2 | #define PERCENTDELEGATE_H 3 | 4 | #include 5 | 6 | class PercentDelegate : public QStyledItemDelegate 7 | { 8 | public: 9 | PercentDelegate(QObject* parent = 0); 10 | 11 | virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; 12 | virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; 13 | 14 | private: 15 | float relativeCost(const QModelIndex &index) const; 16 | QString displayText(const QModelIndex &index, const QLocale &locale) const; 17 | }; 18 | 19 | #endif // PERCENTDELEGATE_H 20 | -------------------------------------------------------------------------------- /viewer/runHeadless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | QT_QPA_PLATFORM='offscreen' ./viewer 3 | 4 | -------------------------------------------------------------------------------- /viewer/sqlutils.cpp: -------------------------------------------------------------------------------- 1 | #include "sqlutils.h" 2 | #include 3 | 4 | QStringList SqlUtils::quoteStrings(const QStringList &list) 5 | { 6 | QStringList newList(list); 7 | for(QString& item : newList) 8 | { 9 | item.prepend("\""); 10 | item.append("\""); 11 | } 12 | return newList; 13 | } 14 | 15 | QString SqlUtils::makeSqlStringObjects(const QStringList &list) 16 | { 17 | return list.join(", "); 18 | } 19 | 20 | QString SqlUtils::makeSqlStringFunctions(const QStringList &list) 21 | { 22 | auto quotedList = quoteStrings(list); 23 | return quotedList.join(" or name = "); 24 | } 25 | 26 | QString SqlUtils::makeSqlStringFunction(const QString &functionString) 27 | { 28 | auto functionStringCpy(functionString); 29 | functionStringCpy.prepend("\""); 30 | functionStringCpy.append("\""); 31 | return functionStringCpy; 32 | } 33 | 34 | QString SqlUtils::makeIncludeNullStatement(const QStringList& items) 35 | { 36 | QString nullClause = ""; 37 | if(items.contains("")) 38 | { 39 | nullClause = "or allocation_id is NULL"; 40 | } 41 | return nullClause; 42 | } 43 | 44 | QSqlDatabase SqlUtils::getThreadDbCon() 45 | { 46 | auto defaultDb = QSqlDatabase::database("QSLITE"); 47 | auto defaultName = defaultDb.databaseName(); 48 | auto name = defaultName + QString::number((quint64)QThread::currentThread(), 16); 49 | if(QSqlDatabase::contains(name)) 50 | return QSqlDatabase::database(name); 51 | else { 52 | auto db = QSqlDatabase::addDatabase( "QSQLITE", name); 53 | db.open(); 54 | // open the database, setup tables, etc. 55 | return db; 56 | } 57 | } 58 | 59 | QVariant SqlUtils::executeSingleResultQuery(QSqlQuery& q) 60 | { 61 | q.exec(); 62 | if(q.next()) 63 | { 64 | return q.value(0); 65 | } 66 | else 67 | { 68 | qDebug() << q.lastQuery(); 69 | qDebug() << q.lastError().text(); 70 | return QVariant(); 71 | } 72 | } 73 | 74 | QString SqlUtils::executeSingleStringQuery(QSqlQuery& q) 75 | { 76 | auto r = executeSingleResultQuery(q); 77 | if(r.isNull()) 78 | { 79 | return ""; 80 | } 81 | else 82 | { 83 | return r.toString(); 84 | } 85 | } 86 | 87 | float SqlUtils::executeSingleFloatQuery(QSqlQuery& q) 88 | { 89 | auto r = executeSingleResultQuery(q); 90 | if(r.isNull()) 91 | { 92 | return 0; 93 | } 94 | else 95 | { 96 | return r.toFloat(); 97 | } 98 | } 99 | 100 | unsigned int SqlUtils::executeSingleUnsignedIntQuery(QSqlQuery& q) 101 | { 102 | auto r = executeSingleResultQuery(q); 103 | if(r.isNull()) 104 | { 105 | return 0; 106 | } 107 | else 108 | { 109 | return r.toUInt(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /viewer/sqlutils.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLUTILS_H 2 | #define SQLUTILS_H 3 | 4 | #include 5 | #include 6 | 7 | class SqlUtils 8 | { 9 | public: 10 | static QStringList quoteStrings(const QStringList& list); 11 | static QString makeSqlStringObjects(const QStringList& list); 12 | static QString makeSqlStringFunctions(const QStringList& list); 13 | static QString makeSqlStringFunction(const QString& functionString); 14 | static QString makeIncludeNullStatement(const QStringList &items); 15 | static QSqlDatabase getThreadDbCon(); 16 | static unsigned int executeSingleUnsignedIntQuery(QSqlQuery &q); 17 | static float executeSingleFloatQuery(QSqlQuery &q); 18 | static QString executeSingleStringQuery(QSqlQuery &q); 19 | static QVariant executeSingleResultQuery(QSqlQuery &q); 20 | }; 21 | 22 | #endif // SQLUTILS_H 23 | -------------------------------------------------------------------------------- /viewer/timelineaxiswidget.cpp: -------------------------------------------------------------------------------- 1 | #include "timelineaxiswidget.h" 2 | #include 3 | #include 4 | 5 | TimelineAxisWidget::TimelineAxisWidget(QWidget *parent) : AbstractTimelineWidget(parent) 6 | { 7 | 8 | } 9 | 10 | void TimelineAxisWidget::drawAxis(unsigned long long min, unsigned long long max) 11 | { 12 | //painter->save(); 13 | this->resize(this->size().width(),30); 14 | setAxisStyle(); 15 | const int lineOffsetTop = 5; 16 | drawStraightLine(lineOffsetTop); 17 | drawLeftmostAxisLabel(min,lineOffsetTop); 18 | drawRightmostAxisLabel(max,lineOffsetTop); 19 | drawTicks(min,max,lineOffsetTop); 20 | //painter->restore(); 21 | } 22 | 23 | void TimelineAxisWidget::drawTicks(const unsigned long long left, const unsigned long long right, const int yOffset) 24 | { 25 | const int textHeight = 20; 26 | const int textWidth = 200; 27 | 28 | unsigned long long half = (right-left) / 2; 29 | half = left + half; 30 | auto drawPos = scale(half); 31 | auto drawPosRight = scale(right); 32 | if(drawPosRight - drawPos > 200) 33 | { 34 | drawLineTick(drawPos,yOffset); 35 | QRect label(QPoint(drawPos-textWidth,yOffset*2),QPoint(drawPos+textWidth,yOffset*2+textHeight)); 36 | painter->drawText(label,Qt::AlignCenter,decodeTime(half)); 37 | drawTicks(left,half,yOffset); 38 | drawTicks(half,right,yOffset); 39 | } 40 | else if(drawPosRight - drawPos > 100) 41 | { 42 | drawLineTick(drawPos,yOffset); 43 | } 44 | } 45 | 46 | void TimelineAxisWidget::drawStraightLine(const int yOffset) 47 | { 48 | auto xEnd = this->size().width(); 49 | painter->drawLine(1,yOffset,xEnd,yOffset); 50 | } 51 | 52 | void TimelineAxisWidget::setAxisStyle() 53 | { 54 | QPen blackPen(QColor(Qt::black),3,Qt::SolidLine); 55 | QBrush blackBrush(QColor(Qt::black)); 56 | painter->setPen(blackPen); 57 | painter->setBrush(blackBrush); 58 | } 59 | 60 | void TimelineAxisWidget::drawLeftmostAxisLabel(const unsigned long long value, const int topOffset) 61 | { 62 | const int textHeight = 20; 63 | const int textWidth = 40; 64 | QRect leftLabel(QPoint(1,topOffset*2),QPoint(1+textWidth,topOffset*2+textHeight)); 65 | painter->drawText(leftLabel,QString::number(value)); 66 | drawLineTick(1,topOffset); 67 | } 68 | 69 | void TimelineAxisWidget::drawRightmostAxisLabel(const unsigned long long value, const int topOffset) 70 | { 71 | 72 | const int textHeight = 20; 73 | const int textWidth = 200; 74 | const int width = this->size().width(); 75 | QRect rightLabel(QPoint(width-textWidth,topOffset*2),QPoint(width,topOffset*2+textHeight)); 76 | painter->drawText(rightLabel,Qt::AlignRight,decodeTime(value)); 77 | drawLineTick(width-2,topOffset); 78 | } 79 | 80 | void TimelineAxisWidget::drawLineTick(const int xPos, const int yLinePos) 81 | { 82 | const int tickHeight = 10; 83 | const int tickYBegin = yLinePos - tickHeight/2; 84 | const int tickYEnd = tickYBegin + tickHeight; 85 | painter->drawLine(xPos,tickYBegin,xPos,tickYEnd); 86 | } 87 | 88 | void TimelineAxisWidget::paintEvent(QPaintEvent *event) 89 | { 90 | painter->begin(this); 91 | AbstractTimelineWidget::paintBackground(event); 92 | drawAxis(minValue,maxValue); 93 | painter->end(); 94 | } 95 | -------------------------------------------------------------------------------- /viewer/timelineaxiswidget.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMELINEAXISWIDGET_H 2 | #define TIMELINEAXISWIDGET_H 3 | 4 | #include 5 | 6 | class QPaintEvent; 7 | 8 | class TimelineAxisWidget : public AbstractTimelineWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit TimelineAxisWidget(QWidget *parent = nullptr); 13 | 14 | signals: 15 | 16 | public slots: 17 | virtual void paintEvent(QPaintEvent *event) override; 18 | 19 | private: 20 | void drawTicks(const unsigned long long left, const unsigned long long right, const int yOffset); 21 | void drawAxis(unsigned long long min, unsigned long long max); 22 | void drawStraightLine(const int yOffset); 23 | void setAxisStyle(); 24 | void drawLeftmostAxisLabel(const unsigned long long value, const int topOffset); 25 | void drawLineTick(const int xPos, const int yLinePos); 26 | void drawRightmostAxisLabel(const unsigned long long value, const int topOffset); 27 | 28 | }; 29 | 30 | #endif // TIMELINEAXISWIDGET_H 31 | -------------------------------------------------------------------------------- /viewer/timelinewidget.cpp: -------------------------------------------------------------------------------- 1 | #include "timelinewidget.h" 2 | #include 3 | #include 4 | 5 | TimelineWidget::TimelineWidget(QWidget *parent) : AbstractTimelineWidget(parent) 6 | { 7 | } 8 | 9 | void TimelineWidget::setPoints(const QList& points, const QColor& color) 10 | { 11 | this->allPoints = points; 12 | this->pointsColor = color; 13 | } 14 | 15 | void TimelineWidget::paintEvent(QPaintEvent *event) 16 | { 17 | painter->begin(this); 18 | paintBackground(event); 19 | drawPoints(allPoints); 20 | painter->end(); 21 | } 22 | 23 | void TimelineWidget::setPointsStyle(const QColor& c) 24 | { 25 | QPen pen(c,1,Qt::SolidLine); 26 | painter->setPen(pen); 27 | } 28 | 29 | void TimelineWidget::drawPoint(const int xPos) 30 | { 31 | painter->drawLine(xPos,1,xPos,this->size().height()); 32 | } 33 | 34 | void TimelineWidget::drawPoints(const QList &points) 35 | { 36 | static int lastDrawPos = -1; 37 | setPointsStyle(pointsColor); 38 | for(auto point : points) 39 | { 40 | unsigned long long drawPos = scale(point); 41 | if(lastDrawPos != (int) drawPos) 42 | { 43 | drawPoint((int)drawPos); 44 | lastDrawPos = (int) drawPos; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /viewer/timelinewidget.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMELINEWIDGET_H 2 | #define TIMELINEWIDGET_H 3 | 4 | #include 5 | #include "abstracttimelinewidget.h" 6 | 7 | class TimelineWidget : public AbstractTimelineWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit TimelineWidget(QWidget *parent = nullptr); 12 | void setPoints(const QList& points, const QColor& color); 13 | 14 | protected: 15 | virtual void paintEvent(QPaintEvent* event) override; 16 | 17 | signals: 18 | 19 | public slots: 20 | 21 | private: 22 | QList allPoints; 23 | QColor pointsColor; 24 | void drawPoints(const QList& points); 25 | void drawPoint(const int xPos); 26 | void setPointsStyle(const QColor &c); 27 | }; 28 | 29 | #endif // TIMELINEWIDGET_H 30 | -------------------------------------------------------------------------------- /viewer/timelinewindow.cpp: -------------------------------------------------------------------------------- 1 | #include "timelinewindow.h" 2 | #include "ui_timelinewindow.h" 3 | #include 4 | #include 5 | #include 6 | #include "pdfwriter.h" 7 | #include "sqlutils.h" 8 | #include 9 | #include 10 | 11 | 12 | TimelineWindow::TimelineWindow(QWidget *parent) : 13 | QDialog(parent), 14 | ui(new Ui::TimelineWindow) 15 | { 16 | this->setWindowFlags(Qt::Window); 17 | ui->setupUi(this); 18 | } 19 | 20 | TimelineWindow::TimelineWindow(const QStringList& items, const QString& dbPath, QWidget *parent) : 21 | QDialog(parent), 22 | ui(new Ui::TimelineWindow) 23 | { 24 | this->setWindowFlags(Qt::Window); 25 | ui->setupUi(this); 26 | this->setWindowTitle(this->windowTitle() + " - " + dbPath); 27 | 28 | this->allItems = items; 29 | 30 | auto min = getFirstSampleTime(); 31 | auto max = getLastSampleTime(); 32 | auto range = max - min; 33 | 34 | for(auto item : items) 35 | { 36 | auto rList = getReadList(QStringList(item)); 37 | trimList(rList,min); 38 | auto r = new TimelineWidget(this); 39 | r->setPoints(rList,QColor(Qt::green)); 40 | r->setMaxValue(range); 41 | ui->formLayout->addRow("Object " + item + " read:",r); 42 | 43 | auto wList = getWriteList(QStringList(item)); 44 | trimList(wList,min); 45 | auto w = new TimelineWidget(this); 46 | w->setPoints(wList,QColor(Qt::red)); 47 | w->setMaxValue(range); 48 | ui->formLayout->addRow("Object " + item + " write:",w); 49 | 50 | ui->formLayout->addRow(" ",(QWidget*)nullptr); // empty widget for spacing 51 | } 52 | 53 | time = new TimelineAxisWidget(this); 54 | time->setAxis(0,range); 55 | ui->formLayout->addRow("Time:",time); 56 | } 57 | 58 | void TimelineWindow::trimList(QList& list, const unsigned long long min) const 59 | { 60 | for(auto& item : list) 61 | { 62 | item = item - min; 63 | } 64 | } 65 | 66 | TimelineWindow::~TimelineWindow() 67 | { 68 | delete ui; 69 | } 70 | 71 | unsigned long long TimelineWindow::getFirstSampleTime() const 72 | { 73 | QSqlQuery q("select min(time) from samples where time != 0"); 74 | q.exec(); 75 | if(q.next()) 76 | { 77 | return q.value(0).toULongLong(); 78 | } 79 | else 80 | { 81 | throw(std::runtime_error("Can not find time of first sample")); 82 | } 83 | } 84 | 85 | unsigned long long TimelineWindow::getLastSampleTime() const 86 | { 87 | QSqlQuery q("select max(time) from samples"); 88 | q.exec(); 89 | if(q.next()) 90 | { 91 | return q.value(0).toULongLong(); 92 | } 93 | else 94 | { 95 | throw(std::runtime_error("Can not find time of first sample")); 96 | } 97 | } 98 | 99 | QList TimelineWindow::getReadList(const QStringList& items) const 100 | { 101 | auto nullClause = SqlUtils::makeIncludeNullStatement(items); 102 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 103 | QString q("select time from samples where memory_opcode = \ 104 | (select id from memory_opcodes where name = \"Load\") and \ 105 | (allocation_id in (" + sqlItems + ") " + nullClause + ") order by time asc"); 106 | return getList(q); 107 | } 108 | 109 | QList TimelineWindow::getWriteList(const QStringList& items) const 110 | { 111 | auto nullClause = SqlUtils::makeIncludeNullStatement(items); 112 | auto sqlItems = SqlUtils::makeSqlStringObjects(items); 113 | QString q("select time from samples where memory_opcode = \ 114 | (select id from memory_opcodes where name = \"Store\") and \ 115 | (allocation_id in (" + sqlItems + ") " + nullClause + ") order by time asc"); 116 | return getList(q); 117 | } 118 | 119 | QList TimelineWindow::getList(const QString &query) const 120 | { 121 | QSqlQuery q(query); 122 | q.exec(); 123 | QList readList; 124 | while(q.next()) 125 | { 126 | readList.push_back(q.value(0).toULongLong()); 127 | } 128 | return readList; 129 | } 130 | 131 | void TimelineWindow::on_exportToPdfPushButton_clicked() 132 | { 133 | PdfWriter p; 134 | p.writeWidgetToPdf(ui->widget); 135 | } 136 | -------------------------------------------------------------------------------- /viewer/timelinewindow.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMELINEVIEW_H 2 | #define TIMELINEVIEW_H 3 | #include 4 | 5 | namespace Ui { 6 | class TimelineWindow; 7 | } 8 | 9 | class TimelineAxisWidget; 10 | 11 | class TimelineWindow : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit TimelineWindow(QWidget *parent = 0); 17 | explicit TimelineWindow(const QStringList &items, const QString &path, QWidget *parent = 0); 18 | ~TimelineWindow(); 19 | 20 | private slots: 21 | void on_exportToPdfPushButton_clicked(); 22 | 23 | private: 24 | Ui::TimelineWindow *ui; 25 | TimelineAxisWidget* time; 26 | QStringList allItems; 27 | 28 | unsigned long long getFirstSampleTime() const; 29 | unsigned long long getLastSampleTime() const; 30 | unsigned long long getObjectLifetimeStart() const; 31 | unsigned long long getObjectLifetimeEnd() const; 32 | QList getReadList(const QStringList &items) const; 33 | QList getWriteList(const QStringList &items) const; 34 | QList getList(const QString& query) const; 35 | void trimList(QList &list, const unsigned long long min) const; 36 | }; 37 | 38 | #endif // TIMELINEVIEW_H 39 | -------------------------------------------------------------------------------- /viewer/timelinewindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TimelineWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 640 10 | 136 11 | 12 | 13 | 14 | Memory Access Timeline 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | QLayout::SetDefaultConstraint 24 | 25 | 26 | QFormLayout::AllNonFixedFieldsGrow 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Qt::Horizontal 39 | 40 | 41 | 42 | 40 43 | 20 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Export to PDF 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /viewer/treeitem.cpp: -------------------------------------------------------------------------------- 1 | #include "treeitem.h" 2 | 3 | #include 4 | 5 | #include "treeitem.h" 6 | 7 | TreeItem::TreeItem(const QList &data, TreeItem *parent) 8 | { 9 | m_parentItem = parent; 10 | m_itemData = data; 11 | } 12 | 13 | TreeItem::~TreeItem() 14 | { 15 | qDeleteAll(m_childItems); 16 | } 17 | 18 | void TreeItem::appendChild(TreeItem *item) 19 | { 20 | m_childItems.append(item); 21 | } 22 | 23 | TreeItem *TreeItem::child(int row) 24 | { 25 | return m_childItems.value(row); 26 | } 27 | 28 | int TreeItem::childCount() const 29 | { 30 | return m_childItems.count(); 31 | } 32 | 33 | int TreeItem::columnCount() const 34 | { 35 | return m_itemData.count(); 36 | } 37 | 38 | QVariant TreeItem::data(int column) const 39 | { 40 | return m_itemData.value(column); 41 | } 42 | 43 | void TreeItem::setData(int column, const QVariant &data) 44 | { 45 | m_itemData[column] = data; 46 | } 47 | 48 | TreeItem *TreeItem::parentItem() 49 | { 50 | return m_parentItem; 51 | } 52 | 53 | int TreeItem::row() const 54 | { 55 | if (m_parentItem) 56 | return m_parentItem->m_childItems.indexOf(const_cast(this)); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /viewer/treeitem.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEITEM_H 2 | #define TREEITEM_H 3 | 4 | #include 5 | 6 | class TreeItem 7 | { 8 | public: 9 | explicit TreeItem(const QList &data, TreeItem *parentItem = nullptr); 10 | ~TreeItem(); 11 | 12 | void appendChild(TreeItem *child); 13 | 14 | TreeItem *child(int row); 15 | int childCount() const; 16 | int columnCount() const; 17 | QVariant data(int column) const; 18 | void setData(int column, const QVariant& data); 19 | int row() const; 20 | TreeItem *parentItem(); 21 | 22 | private: 23 | QList m_childItems; 24 | QList m_itemData; 25 | TreeItem *m_parentItem; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /viewer/treemodel.cpp: -------------------------------------------------------------------------------- 1 | #include "treeitem.h" 2 | #include "treemodel.h" 3 | 4 | #include 5 | 6 | TreeModel::TreeModel(TreeItem* root, QObject *parent) 7 | : QAbstractItemModel(parent) 8 | { 9 | rootItem = root; 10 | } 11 | 12 | TreeModel::~TreeModel() 13 | { 14 | delete rootItem; 15 | } 16 | 17 | int TreeModel::columnCount(const QModelIndex &parent) const 18 | { 19 | if (parent.isValid()) 20 | return static_cast(parent.internalPointer())->columnCount(); 21 | else 22 | return rootItem->columnCount(); 23 | } 24 | 25 | QVariant TreeModel::data(const QModelIndex &index, int role) const 26 | { 27 | if (!index.isValid()) 28 | return QVariant(); 29 | 30 | if (role != Qt::DisplayRole) 31 | return QVariant(); 32 | 33 | TreeItem *item = static_cast(index.internalPointer()); 34 | 35 | return item->data(index.column()); 36 | } 37 | 38 | Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const 39 | { 40 | if (!index.isValid()) 41 | return 0; 42 | 43 | return QAbstractItemModel::flags(index); 44 | } 45 | 46 | QVariant TreeModel::headerData(int section, Qt::Orientation orientation, 47 | int role) const 48 | { 49 | if (orientation == Qt::Horizontal && role == Qt::DisplayRole) 50 | return rootItem->data(section); 51 | 52 | return QVariant(); 53 | } 54 | 55 | QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) 56 | const 57 | { 58 | if (!hasIndex(row, column, parent)) 59 | return QModelIndex(); 60 | 61 | TreeItem *parentItem; 62 | 63 | if (!parent.isValid()) 64 | parentItem = rootItem; 65 | else 66 | parentItem = static_cast(parent.internalPointer()); 67 | 68 | TreeItem *childItem = parentItem->child(row); 69 | if (childItem) 70 | return createIndex(row, column, childItem); 71 | else 72 | return QModelIndex(); 73 | } 74 | 75 | QModelIndex TreeModel::parent(const QModelIndex &index) const 76 | { 77 | if (!index.isValid()) 78 | return QModelIndex(); 79 | 80 | TreeItem *childItem = static_cast(index.internalPointer()); 81 | TreeItem *parentItem = childItem->parentItem(); 82 | 83 | if (parentItem == rootItem) 84 | return QModelIndex(); 85 | 86 | return createIndex(parentItem->row(), 0, parentItem); 87 | } 88 | 89 | int TreeModel::rowCount(const QModelIndex &parent) const 90 | { 91 | TreeItem *parentItem; 92 | if (parent.column() > 0) 93 | return 0; 94 | 95 | if (!parent.isValid()) 96 | parentItem = rootItem; 97 | else 98 | parentItem = static_cast(parent.internalPointer()); 99 | 100 | return parentItem->childCount(); 101 | } 102 | -------------------------------------------------------------------------------- /viewer/treemodel.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TREEMODEL_H 3 | #define TREEMODEL_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class TreeItem; 10 | 11 | class TreeModel : public QAbstractItemModel 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit TreeModel(TreeItem* data, QObject *parent = 0); 17 | ~TreeModel(); 18 | 19 | QVariant data(const QModelIndex &index, int role) const override; 20 | Qt::ItemFlags flags(const QModelIndex &index) const override; 21 | QVariant headerData(int section, Qt::Orientation orientation, 22 | int role = Qt::DisplayRole) const override; 23 | QModelIndex index(int row, int column, 24 | const QModelIndex &parent = QModelIndex()) const override; 25 | QModelIndex parent(const QModelIndex &index) const override; 26 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 27 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 28 | 29 | private: 30 | void setupModelData(const QStringList &lines, TreeItem *parent); 31 | 32 | TreeItem *rootItem; 33 | }; 34 | 35 | #endif // TREEMODEL_H 36 | -------------------------------------------------------------------------------- /viewer/viewer.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-11-13T13:54:33 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += sql core gui concurrent 8 | QT += charts 9 | 10 | greaterThan(QT_MAJOR_VERSION, 5): QT += widgets 11 | 12 | TARGET = viewer 13 | TEMPLATE = app 14 | CONFIG += c++14 15 | 16 | # The following define makes your compiler emit warnings if you use 17 | # any feature of Qt which has been marked as deprecated (the exact warnings 18 | # depend on your compiler). Please consult the documentation of the 19 | # deprecated API in order to know how to port your code away from it. 20 | DEFINES += QT_DEPRECATED_WARNINGS 21 | 22 | # You can also make your code fail to compile if you use deprecated APIs. 23 | # In order to do so, uncomment the following line. 24 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 25 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 26 | 27 | SOURCES += \ 28 | main.cpp \ 29 | analysismain.cpp \ 30 | memorylevelwindow.cpp \ 31 | percentdelegate.cpp \ 32 | objectaccessedbyfunctionwindow.cpp \ 33 | allocationcallpathresolver.cpp \ 34 | pdfwriter.cpp \ 35 | timelinewidget.cpp \ 36 | timelinewindow.cpp \ 37 | memorycoherencywindow.cpp \ 38 | graphwindow.cpp \ 39 | sqlutils.cpp \ 40 | guiutils.cpp \ 41 | timelineaxiswidget.cpp \ 42 | abstracttimelinewidget.cpp \ 43 | autoanalysis.cpp \ 44 | treeitem.cpp \ 45 | treemodel.cpp 46 | 47 | HEADERS += \ 48 | analysismain.h \ 49 | initdb.h \ 50 | memorylevelwindow.h \ 51 | percentdelegate.h \ 52 | objectaccessedbyfunctionwindow.h \ 53 | allocationcallpathresolver.h \ 54 | pdfwriter.h \ 55 | timelinewidget.h \ 56 | timelinewindow.h \ 57 | memorycoherencywindow.h \ 58 | graphwindow.h \ 59 | sqlutils.h \ 60 | guiutils.h \ 61 | timelineaxiswidget.h \ 62 | abstracttimelinewidget.h \ 63 | autoanalysis.h \ 64 | treeitem.h \ 65 | treemodel.h 66 | 67 | FORMS += \ 68 | analysismain.ui \ 69 | memorylevelwindow.ui \ 70 | objectaccessedbyfunctionwindow.ui \ 71 | timelinewindow.ui \ 72 | memorycoherencywindow.ui \ 73 | graphwindow.ui 74 | --------------------------------------------------------------------------------