summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaria Guseva/AI Tools Lab/Staff Engineer/삼성전자 <m.guseva@samsung.com>2018-06-01 12:31:28 +0300
committerGitHub Enterprise <noreply-CODE@samsung.com>2018-06-01 12:31:28 +0300
commit5a86237f7043e3420dd5666b6ecfe7f5c9245f8b (patch)
tree438e72b59f8e32605a04babf3c5e919b7ce1aa41
parentd69ffe2000e6f84f17190f0dc95d1ebf43fd1a1d (diff)
parent58b4a8063d91df42e037f2f3eb3ecb740486c06e (diff)
downloadheaptrack-5a86237f7043e3420dd5666b6ecfe7f5c9245f8b.tar.gz
heaptrack-5a86237f7043e3420dd5666b6ecfe7f5c9245f8b.tar.bz2
heaptrack-5a86237f7043e3420dd5666b6ecfe7f5c9245f8b.zip
Merge pull request #36 from dotnet/delivery
Delivery of Memory Profiler for Tizen VS Changes: 1. Tizen Memory Profiler GUI (heaptrack_gui) ported to MS Windows: - KDE Frameworks 5 libraries not used under Windows, "raw" Qt is used instead (except KChart - see below); - KChart library used for building charts replaced by QWT library under Windows; - code fixed to be able to compile it with MSVC; - Linux compatibility preserved - application's pro-file (qmake format) allows to choose different build options. 2. Some GUI improvements done: - context menu allowing to select different chart display options (show legend, etc.) added; - floating help window explaining how to zoom in / zoom out charts added; - exporting charts to files implemented; - application about dialog added. 3. Some Bugs fixed: - fixed stack overflow in case of too big flame graphs 4. Some improvements for build were done: - Windows GUI build instruction updated - copy required dll files to destination directory on build - Added packaging for ARM
-rw-r--r--CMakeLists.txt3
-rw-r--r--README.md318
-rw-r--r--WINDOWS_GUI_BUILD.md153
-rw-r--r--docs/TIZEN_HEAPTRACK_ORIGINAL_README.md227
-rw-r--r--docs/image/memory_profiler_gui_allocation_histogram.pngbin0 -> 37984 bytes
-rw-r--r--docs/image/memory_profiler_gui_allocations_graph.pngbin0 -> 90924 bytes
-rw-r--r--docs/image/memory_profiler_gui_flame_graph.pngbin0 -> 71271 bytes
-rw-r--r--docs/image/memory_profiler_gui_managed_heap.pngbin0 -> 48953 bytes
-rw-r--r--docs/image/memory_profiling_app_started.pngbin0 -> 55166 bytes
-rw-r--r--docs/image/memory_profiling_msvs_output.pngbin0 -> 78026 bytes
-rw-r--r--docs/image/run_memory_profiler.pngbin0 -> 50301 bytes
-rw-r--r--docs/image/run_memory_profiler_gui.pngbin0 -> 98187 bytes
-rw-r--r--docs/image/start_emulator.pngbin0 -> 13824 bytes
-rw-r--r--packaging/0001-Target-build43
-rw-r--r--packaging/heaptrack.manifest5
-rw-r--r--packaging/heaptrack.spec105
-rw-r--r--profiler/profiler/cross/x86/toolchain.cmake26
-rw-r--r--profiler/profiler/src/profiler.cpp18
-rw-r--r--screenshots/build_gui_from_qt_creator.pngbin0 -> 115854 bytes
-rw-r--r--screenshots/build_settings.pngbin0 -> 61378 bytes
-rw-r--r--src/ThreadWeaver.pro117
-rw-r--r--src/ThreadWeaver/threadweaver_export.h12
-rw-r--r--src/analyze/accumulatedtracedata.cpp40
-rw-r--r--src/analyze/gui/aboutdata.cpp45
-rw-r--r--src/analyze/gui/aboutdata.h24
-rw-r--r--src/analyze/gui/aboutdialog.cpp69
-rw-r--r--src/analyze/gui/aboutdialog.h22
-rw-r--r--src/analyze/gui/aboutdialog.ui90
-rw-r--r--src/analyze/gui/callercalleemodel.cpp32
-rw-r--r--src/analyze/gui/callercalleemodel.h6
-rw-r--r--src/analyze/gui/charthelpwindow.cpp53
-rw-r--r--src/analyze/gui/charthelpwindow.h19
-rw-r--r--src/analyze/gui/chartmodel.cpp128
-rw-r--r--src/analyze/gui/chartmodel.h14
-rw-r--r--src/analyze/gui/chartmodel2qwtseriesdata.cpp43
-rw-r--r--src/analyze/gui/chartmodel2qwtseriesdata.h29
-rw-r--r--src/analyze/gui/chartwidget.cpp207
-rw-r--r--src/analyze/gui/chartwidget.h55
-rw-r--r--src/analyze/gui/chartwidgetqwtplot.cpp433
-rw-r--r--src/analyze/gui/chartwidgetqwtplot.h108
-rw-r--r--src/analyze/gui/contextmenuqwt.cpp199
-rw-r--r--src/analyze/gui/contextmenuqwt.h41
-rw-r--r--src/analyze/gui/flamegraph.cpp207
-rw-r--r--src/analyze/gui/flamegraph.h11
-rw-r--r--src/analyze/gui/gui.cpp32
-rw-r--r--src/analyze/gui/gui.qrc5
-rw-r--r--src/analyze/gui/gui_config.h28
-rw-r--r--src/analyze/gui/histogrammodel.cpp53
-rw-r--r--src/analyze/gui/histogrammodel.h4
-rw-r--r--src/analyze/gui/histogramwidget.cpp133
-rw-r--r--src/analyze/gui/histogramwidget.h48
-rw-r--r--src/analyze/gui/histogramwidgetqwtplot.cpp368
-rw-r--r--src/analyze/gui/histogramwidgetqwtplot.h36
-rw-r--r--src/analyze/gui/icons/fileopen.pngbin0 -> 1771 bytes
-rw-r--r--src/analyze/gui/icons/if_diagram_v2-14_37134.icobin0 -> 30894 bytes
-rw-r--r--src/analyze/gui/locationdata.h6
-rw-r--r--src/analyze/gui/mainwindow.cpp297
-rw-r--r--src/analyze/gui/mainwindow.h20
-rw-r--r--src/analyze/gui/mainwindow.ui13
-rw-r--r--src/analyze/gui/mainwindow_noklib.ui912
-rw-r--r--src/analyze/gui/noklib.h91
-rw-r--r--src/analyze/gui/objecttreemodel.cpp10
-rw-r--r--src/analyze/gui/objecttreemodel.h5
-rw-r--r--src/analyze/gui/objecttreeproxy.cpp9
-rw-r--r--src/analyze/gui/objecttreeproxy.h17
-rw-r--r--src/analyze/gui/parser.cpp226
-rw-r--r--src/analyze/gui/parser.h3
-rw-r--r--src/analyze/gui/stacksmodel.cpp4
-rw-r--r--src/analyze/gui/topproxy.cpp2
-rw-r--r--src/analyze/gui/treemodel.cpp21
-rw-r--r--src/analyze/gui/treemodel.h4
-rw-r--r--src/analyze/gui/treeproxy.cpp16
-rw-r--r--src/analyze/gui/treeproxy.h18
-rw-r--r--src/analyze/gui/util.cpp169
-rw-r--r--src/analyze/gui/util.h17
-rw-r--r--src/heaptrack_gui.pro254
-rw-r--r--src/heaptrack_print.pro16
-rwxr-xr-xsrc/track/heaptrack.sh.cmake2
-rw-r--r--src/util/config.h36
-rw-r--r--src/util/config.h.cmake2
-rw-r--r--src/util/indices.h2
-rw-r--r--src/util/libunwind_config.h27
82 files changed, 5329 insertions, 479 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 73913f9..d02e780 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,7 +16,8 @@ endif()
set(HEAPTRACK_VERSION_MAJOR 1)
set(HEAPTRACK_VERSION_MINOR 1)
set(HEAPTRACK_VERSION_PATCH 0)
-set(HEAPTRACK_LIB_VERSION 1.0.0)
+set(HEAPTRACK_VERSION_SUFFIX 0.1)
+set(HEAPTRACK_LIB_VERSION 1.0.0-0.1)
set(HEAPTRACK_LIB_SOVERSION 1)
set(HEAPTRACK_FILE_FORMAT_VERSION 2)
diff --git a/README.md b/README.md
index 1e1e0bd..1ac5da5 100644
--- a/README.md
+++ b/README.md
@@ -1,227 +1,169 @@
-# Tizen .NET Memory Profiler
+# Tizen .NET Memory Profiler for Microsoft Visual Studio
-(Original KDE Heaptrack's README can be found [here](docs/HEAPTRACK_README.md))
+## Introduction
-## Brief contents of the Guide
+Tizen .NET Memory Profiler is based on an open-source memory profiler for C/C++ Linux applications named "Heaptrack". Heaptrack was created by Milian Wolff (see <http://milianw.de/tag/heaptrack> and <https://github.com/KDE/heaptrack>). Original Heaptrack's README can be found [here](docs/HEAPTRACK_README.md).
-### Building and launching profiler in Docker
+Later Samsung developers modified Heaptrack to support Samsung Tizen OS to enable profiling the memory consumption of managed .NET CoreCLR applications running under Tizen. The official name for this modification of heaptrack is Tizen .NET Memory Profiler. This tool can run under Linux only.
+Original README for Tizen .NET Memory Profiler can be found [here](docs/TIZEN_HEAPTRACK_ORIGINAL_README.md).
-Prerequisites:
-* [Tizen SDK](https://developer.tizen.org/development/tizen-studio/download) is required to run the profiler.
-* Your host machine should have Docker installed. For Ubuntu instructions, please see [the manual](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/).
+The next step in evolution of Tizen .NET memory profiler is, porting its GUI to Windows platform and then integration into Visual Studio Tools for Tizen extension.
-Please, keep in mind:
+The same as original heaptrack, Tizen .NET Memory Profiler consists of the following two major parts:
+ - a utility which collects memory profiling data and saves them to a file, this part of the Memory Profiler runs on the target side;
-> If you would like to use Docker as a non-root user, you should now consider
-> adding your user to the "docker" group with something like:
->
-> `sudo usermod -aG docker your-user`
+ - tools for analyzing the resulting data, the command-line one and the GUI-based one (originally named "heaptrack_gui"), these analyzing tools run on the host side.
-1. When building in Docker, the first thing you need to do is to create a `coreclr-devel` directory and put coreclr-devel rpms for the CoreCLR versions you use on your devices in this folder. You can find out which version of CoreCLR is installed on your device using `rpm -qa` command. Alternatively, you can let docker download the latest `coreclr-devel` package from download.tizen.org, or, if you are building CoreCLR yourself, point the build script to the compiled CoreCLR source folder. If you choose one of these two options, leave the `coreclr-devel` folder empty and proceed to step 2.
-2. Run
+The Tizen .NET Memory Profiler GUI provides different views on the collected data: text-based views (a summary, different lists and tables) and graphical ones (a so-called flame graph, several charts, and an allocation histogram).
+
+Currently Tizen .NET Memory profiler is adapted for usage with Visual Studio Tools for Tizen extension.
+
+Further in this document the Tizen .NET Memory profiler for MS Visual Studio is described in more details.
+
+## Prerequisites
+
+### Prerequisites for building
+
+Linux and Windows hosts will be needed in order to build all components of the Memory Profiler.
+
+#### Windows host
+To build the Memory profiler GUI, any Microsoft Windows 64-bit operating system starting from Windows 7 can be used.
+
+Additional software products and components must be installed on the Windows host. Please see [here](WINDOWS_GUI_BUILD.md) for the list of required additional software and instructions about how to obtain and install it.
+
+#### Linux host
+All build procedures were tested on the [Ubuntu 16.04](http://releases.ubuntu.com/16.04/)
+
+The GBS build system must be installed to the Linux system. For more info about GBS installation please refer [here](https://source.tizen.org/documentation/developer-guide/getting-started-guide/installing-development-tools)
+
+
+### Prerequisites for running
+- VS Tools for Tizen. A compartible version of VS Tools for Tizen must be used which has the user interface for launching the Memory profiler.
+- dotnet-launcher. A compartible version of dotnet-launcher must be used which has support for profiler launching.
+
+
+## Build
+
+The Tizen .NET Memory Profiler for Visual Studio consists of two components:
+1) target-specific part (running on target side);
+2) GUI (running on host side)
+
+
+### Building target-specific part
+The target specific part is built on Linux host with GBS built system installed.
+
+You should use compatible GBS config files to build the target-specific part for supported targets.
+
+By default GBS store resulted RPMs and SRPMs at
+`~/GBS-ROOT/local/repos/<distro>/<arch>` directories.
+
+In the following examples option `-c` is used to specify path to the GBS config file and `-P` option to select profile suitable for target.
+
+#### Build for ARM Tizen TM1 target
+
+```console
+~/heaptrack$ gbs -c ../gbs-config/gbs.conf build -P profile.arm.snapshot -A armv7l
```
-sudo ./launch.sh org.tizen.example.HelloWorld.Tizen /opt/usr/home/owner/apps_rw/HelloWorld.Tizen/bin/HelloWorld.Tizen.exe
+
+#### Build for x86 Tizen emulator
+
+```console
+~/heaptrack$ gbs -c ../gbs-config/gbs.conf build -P profile.emul32.snapshot -A i586
```
-to build the profiler and launch profiling of HelloWorld application. If you are running the profiler for the first time, you will be asked permission to move files around on the device in order to free up some disk space. You will also be asked to point the script to the location of "debuginfo" RPMs for the native libraries in which you want to see stack traces.
-### VM [[Details]](docs/DETAILED.md#download-and-unpack-vm-disk-image-from-this-page)
+### Building GUI
+
+The Memory profiler GUI is built on Windows, detailed instructions are provided [here](WINDOWS_GUI_BUILD.md).
+
+## Install procedure
+
+### Installing target-specific part
-1\. Download and unpack VM image, start it in VirtualBox and connect with Tizen device
+After successfull build on the Linux host, resulting target-specific RPMs will be located in the corresponding directories
-To unpack it on Linux, please, download the ubuntu-17.04.vdi.tar.bz2-* files to separate directory and run the following command in the directory:
+ `~/GBS-ROOT/local/repos/<distro>/<arch>/RPMS`
+The resulting RPMs shall be copied to the Windows host into the following specific directory:
+
+`<Tizen SDK installation dir>\platforms\tizen-4.0\common\on-demand`
+
+The RPMs will be auhomatically installed by on demand installation procedure.
+
+Alternatively manual installation procedure can be also used:
```
-cat ubuntu-17.04.vdi.tar.bz2-* | tar xjvf -
+> sdb push RPMS /tmp
+> sdb root on
+> sdb shell
+sh-3.2# export TERM=windows
+sh-3.2# cd /tmp
+sh-3.2# mount -o rw,remount /
+sh-3.2# rpm --force -i <rpm_packet_name>.rpm
```
+### Installing GUI
+After successfull build on the Windows host, a number of .dll and .exe files will be available in the build destination directory (see [here](WINDOWS_GUI_BUILD.md) for details).
+These files shall be copied to the following directory:
+`<Tizen SDK installation dir>\tools\memory-profiler`
-For unpacking on Windows, please see [the details](docs/DETAILED.md#download-and-unpack-vm-disk-image-from-this-page).
+## Running
+Basic instructions for Running the Memory Profiler under Visual Studio are provided below. For more detailed guide on the Memory profiler please refer to the [Original README](docs/TIZEN_HEAPTRACK_ORIGINAL_README.md)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-aa](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-aa?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ab](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ab?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ac](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ac?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ad](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ad?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ae](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ae?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-af](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-af?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ag](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ag?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ah](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ah?api=v2)
-* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ai](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ai?api=v2)
+### Step 1. Running Memory Profiler
+ * In the menu, choose Tools > Tizen > Profiler > Run Memory Profiler.
-[[SHA256 values for archive contents]](#checksums)
+ ![Run Memory Profiler](docs/image/run_memory_profiler.png)
-### Initialization of device for measurements [[Details]](docs/DETAILED.md#prepare-tizen-device-for-measurements)
-2\. Put Tizen RPMs to VM's /home/ubuntu/device-rpms for:
-- debuginfo packages
+ * If no Tizen device is connected and no Tizen emulators are running then Emulator Manager will be started. Please launch the type of emulator you want to use for running and profiling your application.
-3\. Run /home/ubuntu/heaptrack-scripts/prepare-device.sh on VM
+ ![Launch Emulator](docs/image/start_emulator.png)
-[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/How-to-prepare-Tizen-device-for-measurements.mp4?api=v2)
+ * If everything is OK then the application starts as if you run it normally (using Start Debugging in Visual Studio).
-### Build profiler module for your CoreCLR version [[Details]](docs/DETAILED.md#build-profiler-module-for-your-coreclr-version)
+ ![Tizen application running](docs/image/memory_profiling_app_started.png)
-By default the libprofiler.so is already built for coreclr-2.0.0.11992-11.1.armv7l, so if you use this version,
-then you don't need to rebuild it.
-For details, see "Build profiler module" in full contents below (section 5)
+ * In case of errors the Output Window will display them (the output below corresponds to the normal operation case).
-[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/How-to-build-managed-profiling-module.mp4?api=v2)
+ ![Memory Profiler Output Window](docs/image/memory_profiling_msvs_output.png)
-### Measurements [[Details]](docs/DETAILED.md#run-measurements)
-8\. Make sure that "debuginfo" packages are installed for all libraries that you want to track by the profiler
+ * After finishing the application the memory profiling data file will be copied from Tizen to Windows host.
-9\. Run application using /home/ubuntu/heaptrack-scripts/heaptrack.sh with `[application ID]` and `[path to executable]` arguments on VM,
-like the following:
+### Step 2. Running Memory Profiler GUI
+ * You can open the memory profiling data file in the GUI application to analyze it. In the menu, choose Tools > Tizen > Profiler > Show Memory Profiler.
-```
-./heaptrack-scripts/heaptrack.sh org.tizen.example.HelloWorld.Tizen /opt/usr/home/owner/apps_rw/HelloWorld.Tizen/bin/HelloWorld.Tizen.exe
-```
+ ![Run Memory Profiler GUI](docs/image/run_memory_profiler_gui.png)
-[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/Profiling-memory-consumption.mp4?api=v2)
+### Step 3. Analyzing the results
+ * The GUI application provides several views to the memory profiling data. The views include:
-10\. Wait until a moment, in which you want to figure out the memory consumption.
-
-Please, note that application runs significantly slower (30x to 100x slower) under profiling.
-
-11\. Press 'c' on VM, wait for GUI to start and analyze the results
-The GUI will start separately for **Malloc** part, **Managed** part, **mmap Private_Dirty** part, **mmap Private_Clean** part and **mmap Shared_Clean** part
-
-[[Profiling results description]](#profiling-results)
-
-[[Some screenshots]](#screenshots)
-
-[[Solutions for possible issues]](#troubleshooting)
-
-### Profiling results
-
-The GUI viewer provides several views for the profiling results.
-
-- If some functions are shown as `<unresolved function>` - most probably (with few exceptions, like libc internals) it means that "debuginfo" package for the corresponding library is missing or of wrong version.
-
-- The functions, which are marked as `<untracked>` are the areas, for which locations is not known. This is a mark for areas, which were allocated before attaching profiler, or by ways, which are not trackable by current version of profiler (current profiler doesn't track allocations from ld-linux.so.3).
-
-- The "leaks" in the interface are most probably not actual leaks - it is label for memory that wasn't freed when application was terminating. However, it is usual behaviour for many libraries, including CoreCLR to not free memory upon application termination, as kernel anyway frees it. So, "leaks" labels should be considered as labels for memory, which was consumed just before memory profiling stopped.
-
-#### Summary view
-Peak contributions shows top functions that consumed memory at moment of highest memory consumption.
-Peak instances shows top functions that has most memory items allocated and not freed at moment of highest count of the memory items.
-Largest memory leaks - top functions that allocated memory, which wasn't deallocated up to moment of memory profiling stop.
-Most memory allocations - top functions that call allocation most number of times.
-Most temporary allocations - the same for allocations, in which deallocation is called very close to allocation, like `p = malloc(...); simple code; free (p)`.
-Most memory allocated - top functions, which allocated most memory (this doesn't account deallocations, so is not very useful for analysis of memory consumption causes).
-
-#### Bottom-Up/Top-Down view
-Memory consumption details for each detected call stack.
-
-#### Caller / Callee view
-Memory consumption statistics for each function for itself, and also inclusive (i.e. including statistics of other functions, which it called).
-
-#### Flame Graph view
-Also represents memory consumption per call-stack (either top-down or bottom-down mode)
-Combobox allows to choose between different statistics (like in Summary view).
-The "leaks" here also are not the memory leaks: it is memory that was consumed at moment of profiling stop.
-So, the "leaks" view seems to be the most useful to investigate memory consumption details.
-
-#### Other
-Next several views are graphs for different memory consumption statistics (also, like in Summary view), as they change in time of application execution.
-Here, the "consumed" is used instead of "leaks", which is more precise.
-The "consumed" view shows memory consumption graph for top memory consuming function (separately, as shown in color, and also total consumption - the topmost graph).
-
-## Troubleshooting
-
-1\. The heaptrack is built for one of latest Tizen Unified TM1 system.
-If it can't be started on your Tizen device, because of missing dependencies, please recompile it from sources (see /home/ubuntu/heaptrack-common/build-armel).
-Recompilation is simple, like "mkdir build_dir; cd build_dir; cmake ..; make -j4" and should be performed on an armel system with compatible libraries or in a corresponding chroot.
-
-2\. Managed memory consumption or managed call stacks data is missing
-
-See [[Build profiler module]](docs/DETAILED.md#build-profiler-module-for-your-coreclr-version)
-
-3\. Please, feel free to ask any questions, in case of the described or other issues ([https://github.sec.samsung.net/dotnet/profiler/issues/4](https://github.sec.samsung.net/dotnet/profiler/issues/4))
-
-The profiling tool can show memory consumption of **Managed**, **Malloc** and **Mmap**-allocated regions.
-
-## Screenshots
-
-### malloc'ed memory over time
-![malloc-Consumed-Graph.png](screenshots/malloc-Consumed-Graph.png)
-* Each color shows different function (function name is shown when mouse is over a part of graph)
-* The top part is sum of all malloc-allocated memory (for all allocating functions)
-* The graph is useful to get overall picture of how memory consumption changed as program executed
-### Functions and their statistics
-![malloc-Plain-Statistics.png](screenshots/malloc-Plain-Statistics.png)
-* Peak is memory consumption of particular function at overall maximum of application consumption (this considers only currently shown memory - i.e. only malloc, or mmap Private_Dirty, or mmap Private_Clean, etc.)
-* Leaked is memory consumption just at application exit (not actually, leak, as most application do not free memory at their exit - so, it is just information about memory consumption just before application exit)
-* Allocated is sum of all allocated memory sizes (doesn't account that some memory was already freed - counts only allocations)
-* "Incl." mark is for the function and all functions that it calls; "Self" mark is for the function only
-### Call stacks and memory consumption at each point of call stack
-![malloc-FlameGraph-Details.png](screenshots/malloc-FlameGraph-Details.png)
-* For example, "icu::Normalizer2Impl::ensureCanonIterData" calls "utrie2_enum" and "icu::CanonIterData::CanonIterData" and "utri2_freeze"
- * The "utrie2_enum" and "icu::CanonIterData::CanonIterData" and "utri2_freeze" call other functions that in sum allocate 539, 283 and 80 kilobytes, correspondingly
- * The consumption for "ensureCanonIterData" will be shown as 904 kB - it is sum of the three values
-* In another places (another call stacks) the functions can also be called and that memory consumption will be accounted separately for these and those call stacks
-### Reversed FlameGraph (if "Bottom-Down View" is checked)
-![malloc-FlameGraph-Reversed.png](screenshots/malloc-FlameGraph-Reversed.png)
-* A useful form of FlameGraph to show the call stacks reversed
- * So, the bottom line will show the allocator functions - like malloc, calloc, realloc, etc. and lines above will be the functions that call the allocator functions
-### Top-N lists of functions:
-![malloc-Summary.png](screenshots/malloc-Summary.png)
-* functions that consumed most when peak of malloc memory consumption occured ("peak contributions")
-* functions that consumed most just before application exit ("largest memory leaks")
-* functions that called allocators more than others ("most memory allocations")
-* functions that called allocators more than others for temporary allocations ("most temporary allocations") - temporary is the case when malloc and free are called almost one after other (with no allocations between them)
-* functions that allocated most memory at sum ("most memory allocated") - just sum of allocations, without accounting freeing of memory
-* "peak RSS" is currently experimental and so is not precise
-### Managed heap inspection
-![managed-ReferenceTree.png](screenshots/managed-ReferenceTree.png)
-* Objects are grouped by their type
-* If type T2 is a child of type T1, it means that objects of type T2 reference objects of type T1
-* Shallow Size is the total size of objects in the reference chain
-* Referenced Size is the size of objects referenced by the parent object.
-For example, `System.AppDomainSetup` references 76 bytes of `System.String`s in the screenshot through `System.Object[]`->`System.String[]` chain and 32 bytes of `System.String`s directly.
-### mmap-allocated memory graphs
-Most of the graphs listed above are also available for mmap-allocated memory.
-![mmap-private-dirty-Plain-Statistics.png](screenshots/mmap-private-dirty-Plain-Statistics.png)
-![mmap-private-dirty-Consumed-Graph.png](screenshots/mmap-private-dirty-Consumed-Graph.png)
-* the mmap-allocated memory is divided into four groups (as in /proc/.../smaps): Private_Dirty, Private_Clean, Shared_Clean + Shared_Dirty
- * Private_Dirty is process-local (not shared) memory that was modified by the process
- * Private_Clean is process-local (not shared) memory that was loaded from disk and not modified by the process
- * Shared_Clean is shared between processes memory, not modified by the process
- * Shared_Clean is shared between processes memory, modified by the process
- * the groups are chosen by --private_dirty, --private_clean, --shared keys of heaptrack_gui (--malloc is for malloc memory consumption and --managed is for managed memory consumption)
-* there are two additional groups, which are accounted indirectly
- * dlopen (memory, used for loaded libraries)
- * sbrk (memory, used for "[heap]") - this includes:
- * libc allocator (malloc-allocated memory, can be investigated through `heaptrack_gui --malloc` run)
- * overhead of profiler
- * possibly, other allocators if they use `sbrk` (this is unusual)
-
-## Checksums
-
-SHA256SUM for unpacked VM image
+ - summary page with information on which process was profiled, its total runtime, some memory related statistics, etc.
+ - bottom-up table tree view of the code locations that allocated memory with their aggregated cost and stack traces
+ - caller/callee table
+ - top-down table tree view of the code locations
+ - managed heap table tree view
+ - flame graph visualization (explanation: <http://www.brendangregg.com/FlameGraphs/memoryflamegraphs.html>)
+ - consumed memory size over time graph
+ - number of instances over time graph
+ - number of memory allocations over time graph
+ - size of memory allocated over time graph
+ - allocation histogram displaying the number of allocations (the total number and the several topmost code locations) belonging to one of the groups divided by allocation size (0 - 8 bytes, 9 - 16 bytes, ... , 512 bytes - 1 KB, more than 1 KB)
-```
-1afcea540149f76fae6e243839b6a21666725cc1409b4c259be82533e2a21a24 ubuntu-17.04.vdi
-```
+#### Managed heap view sample
-SHA256SUM for archive
+ ![Managed heap view](docs/image/memory_profiler_gui_managed_heap.png)
-```
-e8de003daa38880e37d847af462b86145ccf859f0af127e9a18dcc7c7cc04deb ubuntu-17.04.vdi.tar.bz2
-```
+#### Flame graph view sample
-SHA256SUM for archive parts
-```
-7da95143eb387f94d09c1a34a952bcdc05844cbf064ba971884b6107f2665e55 ubuntu-17.04.vdi.tar.bz2-31-08-17-aa
-34bec202f4521fc3857fa57c7acb07910f02df788add8a01dc03b09bc732895f ubuntu-17.04.vdi.tar.bz2-31-08-17-ab
-a03cd105647fd225fcbc85310490032f96c23c9e55289fe88341daf8db560236 ubuntu-17.04.vdi.tar.bz2-31-08-17-ac
-788790f51a237273eff96e102a6ad39fdd0008dcfcbbe537d80a1ab543cbcd7c ubuntu-17.04.vdi.tar.bz2-31-08-17-ad
-a165a9642e8d58536b4c944e12fe23bd666d77cd74b3fce1b050c01308a4b887 ubuntu-17.04.vdi.tar.bz2-31-08-17-ae
-7bf1495ae705a6a32577c4b1982c18230338eaa4b7cd5079b87a376d99b77b48 ubuntu-17.04.vdi.tar.bz2-31-08-17-af
-6fea617bf0833bb841317c56674e6b34c09a145fc5a95f4ce1ea202dc6f4b56a ubuntu-17.04.vdi.tar.bz2-31-08-17-ag
-21389420ce2dcc0cd86d1b2c0872cb116e18ce226e1bab111e00536ed5be17f1 ubuntu-17.04.vdi.tar.bz2-31-08-17-ah
-58a3950e44c37f9b825d13ff738e75544a3a2e0bca8f5a74ce877cbbee7a141b ubuntu-17.04.vdi.tar.bz2-31-08-17-ai
-```
+ ![Flame graph view](docs/image/memory_profiler_gui_flame_graph.png)
+
+#### Memory allocations graph view sample
+
+ ![Memory allocations graph view](docs/image/memory_profiler_gui_allocations_graph.png)
+
+#### Allocation histogram view sample
+
+ ![Allocation histogram view](docs/image/memory_profiler_gui_allocation_histogram.png)
diff --git a/WINDOWS_GUI_BUILD.md b/WINDOWS_GUI_BUILD.md
new file mode 100644
index 0000000..c1135f3
--- /dev/null
+++ b/WINDOWS_GUI_BUILD.md
@@ -0,0 +1,153 @@
+# Building Tizen .NET Memory Profiler GUI on Windows
+
+## Introduction
+
+Tizen .NET Memory Profiler is based on an open-source memory profiler for C/C++ Linux applications named "Heaptrack".
+
+Tizen .NET Memory Profiler allows to profile the memory consumption of managed .NET CoreCLR applications running under Tizen OS.
+
+The Memory profiler consists of the following two major parts:
+ - a utility which collects memory profiling data and saves them to a file, this part of the Profiler runs on the target side;
+ - tools for analyzing the resulting data, the command-line one and the GUI-based one (originally named "heaptrack_gui"), these analyzing tools run on the host side.
+
+This document provides instructions on how to build the GUI-based part of the Memory profiler for Windows OS.
+
+### Background regarding Windows GUI port
+The original GUI application uses Qt framework. Qt is multi-platform supporting Windows but also the application uses several libraries from KDE Frameworks 5 (KF5): KCoreAddons, KI18n, ThreadWeaver, KChart, and others. The KDE Frameworks libraries are interrelated with Qt (they can be treated as a Qt superset). There is an ongoing project to port KDE applications and KDE Frameworks libraries to Windows (<https://community.kde.org/Windows>) but it’s not completed. Another issue is licensing: some KF5 libraries use GNU GPL v.2 license which is not acceptable according to Samsung (while LGPL is acceptable). It was easy to find replacements to most KDE libraries features used among Qt 5 libraries (Qt version 5.10 or later is recommended). The most important KDE library in question was KChart, a part of KDE KDiagram libraries. KChart is used in the original Heaptrack GUI application to draw charts and an allocation histogram.
+
+Instead of KDE-specific libraries, the Windows GUI port uses QWT library (<http://qwt.sourceforge.net>) to draw charts on Windows platform. The library is licensed on terms of its own license based on LGPL (but less restrictive). It’s possible to use QWT instead of KChart when building the application on Linux as well (controlled by a setting in the application’s project file for *Qt Creator* / *qmake*).
+
+## Prerequisites
+
+Operating system: any Microsoft Windows 64-bit operating system starting from Windows 7 can be used to build the memory profiler GUI.
+
+All needed prerequisites can be freely downloaded from Web. The following software and libraries are needed:
+
+1. Microsoft Visual Studio 2017;
+
+2. boost C++ libraries;
+
+3. Qt 5 framework including some libraries, tools (*qmake*), and optionally IDE (*Qt Creator*);
+
+4. QWT library and any SVN client to get it from its repository;
+
+5. ThreadWeaver library and *git* software to get it from its repository.
+
+### Microsoft Visual Studio 2017
+
+Microsoft Visual Studio 2017 (<https://www.visualstudio.com/downloads>) or higher is required (Community Edition can be used). “Desktop development with C++” workload must be selected in Visual Studio installer as well as the “VC++ 2017 v141 toolset (x86, x64)” and “MSBuild” components (see Individual components | Compilers, build tools, and runtimes).
+
+### Boost libraries
+
+Boost library 1.66.0 or higher can be downloaded from <https://www.boost.org>. Build instructions:
+
+1. extract files e.g. to *c:\src\boost_1_66_0*;
+
+2. open “x64 Native Tools Command Prompt for VS 2017” available in Windows Start Menu under Visual Studio 2017 submenu;
+
+3. build Boost.Build engine: go to *c:\src\boost_1_66_0* and start *bootstrap.bat*.
+
+Special handling is required to build the required boost library *iostreams* with *zlib* support enabled. Original *zlib* library sources are needed for that.
+
+4. download *zlib* sources from <http://zlib.net> (direct link to version 1.2.11 is <http://zlib.net/zlib-1.2.11.tar.gz>) and extract them to some directory, for example *c:\src\zlib-1.2.11*;
+
+5. run *b2.exe* built on step 3 with the following options:<br>
+`b2 -a --with-iostreams -sZLIB_SOURCE="c:/src/zlib-1.2.11"`
+
+Finally create the system environment variable BOOST_LIB and set it to *c:\src\boost_1_66_0* (you may use *System Properties \ Advanced \ Environment Variables* Windows dialog for this).
+
+### Qt 5
+
+Download open source Qt from <https://www.qt.io/download>. The software can be installed with the help of Qt Online Installer for Windows. It’s necessary to select the “MSVC 2017 64-bit” component (Qt 5.xx Prebuilt Components for MSVC 2017 64-bit) in the installer’s component tree (under “Qt \ Qt 5.xx”). To enable building the memory profiler from IDE (*Qt Creator*) and debugging it with the help of CDB (Microsoft Symbolic Debugger for Windows) the “Qt Creator 4.xx CDB Debugger Support” shall be selected (*Qt Creator* itself is always selected). CDB is a part of Windows SDK (WDK) which can be installed using Windows SDK online installer (for Windows 10 it’s available from <https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk>): select *Debugging Tools for Windows* feature.
+
+### QWT
+
+QWT 6.2 or higher is needed for the memory profiler GUI. Its sources can be received using this command (presuming “svn” utility is installed):
+
+`svn checkout https://svn.code.sf.net/p/qwt/code/trunk`
+
+If the current directory was *c:\svn* then after running the command the latest QWT sources will be located at *c:\svn\trunk\qwt\src*.
+
+QWT documentation suggests downloading stable releases from <https://sourceforge.net/projects/qwt/files/qwt> but it seems the versions available there are rather obsolete.
+
+Building:
+
+1. open *Qt command prompt* (e.g. “Qt 5.11.0 64-bit for Desktop (MSVC 2017)”) available in Windows Start Menu under Qt submenu;
+
+2. go to the directory where *qwt.pro* file is located, e.g.<br>
+`cd c:\svn\trunk\qwt`
+
+3. (optionally) edit *qwtconfig.pri*, e.g. set the QWT_INSTALL_PREFIX variable (see *win32* section in the file) to the directory you want (the default is *C:/Qwt-$$QWT_VERSION-svn*);
+
+4. setup the 64-bit MSVC compiler environment running *vcvars64.bat* script: if Visual Studio 2017 is installed to “c:\Program Files (x86)” then run<br>
+`“c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat”`
+
+5. ensure “rc.exe” for x64 platform is in the path; it’s location depends on the version of Windows Kits installed, e.g. it can be located in “c:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x64” – you may add this folder to PATH temporarily:<br>
+`set PATH=%PATH%;"C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x64"`
+
+6. run `qmake qwt.pro`, then `nmake`, then `nmake install`.
+
+After successful completion of these steps QWT files including headers, documentation, and binaries will be located under *C:\Qwt-$$QWT_VERSION-svn* folder (e.g. *C:\Qwt-6.3.0-svn*). Dynamic link libraries needed to run the memory profiler GUI, *qwt.dll* and *qwtd.dll* (for Debug version), are in *lib* subfolder of that folder.
+
+### ThreadWeaver
+
+This library is a helper for multithreaded programming. It is used in the memory profiler GUI to speed-up some operations, such as parsing the source memory profiling data, on multi-CPU and multicore systems. It can be built from sources available from its repository:
+
+`git clone git://anongit.kde.org/threadweaver.git`
+
+The recommended path to clone to is *c:\git\kf5\threadweaver*. In this case the *qmake* project file for ThreadWeaver added to *heaptrack* to build the library may be used “as is”. If *heaptrack* (with Tizen Memory Profiler GUI) repository is located in “c:\git\heaptrack” then the project file is “C:\git\heaptrack\src\ThreadWeaver.pro”. You may load it to *Qt Creator* and then build or use *qmake* utility from *Qt command prompt* (the 64-bit MSVC compiler environment must be set – see above).
+
+Build DEBUG version:
+
+`qmake.exe ThreadWeaver.pro -spec win32-msvc "CONFIG+=debug" "CONFIG+=qml_debug"`<br>
+`nmake`
+
+Build RELEASE version:
+
+`qmake.exe ThreadWeaver.pro -spec win32-msvc`<br>
+`nmake`
+
+## Building the GUI application
+
+### How to build
+
+After building all libraries required it’s possible to build the GUI application itself. It can be done using *Qt Creator* or *qmake* utility. The application's project file in *qmake* format is *heaptrack_gui.pro*. In either case two environment variables must be set (on the system level or in *Qt Creator*, in the *heaptrack_gui* project settings – both for Debug and Release build configurations) to be able to use QWT. First variable, QMAKEFEATURES, must point to the QWT “features” folder (where *prf-* and *pri-* files are located), e.g.<br>
+`QMAKEFEATURES=c:\Qwt-6.3.0-svn\features`.
+
+Another variable, QWT_ROOT, must point to the base QWT folder, e.g.<br>
+`QWT_ROOT=c:\Qwt-6.3.0-svn`.
+
+For setting the variables in *Qt Creator* you need to switch to the *Projects* mode in *Mode Selector* (use *Window* | *Show Mode Selector* menu command to show the selector if it's not visible). Then ensure that *Active Project* is *heaptrack_gui* and *Build* is selected in the *Build & Run* section (see the left part of the window) under the name of the Qt Kit used (e.g. "Desktop Qt 5.11.0 MSVC2017 64bit"). In the *Build Settings* section (the right part of the window) you need to edit *Build Environment*: press *Details* button in the *Build Environment* section to expand the section and then use *Add* or *Batch Edit* button to set the variables. Do this for Debug and Release build configurations (change the configuration using the *Edit build configuration* combo box located at the top of *Build Settings*).
+
+![Build settings in Qt Creator](screenshots/build_settings.png)
+
+Then you may load *heaptrack\src\heaptrack_gui.pro*, select the required build configuration (Debug or Release) and start the build from the project's context menu (*heaptrack_gui* project shall be selected in the Projects tree) or from the "Build" top-level menu of the main menu bar.
+
+![Build GUI from Qt Creator](screenshots/build_gui_from_qt_creator.png)
+
+If using *qmake* you can start *Qt command prompt*, go to the *heaptrack\src* path and run<br>
+`qmake heaptrack_gui.pro -spec win32-msvc`.
+
+After that the application shall build successfully. To be able to run it from *Qt Creator* the dynamic library *qwtd.dll* (*qwt.dll* for Release version) must be copied from the QWT output directory (e.g. *c:\Qwt-6.3.0-svn\lib*) to *bin\debug* (*bin\release*) folders.
+
+### Application's binaries
+
+The following files and directories are required to run the application (Release version) under Windows.
+
+<pre>
+TizenMemoryProfiler.exe
+Qt5Core.dll
+Qt5Gui.dll
+Qt5OpenGL.dll
+Qt5Svg.dll
+Qt5Widgets.dll
+qwt.dll
+threadweaver.dll
+<b>imageformats\</b>qjpeg.dll
+<b>platforms\</b>qwindows.dll
+<b>styles\</b>qwindowsvistastyle.dll
+</pre>
+
+After building the Release configuration of the GUI application (TizenMemoryProfiler.exe) using *Qt Creator* or *qmake* the required dynamic link libraries (except *threadweaver.dll*) will be copied to the destination directory *bin\release* automatically. The remaining *threadweaver.dll* file will be created after building *ThreadWeaver.pro* if the library was installed to the recommended directory (see [ThreadWeaver](#ThreadWeaver)), otherwise you may need to copy this file manually.
+
+You may also use the Qt Windows deployment tool *windeployqt* (see [Qt for Windows - Deployment](http://doc.qt.io/qt-5/windows-deployment.html)) to create a folder containing the Qt-related dependencies of TizenMemoryProfiler.exe (but some of the files and directories copied by the tool may be unnecessary). File *qwt.dll* (and possibly *threadweaver.dll*) shall be copied manually in this case.
diff --git a/docs/TIZEN_HEAPTRACK_ORIGINAL_README.md b/docs/TIZEN_HEAPTRACK_ORIGINAL_README.md
new file mode 100644
index 0000000..1e1e0bd
--- /dev/null
+++ b/docs/TIZEN_HEAPTRACK_ORIGINAL_README.md
@@ -0,0 +1,227 @@
+# Tizen .NET Memory Profiler
+
+(Original KDE Heaptrack's README can be found [here](docs/HEAPTRACK_README.md))
+
+## Brief contents of the Guide
+
+### Building and launching profiler in Docker
+
+Prerequisites:
+* [Tizen SDK](https://developer.tizen.org/development/tizen-studio/download) is required to run the profiler.
+* Your host machine should have Docker installed. For Ubuntu instructions, please see [the manual](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/).
+
+Please, keep in mind:
+
+> If you would like to use Docker as a non-root user, you should now consider
+> adding your user to the "docker" group with something like:
+>
+> `sudo usermod -aG docker your-user`
+
+1. When building in Docker, the first thing you need to do is to create a `coreclr-devel` directory and put coreclr-devel rpms for the CoreCLR versions you use on your devices in this folder. You can find out which version of CoreCLR is installed on your device using `rpm -qa` command. Alternatively, you can let docker download the latest `coreclr-devel` package from download.tizen.org, or, if you are building CoreCLR yourself, point the build script to the compiled CoreCLR source folder. If you choose one of these two options, leave the `coreclr-devel` folder empty and proceed to step 2.
+2. Run
+```
+sudo ./launch.sh org.tizen.example.HelloWorld.Tizen /opt/usr/home/owner/apps_rw/HelloWorld.Tizen/bin/HelloWorld.Tizen.exe
+```
+to build the profiler and launch profiling of HelloWorld application. If you are running the profiler for the first time, you will be asked permission to move files around on the device in order to free up some disk space. You will also be asked to point the script to the location of "debuginfo" RPMs for the native libraries in which you want to see stack traces.
+
+### VM [[Details]](docs/DETAILED.md#download-and-unpack-vm-disk-image-from-this-page)
+
+1\. Download and unpack VM image, start it in VirtualBox and connect with Tizen device
+
+To unpack it on Linux, please, download the ubuntu-17.04.vdi.tar.bz2-* files to separate directory and run the following command in the directory:
+
+```
+cat ubuntu-17.04.vdi.tar.bz2-* | tar xjvf -
+```
+
+
+
+For unpacking on Windows, please see [the details](docs/DETAILED.md#download-and-unpack-vm-disk-image-from-this-page).
+
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-aa](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-aa?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ab](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ab?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ac](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ac?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ad](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ad?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ae](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ae?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-af](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-af?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ag](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ag?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ah](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ah?api=v2)
+* [ubuntu-17.04.vdi.tar.bz2-31-08-17-ai](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/ubuntu-17.04.vdi.tar.bz2-31-08-17-ai?api=v2)
+
+[[SHA256 values for archive contents]](#checksums)
+
+### Initialization of device for measurements [[Details]](docs/DETAILED.md#prepare-tizen-device-for-measurements)
+
+2\. Put Tizen RPMs to VM's /home/ubuntu/device-rpms for:
+- debuginfo packages
+
+3\. Run /home/ubuntu/heaptrack-scripts/prepare-device.sh on VM
+
+[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/How-to-prepare-Tizen-device-for-measurements.mp4?api=v2)
+
+
+### Build profiler module for your CoreCLR version [[Details]](docs/DETAILED.md#build-profiler-module-for-your-coreclr-version)
+
+By default the libprofiler.so is already built for coreclr-2.0.0.11992-11.1.armv7l, so if you use this version,
+then you don't need to rebuild it.
+
+For details, see "Build profiler module" in full contents below (section 5)
+
+[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/How-to-build-managed-profiling-module.mp4?api=v2)
+
+### Measurements [[Details]](docs/DETAILED.md#run-measurements)
+
+8\. Make sure that "debuginfo" packages are installed for all libraries that you want to track by the profiler
+
+9\. Run application using /home/ubuntu/heaptrack-scripts/heaptrack.sh with `[application ID]` and `[path to executable]` arguments on VM,
+like the following:
+
+```
+./heaptrack-scripts/heaptrack.sh org.tizen.example.HelloWorld.Tizen /opt/usr/home/owner/apps_rw/HelloWorld.Tizen/bin/HelloWorld.Tizen.exe
+```
+
+[Video tutorial](http://suprem.sec.samsung.net/confluence/download/attachments/81831470/Profiling-memory-consumption.mp4?api=v2)
+
+10\. Wait until a moment, in which you want to figure out the memory consumption.
+
+Please, note that application runs significantly slower (30x to 100x slower) under profiling.
+
+11\. Press 'c' on VM, wait for GUI to start and analyze the results
+The GUI will start separately for **Malloc** part, **Managed** part, **mmap Private_Dirty** part, **mmap Private_Clean** part and **mmap Shared_Clean** part
+
+[[Profiling results description]](#profiling-results)
+
+[[Some screenshots]](#screenshots)
+
+[[Solutions for possible issues]](#troubleshooting)
+
+### Profiling results
+
+The GUI viewer provides several views for the profiling results.
+
+- If some functions are shown as `<unresolved function>` - most probably (with few exceptions, like libc internals) it means that "debuginfo" package for the corresponding library is missing or of wrong version.
+
+- The functions, which are marked as `<untracked>` are the areas, for which locations is not known. This is a mark for areas, which were allocated before attaching profiler, or by ways, which are not trackable by current version of profiler (current profiler doesn't track allocations from ld-linux.so.3).
+
+- The "leaks" in the interface are most probably not actual leaks - it is label for memory that wasn't freed when application was terminating. However, it is usual behaviour for many libraries, including CoreCLR to not free memory upon application termination, as kernel anyway frees it. So, "leaks" labels should be considered as labels for memory, which was consumed just before memory profiling stopped.
+
+#### Summary view
+Peak contributions shows top functions that consumed memory at moment of highest memory consumption.
+Peak instances shows top functions that has most memory items allocated and not freed at moment of highest count of the memory items.
+Largest memory leaks - top functions that allocated memory, which wasn't deallocated up to moment of memory profiling stop.
+Most memory allocations - top functions that call allocation most number of times.
+Most temporary allocations - the same for allocations, in which deallocation is called very close to allocation, like `p = malloc(...); simple code; free (p)`.
+Most memory allocated - top functions, which allocated most memory (this doesn't account deallocations, so is not very useful for analysis of memory consumption causes).
+
+#### Bottom-Up/Top-Down view
+Memory consumption details for each detected call stack.
+
+#### Caller / Callee view
+Memory consumption statistics for each function for itself, and also inclusive (i.e. including statistics of other functions, which it called).
+
+#### Flame Graph view
+Also represents memory consumption per call-stack (either top-down or bottom-down mode)
+Combobox allows to choose between different statistics (like in Summary view).
+The "leaks" here also are not the memory leaks: it is memory that was consumed at moment of profiling stop.
+So, the "leaks" view seems to be the most useful to investigate memory consumption details.
+
+#### Other
+Next several views are graphs for different memory consumption statistics (also, like in Summary view), as they change in time of application execution.
+Here, the "consumed" is used instead of "leaks", which is more precise.
+The "consumed" view shows memory consumption graph for top memory consuming function (separately, as shown in color, and also total consumption - the topmost graph).
+
+## Troubleshooting
+
+1\. The heaptrack is built for one of latest Tizen Unified TM1 system.
+If it can't be started on your Tizen device, because of missing dependencies, please recompile it from sources (see /home/ubuntu/heaptrack-common/build-armel).
+Recompilation is simple, like "mkdir build_dir; cd build_dir; cmake ..; make -j4" and should be performed on an armel system with compatible libraries or in a corresponding chroot.
+
+2\. Managed memory consumption or managed call stacks data is missing
+
+See [[Build profiler module]](docs/DETAILED.md#build-profiler-module-for-your-coreclr-version)
+
+3\. Please, feel free to ask any questions, in case of the described or other issues ([https://github.sec.samsung.net/dotnet/profiler/issues/4](https://github.sec.samsung.net/dotnet/profiler/issues/4))
+
+The profiling tool can show memory consumption of **Managed**, **Malloc** and **Mmap**-allocated regions.
+
+## Screenshots
+
+### malloc'ed memory over time
+![malloc-Consumed-Graph.png](screenshots/malloc-Consumed-Graph.png)
+* Each color shows different function (function name is shown when mouse is over a part of graph)
+* The top part is sum of all malloc-allocated memory (for all allocating functions)
+* The graph is useful to get overall picture of how memory consumption changed as program executed
+### Functions and their statistics
+![malloc-Plain-Statistics.png](screenshots/malloc-Plain-Statistics.png)
+* Peak is memory consumption of particular function at overall maximum of application consumption (this considers only currently shown memory - i.e. only malloc, or mmap Private_Dirty, or mmap Private_Clean, etc.)
+* Leaked is memory consumption just at application exit (not actually, leak, as most application do not free memory at their exit - so, it is just information about memory consumption just before application exit)
+* Allocated is sum of all allocated memory sizes (doesn't account that some memory was already freed - counts only allocations)
+* "Incl." mark is for the function and all functions that it calls; "Self" mark is for the function only
+### Call stacks and memory consumption at each point of call stack
+![malloc-FlameGraph-Details.png](screenshots/malloc-FlameGraph-Details.png)
+* For example, "icu::Normalizer2Impl::ensureCanonIterData" calls "utrie2_enum" and "icu::CanonIterData::CanonIterData" and "utri2_freeze"
+ * The "utrie2_enum" and "icu::CanonIterData::CanonIterData" and "utri2_freeze" call other functions that in sum allocate 539, 283 and 80 kilobytes, correspondingly
+ * The consumption for "ensureCanonIterData" will be shown as 904 kB - it is sum of the three values
+* In another places (another call stacks) the functions can also be called and that memory consumption will be accounted separately for these and those call stacks
+### Reversed FlameGraph (if "Bottom-Down View" is checked)
+![malloc-FlameGraph-Reversed.png](screenshots/malloc-FlameGraph-Reversed.png)
+* A useful form of FlameGraph to show the call stacks reversed
+ * So, the bottom line will show the allocator functions - like malloc, calloc, realloc, etc. and lines above will be the functions that call the allocator functions
+### Top-N lists of functions:
+![malloc-Summary.png](screenshots/malloc-Summary.png)
+* functions that consumed most when peak of malloc memory consumption occured ("peak contributions")
+* functions that consumed most just before application exit ("largest memory leaks")
+* functions that called allocators more than others ("most memory allocations")
+* functions that called allocators more than others for temporary allocations ("most temporary allocations") - temporary is the case when malloc and free are called almost one after other (with no allocations between them)
+* functions that allocated most memory at sum ("most memory allocated") - just sum of allocations, without accounting freeing of memory
+* "peak RSS" is currently experimental and so is not precise
+### Managed heap inspection
+![managed-ReferenceTree.png](screenshots/managed-ReferenceTree.png)
+* Objects are grouped by their type
+* If type T2 is a child of type T1, it means that objects of type T2 reference objects of type T1
+* Shallow Size is the total size of objects in the reference chain
+* Referenced Size is the size of objects referenced by the parent object.
+For example, `System.AppDomainSetup` references 76 bytes of `System.String`s in the screenshot through `System.Object[]`->`System.String[]` chain and 32 bytes of `System.String`s directly.
+### mmap-allocated memory graphs
+Most of the graphs listed above are also available for mmap-allocated memory.
+![mmap-private-dirty-Plain-Statistics.png](screenshots/mmap-private-dirty-Plain-Statistics.png)
+![mmap-private-dirty-Consumed-Graph.png](screenshots/mmap-private-dirty-Consumed-Graph.png)
+* the mmap-allocated memory is divided into four groups (as in /proc/.../smaps): Private_Dirty, Private_Clean, Shared_Clean + Shared_Dirty
+ * Private_Dirty is process-local (not shared) memory that was modified by the process
+ * Private_Clean is process-local (not shared) memory that was loaded from disk and not modified by the process
+ * Shared_Clean is shared between processes memory, not modified by the process
+ * Shared_Clean is shared between processes memory, modified by the process
+ * the groups are chosen by --private_dirty, --private_clean, --shared keys of heaptrack_gui (--malloc is for malloc memory consumption and --managed is for managed memory consumption)
+* there are two additional groups, which are accounted indirectly
+ * dlopen (memory, used for loaded libraries)
+ * sbrk (memory, used for "[heap]") - this includes:
+ * libc allocator (malloc-allocated memory, can be investigated through `heaptrack_gui --malloc` run)
+ * overhead of profiler
+ * possibly, other allocators if they use `sbrk` (this is unusual)
+
+## Checksums
+
+SHA256SUM for unpacked VM image
+
+```
+1afcea540149f76fae6e243839b6a21666725cc1409b4c259be82533e2a21a24 ubuntu-17.04.vdi
+```
+
+SHA256SUM for archive
+
+```
+e8de003daa38880e37d847af462b86145ccf859f0af127e9a18dcc7c7cc04deb ubuntu-17.04.vdi.tar.bz2
+```
+
+SHA256SUM for archive parts
+```
+7da95143eb387f94d09c1a34a952bcdc05844cbf064ba971884b6107f2665e55 ubuntu-17.04.vdi.tar.bz2-31-08-17-aa
+34bec202f4521fc3857fa57c7acb07910f02df788add8a01dc03b09bc732895f ubuntu-17.04.vdi.tar.bz2-31-08-17-ab
+a03cd105647fd225fcbc85310490032f96c23c9e55289fe88341daf8db560236 ubuntu-17.04.vdi.tar.bz2-31-08-17-ac
+788790f51a237273eff96e102a6ad39fdd0008dcfcbbe537d80a1ab543cbcd7c ubuntu-17.04.vdi.tar.bz2-31-08-17-ad
+a165a9642e8d58536b4c944e12fe23bd666d77cd74b3fce1b050c01308a4b887 ubuntu-17.04.vdi.tar.bz2-31-08-17-ae
+7bf1495ae705a6a32577c4b1982c18230338eaa4b7cd5079b87a376d99b77b48 ubuntu-17.04.vdi.tar.bz2-31-08-17-af
+6fea617bf0833bb841317c56674e6b34c09a145fc5a95f4ce1ea202dc6f4b56a ubuntu-17.04.vdi.tar.bz2-31-08-17-ag
+21389420ce2dcc0cd86d1b2c0872cb116e18ce226e1bab111e00536ed5be17f1 ubuntu-17.04.vdi.tar.bz2-31-08-17-ah
+58a3950e44c37f9b825d13ff738e75544a3a2e0bca8f5a74ce877cbbee7a141b ubuntu-17.04.vdi.tar.bz2-31-08-17-ai
+```
diff --git a/docs/image/memory_profiler_gui_allocation_histogram.png b/docs/image/memory_profiler_gui_allocation_histogram.png
new file mode 100644
index 0000000..00969ac
--- /dev/null
+++ b/docs/image/memory_profiler_gui_allocation_histogram.png
Binary files differ
diff --git a/docs/image/memory_profiler_gui_allocations_graph.png b/docs/image/memory_profiler_gui_allocations_graph.png
new file mode 100644
index 0000000..5516946
--- /dev/null
+++ b/docs/image/memory_profiler_gui_allocations_graph.png
Binary files differ
diff --git a/docs/image/memory_profiler_gui_flame_graph.png b/docs/image/memory_profiler_gui_flame_graph.png
new file mode 100644
index 0000000..eaf75ef
--- /dev/null
+++ b/docs/image/memory_profiler_gui_flame_graph.png
Binary files differ
diff --git a/docs/image/memory_profiler_gui_managed_heap.png b/docs/image/memory_profiler_gui_managed_heap.png
new file mode 100644
index 0000000..c8718b3
--- /dev/null
+++ b/docs/image/memory_profiler_gui_managed_heap.png
Binary files differ
diff --git a/docs/image/memory_profiling_app_started.png b/docs/image/memory_profiling_app_started.png
new file mode 100644
index 0000000..ff42d1a
--- /dev/null
+++ b/docs/image/memory_profiling_app_started.png
Binary files differ
diff --git a/docs/image/memory_profiling_msvs_output.png b/docs/image/memory_profiling_msvs_output.png
new file mode 100644
index 0000000..6f4ae7b
--- /dev/null
+++ b/docs/image/memory_profiling_msvs_output.png
Binary files differ
diff --git a/docs/image/run_memory_profiler.png b/docs/image/run_memory_profiler.png
new file mode 100644
index 0000000..0d7208d
--- /dev/null
+++ b/docs/image/run_memory_profiler.png
Binary files differ
diff --git a/docs/image/run_memory_profiler_gui.png b/docs/image/run_memory_profiler_gui.png
new file mode 100644
index 0000000..e25620c
--- /dev/null
+++ b/docs/image/run_memory_profiler_gui.png
Binary files differ
diff --git a/docs/image/start_emulator.png b/docs/image/start_emulator.png
new file mode 100644
index 0000000..5d09774
--- /dev/null
+++ b/docs/image/start_emulator.png
Binary files differ
diff --git a/packaging/0001-Target-build b/packaging/0001-Target-build
new file mode 100644
index 0000000..ea68e5f
--- /dev/null
+++ b/packaging/0001-Target-build
@@ -0,0 +1,43 @@
+ heaptrack/src/CMakeLists.txt | 4 ++--
+ heaptrack/src/track/CMakeLists.txt | 12 ++++++------
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/heaptrack/src/CMakeLists.txt b/heaptrack/src/CMakeLists.txt
+index f29097a..afa1a09 100644
+--- a/heaptrack/src/CMakeLists.txt
++++ b/heaptrack/src/CMakeLists.txt
+@@ -5,5 +5,5 @@ include_directories(
+
+ add_subdirectory(util)
+ add_subdirectory(track)
+-add_subdirectory(interpret)
+-add_subdirectory(analyze)
++add_subdirectory(interpret)
++#add_subdirectory(analyze)
+diff --git a/heaptrack/src/track/CMakeLists.txt b/heaptrack/src/track/CMakeLists.txt
+index 06f59b7..ef005e7 100644
+--- a/heaptrack/src/track/CMakeLists.txt
++++ b/heaptrack/src/track/CMakeLists.txt
+@@ -8,9 +8,9 @@ configure_file(heaptrack.sh.cmake
+ ${PROJECT_BINARY_DIR}/${BIN_INSTALL_DIR}/heaptrack @ONLY
+ )
+
+-install(PROGRAMS ${PROJECT_BINARY_DIR}/${BIN_INSTALL_DIR}/heaptrack
+- DESTINATION ${BIN_INSTALL_DIR}
+-)
++#install(PROGRAMS ${PROJECT_BINARY_DIR}/${BIN_INSTALL_DIR}/heaptrack
++# DESTINATION ${BIN_INSTALL_DIR}
++#)
+
+ # heaptrack_preload: track a newly started process
+ add_library(heaptrack_preload MODULE
+@@ -62,6 +62,6 @@ install(TARGETS heaptrack_inject
+ )
+
+ # public API for custom pool allocators or static binaries
+-install(FILES heaptrack_api.h
+- DESTINATION ${CMAKE_INSTALL_PREFIX}/include
+-)
++#install(FILES heaptrack_api.h
++# DESTINATION ${CMAKE_INSTALL_PREFIX}/include
++#)
diff --git a/packaging/heaptrack.manifest b/packaging/heaptrack.manifest
new file mode 100644
index 0000000..75b0fa5
--- /dev/null
+++ b/packaging/heaptrack.manifest
@@ -0,0 +1,5 @@
+<manifest>
+ <request>
+ <domain name="_"/>
+ </request>
+</manifest>
diff --git a/packaging/heaptrack.spec b/packaging/heaptrack.spec
new file mode 100644
index 0000000..8f64c0e
--- /dev/null
+++ b/packaging/heaptrack.spec
@@ -0,0 +1,105 @@
+Name: heaptrack
+Summary: heaptrack for dotnet apps
+# Version corresponds to CMake @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@-@HEAPTRACK_VERSION_SUFFIX@
+Version: 1.1.0
+Release: 0.1
+Group: Application Framework/Application State Management
+License: GPL
+Source0: %{name}-%{version}.tar.gz
+Source1001: heaptrack.manifest
+Source1002: 0001-Target-build
+%define heaptrack_src heaptrack-%{version}
+%define heaptrack_build build-%{_target_platform}
+AutoReqProv: no
+
+BuildRequires: cmake
+BuildRequires: gcc
+BuildRequires: clang
+BuildRequires: glibc-devel
+BuildRequires: libdw-devel
+BuildRequires: libunwind-devel
+BuildRequires: boost-devel
+BuildRequires: boost-iostreams
+BuildRequires: boost-program-options
+BuildRequires: pkgconfig(zlib)
+BuildRequires: coreclr-devel
+
+# .NET Core Runtime
+%define dotnet_version 2.0.0
+%define dotnetdir dotnet
+%define netshareddir %{dotnetdir}/shared
+%define netcoreappdir %{netshareddir}/Microsoft.NETCore.App/%{dotnet_version}
+
+%description
+Heaptrack for Tizen applications
+
+%prep
+%setup -q
+# 0001-Target-build.patch
+cp %{SOURCE1001} .
+cp %{SOURCE1002} .
+# Gbp-Patch-Macros
+patch -p2 < 0001-Target-build
+
+%build
+
+echo _target _target_cpu _target_arch _target_os _target_platform
+echo %{_target}
+echo %{_target_cpu}
+echo %{_target_arch}
+echo %{_target_os}
+echo %{_target_platform}
+
+export CFLAGS="--target=%{_host}"
+export CXXFLAGS="--target=%{_host}"
+
+%define _heaptrack_build_conf RelWithDebInfo
+%define _coreclr_devel_directory /usr/share/%{netcoreappdir}
+
+cmake \
+ -DCMAKE_INSTALL_PREFIX=%{_prefix} \
+ -DCMAKE_BUILD_TYPE=%{_heaptrack_build_conf} \
+ -DHEAPTRACK_BUILD_GUI=OFF \
+ .
+
+%ifarch %{arm}
+%define arch_dir armel
+%else
+%define arch_dir x86
+%endif
+
+cd profiler;
+ ROOTFS_DIR=/ \
+ CC=clang CXX=clang++ \
+ cmake \
+ -DCMAKE_TOOLCHAIN_FILE=profiler/cross/%{arch_dir}/toolchain.cmake \
+ -DCLR_BIN_DIR=%{_coreclr_devel_directory} \
+ -DCLR_SRC_DIR=%{_coreclr_devel_directory} \
+ -DCLR_ARCH=%{_target_cpu} \
+ profiler \
+ ; \
+ make
+cd -
+
+make %{?jobs:-j%jobs} VERBOSE=1
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+#mkdir -p %{buildroot}%{_native_lib_dir}
+#ln -sf %{_libdir}/libheaptrack_preload.so.1 %{buildroot}%{_native_lib_dir}/libheaptrack_preload.so
+#ln -sf %{_libdir}/libheaptrack_inject.so.1 %{buildroot}%{_native_lib_dir}/libheaptrack_inject.so
+
+pwd
+cp profiler/src/libprofiler.so %{buildroot}%{_prefix}/lib/heaptrack/libprofiler.so
+echo %{buildroot}
+ls %{buildroot}
+echo %{_prefix}
+
+%files
+%manifest heaptrack.manifest
+%{_prefix}/lib/heaptrack/libheaptrack_preload.so*
+%{_prefix}/lib/heaptrack/libheaptrack_inject.so*
+%{_prefix}/lib/heaptrack/libprofiler.so
+%{_prefix}/lib/heaptrack/libexec/heaptrack_interpret
diff --git a/profiler/profiler/cross/x86/toolchain.cmake b/profiler/profiler/cross/x86/toolchain.cmake
new file mode 100644
index 0000000..67ea813
--- /dev/null
+++ b/profiler/profiler/cross/x86/toolchain.cmake
@@ -0,0 +1,26 @@
+set(CROSS_ROOTFS $ENV{ROOTFS_DIR})
+set(CMAKE_SYSTEM_NAME Linux)
+set(CMAKE_SYSTEM_PROCESSOR i686)
+
+add_compile_options("-m32")
+add_compile_options("--sysroot=${CROSS_ROOTFS}")
+
+## Specify the toolchain
+set(TOOLCHAIN "i586-tizen-linux-gnu")
+set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-)
+
+if ("$ENV{CC}" MATCHES "lang")
+ add_compile_options(--target=i686-tizen-linux-gnu)
+endif()
+
+if ("$ENV{CC}" MATCHES "lang")
+ set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -target i686-linux-gnu")
+ set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}/6.2.1")
+endif()
+set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}/6.2.1")
+set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE)
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE)
+set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE)
+
diff --git a/profiler/profiler/src/profiler.cpp b/profiler/profiler/src/profiler.cpp
index ca8e03d..0867d30 100644
--- a/profiler/profiler/src/profiler.cpp
+++ b/profiler/profiler/src/profiler.cpp
@@ -33,6 +33,14 @@ const CLSID CLSID_Profiler = {
0x4C0B,
{0xB3, 0x54, 0x56, 0x63, 0x90, 0xB2, 0x15, 0xCA}};
+#ifdef __i686__
+#define ELT_PARAMETER
+#define SetupHooks SetEnterLeaveFunctionHooks3
+#else // __i686__
+#define ELT_PARAMETER , COR_PRF_ELT_INFO eltInfo
+#define SetupHooks SetEnterLeaveFunctionHooks3WithInfo
+#endif // __i686__
+
extern "C" {
#ifdef __llvm__
__attribute__((used))
@@ -284,8 +292,7 @@ void encodeWChar(WCHAR *orig, char *encoded) {
encoded[i] = 0;
}
-void OnFunctionEnter(FunctionIDOrClientID functionID,
- COR_PRF_ELT_INFO eltInfo) {
+void __stdcall OnFunctionEnter(FunctionIDOrClientID functionID ELT_PARAMETER) {
ICorProfilerInfo3 *info;
HRESULT hr = g_pICorProfilerInfoUnknown->QueryInterface(IID_ICorProfilerInfo3,
(void **)&info);
@@ -313,8 +320,8 @@ void OnFunctionEnter(FunctionIDOrClientID functionID,
info->Release();
}
-void OnFunctionLeave(FunctionIDOrClientID functionID,
- COR_PRF_ELT_INFO eltInfo) {
+void __stdcall OnFunctionLeave(FunctionIDOrClientID functionID
+ ELT_PARAMETER) {
PopShadowStack();
}
@@ -328,8 +335,7 @@ HRESULT STDMETHODCALLTYPE Profiler::Initialize(IUnknown *pICorProfilerInfoUnk) {
COR_PRF_ENABLE_FUNCTION_RETVAL | COR_PRF_ENABLE_FRAME_INFO |
COR_PRF_ENABLE_STACK_SNAPSHOT | COR_PRF_MONITOR_CLASS_LOADS |
COR_PRF_ENABLE_OBJECT_ALLOCATED | COR_PRF_MONITOR_OBJECT_ALLOCATED | COR_PRF_MONITOR_GC);
- info->SetEnterLeaveFunctionHooks3WithInfo(OnFunctionEnter, OnFunctionLeave,
- NULL);
+ info->SetupHooks(OnFunctionEnter, OnFunctionLeave, NULL);
info->Release();
info = NULL;
}
diff --git a/screenshots/build_gui_from_qt_creator.png b/screenshots/build_gui_from_qt_creator.png
new file mode 100644
index 0000000..5f08094
--- /dev/null
+++ b/screenshots/build_gui_from_qt_creator.png
Binary files differ
diff --git a/screenshots/build_settings.png b/screenshots/build_settings.png
new file mode 100644
index 0000000..62ea179
--- /dev/null
+++ b/screenshots/build_settings.png
Binary files differ
diff --git a/src/ThreadWeaver.pro b/src/ThreadWeaver.pro
new file mode 100644
index 0000000..7334638
--- /dev/null
+++ b/src/ThreadWeaver.pro
@@ -0,0 +1,117 @@
+QT -= gui
+
+TEMPLATE = lib
+
+TARGET = threadweaver
+
+# change the variable if ThreadWeaver sources are located in another directory
+SRC_PATH = ../../kf5/threadweaver/src/
+
+win32 {
+ CONFIG(debug, debug|release) {
+ TARGET = $${TARGET}d
+ DESTDIR = ../bin/debug
+ }
+ else {
+ DESTDIR = ../bin/release
+ }
+}
+
+# Probably you don't need to build the library on Linux - check whether it's available
+# in your Linux distribution's repositories.
+unix {
+ target.path = /usr/lib
+ INSTALLS += target
+}
+
+DEFINES += THREADWEAVER_LIBRARY
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which has been marked as deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+INCLUDEPATH += ThreadWeaver
+
+SOURCES += \
+ $${SRC_PATH}collection.cpp \
+ $${SRC_PATH}collection_p.cpp \
+ $${SRC_PATH}debuggingaids.cpp \
+ $${SRC_PATH}dependency.cpp \
+ $${SRC_PATH}dependencypolicy.cpp \
+ $${SRC_PATH}destructedstate.cpp \
+ $${SRC_PATH}exception.cpp \
+ $${SRC_PATH}executewrapper.cpp \
+ $${SRC_PATH}executor.cpp \
+ $${SRC_PATH}iddecorator.cpp \
+ $${SRC_PATH}inconstructionstate.cpp \
+ $${SRC_PATH}job.cpp \
+ $${SRC_PATH}job_p.cpp \
+ $${SRC_PATH}qobjectdecorator.cpp \
+ $${SRC_PATH}queue.cpp \
+ $${SRC_PATH}queueapi.cpp \
+ $${SRC_PATH}queuesignals.cpp \
+ $${SRC_PATH}queuesignals_p.cpp \
+ $${SRC_PATH}queuestream.cpp \
+ $${SRC_PATH}resourcerestrictionpolicy.cpp \
+ $${SRC_PATH}sequence.cpp \
+ $${SRC_PATH}sequence_p.cpp \
+ $${SRC_PATH}shuttingdownstate.cpp \
+ $${SRC_PATH}state.cpp \
+ $${SRC_PATH}suspendedstate.cpp \
+ $${SRC_PATH}suspendingstate.cpp \
+ $${SRC_PATH}thread.cpp \
+ $${SRC_PATH}threadweaver.cpp \
+ $${SRC_PATH}weaver.cpp \
+ $${SRC_PATH}weaver_p.cpp \
+ $${SRC_PATH}weaverimplstate.cpp \
+ $${SRC_PATH}workinghardstate.cpp
+
+HEADERS += \
+ ThreadWeaver/threadweaver_export.h \
+ $${SRC_PATH}collection.h \
+ $${SRC_PATH}collection_p.h \
+ $${SRC_PATH}debuggingaids.h \
+ $${SRC_PATH}dependency.h \
+ $${SRC_PATH}dependencypolicy.h \
+ $${SRC_PATH}destructedstate.h \
+ $${SRC_PATH}exception.h \
+ $${SRC_PATH}executewrapper_p.h \
+ $${SRC_PATH}executor_p.h \
+ $${SRC_PATH}iddecorator.h \
+ $${SRC_PATH}inconstructionstate.h \
+ $${SRC_PATH}job.h \
+ $${SRC_PATH}job_p.h \
+ $${SRC_PATH}jobinterface.h \
+ $${SRC_PATH}jobpointer.h \
+ $${SRC_PATH}lambda.h \
+ $${SRC_PATH}managedjobpointer.h \
+ $${SRC_PATH}qobjectdecorator.h \
+ $${SRC_PATH}queue.h \
+ $${SRC_PATH}queueapi.h \
+ $${SRC_PATH}queueing.h \
+ $${SRC_PATH}queueinterface.h \
+ $${SRC_PATH}queuepolicy.h \
+ $${SRC_PATH}queuesignals.h \
+ $${SRC_PATH}queuesignals_p.h \
+ $${SRC_PATH}queuestream.h \
+ $${SRC_PATH}resourcerestrictionpolicy.h \
+ $${SRC_PATH}sequence.h \
+ $${SRC_PATH}sequence_p.h \
+ $${SRC_PATH}shuttingdownstate.h \
+ $${SRC_PATH}state.h \
+ $${SRC_PATH}suspendedstate.h \
+ $${SRC_PATH}suspendingstate.h \
+ $${SRC_PATH}thread.h \
+ $${SRC_PATH}threadweaver.h \
+ $${SRC_PATH}weaver.h \
+ $${SRC_PATH}weaver_p.h \
+ $${SRC_PATH}weaverimplstate.h \
+ $${SRC_PATH}weaverinterface.h \
+ $${SRC_PATH}workinghardstate.h
diff --git a/src/ThreadWeaver/threadweaver_export.h b/src/ThreadWeaver/threadweaver_export.h
new file mode 100644
index 0000000..4a45c49
--- /dev/null
+++ b/src/ThreadWeaver/threadweaver_export.h
@@ -0,0 +1,12 @@
+#ifndef THREADWEAVER_GLOBAL_H
+#define THREADWEAVER_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(THREADWEAVER_LIBRARY)
+# define THREADWEAVER_EXPORT Q_DECL_EXPORT
+#else
+# define THREADWEAVER_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // THREADWEAVER_GLOBAL_H
diff --git a/src/analyze/accumulatedtracedata.cpp b/src/analyze/accumulatedtracedata.cpp
index 0857370..d94eff0 100644
--- a/src/analyze/accumulatedtracedata.cpp
+++ b/src/analyze/accumulatedtracedata.cpp
@@ -25,6 +25,7 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/iostreams/filter/gzip.hpp>
+#include <boost/iostreams/filter/newline.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include "util/config.h"
@@ -37,13 +38,13 @@
#define POTENTIALLY_UNUSED
#endif
-using namespace std;
-
AllocationData::DisplayId AllocationData::display = AllocationData::DisplayId::malloc;
bool AccumulatedTraceData::isHideUnmanagedStackParts = false;
bool AccumulatedTraceData::isShowCoreCLRPartOption = false;
+typedef unsigned int uint;
+
namespace {
template <typename Base>
@@ -53,7 +54,7 @@ bool operator>>(LineReader& reader, Index<Base>& index)
}
template <typename Base>
-ostream& operator<<(ostream& out, const Index<Base> index)
+std::ostream& operator<<(std::ostream& out, const Index<Base> index)
{
out << index.index;
return out;
@@ -72,22 +73,22 @@ AccumulatedTraceData::AccumulatedTraceData()
objectTreeNodes.reserve(16384);
}
-const string& AccumulatedTraceData::stringify(const StringIndex stringId) const
+const std::string& AccumulatedTraceData::stringify(const StringIndex stringId) const
{
if (!stringId || stringId.index > strings.size()) {
- static const string empty;
+ static const std::string empty;
return empty;
} else {
return strings.at(stringId.index - 1);
}
}
-string AccumulatedTraceData::prettyFunction(const string& function) const
+std::string AccumulatedTraceData::prettyFunction(const std::string& function) const
{
if (!shortenTemplates) {
return function;
}
- string ret;
+ std::string ret;
ret.reserve(function.size());
int depth = 0;
for (size_t i = 0; i < function.size(); ++i) {
@@ -124,7 +125,7 @@ string AccumulatedTraceData::prettyFunction(const string& function) const
return ret;
}
-bool AccumulatedTraceData::read(const string& inputFile)
+bool AccumulatedTraceData::read(const std::string& inputFile)
{
if (totalTime == 0) {
return read(inputFile, FirstPass) && read(inputFile, SecondPass);
@@ -133,8 +134,10 @@ bool AccumulatedTraceData::read(const string& inputFile)
}
}
-bool AccumulatedTraceData::read(const string& inputFile, const ParsePass pass)
+bool AccumulatedTraceData::read(const std::string& inputFile, const ParsePass pass)
{
+ using namespace std;
+
const bool isCompressed = boost::algorithm::ends_with(inputFile, ".gz");
ifstream file(inputFile, isCompressed ? ios_base::in | ios_base::binary : ios_base::in);
@@ -144,6 +147,7 @@ bool AccumulatedTraceData::read(const string& inputFile, const ParsePass pass)
}
boost::iostreams::filtering_istream in;
+ in.push(boost::iostreams::newline_filter(boost::iostreams::newline::posix)); // fix possible newline issues
if (isCompressed) {
in.push(boost::iostreams::gzip_decompressor());
}
@@ -152,8 +156,10 @@ bool AccumulatedTraceData::read(const string& inputFile, const ParsePass pass)
return read(in, pass);
}
-bool AccumulatedTraceData::read(istream& in, const ParsePass pass)
+bool AccumulatedTraceData::read(std::istream& in, const ParsePass pass)
{
+ using namespace std;
+
LineReader reader;
int64_t timeStamp = 0;
@@ -1120,8 +1126,9 @@ AccumulatedTraceData::checkCallStackIsUntracked(TraceIndex index)
namespace { // helpers for diffing
template <typename IndexT, typename SortF>
-vector<IndexT> sortedIndices(size_t numIndices, SortF sorter)
+std::vector<IndexT> sortedIndices(size_t numIndices, SortF sorter)
{
+ using namespace std;
vector<IndexT> indices;
indices.resize(numIndices);
for (size_t i = 0; i < numIndices; ++i) {
@@ -1131,8 +1138,10 @@ vector<IndexT> sortedIndices(size_t numIndices, SortF sorter)
return indices;
}
-vector<StringIndex> remapStrings(vector<string>& lhs, const vector<string>& rhs)
+std::vector<StringIndex> remapStrings(std::vector<std::string>& lhs, const std::vector<std::string>& rhs)
{
+ using namespace std;
+
unordered_map<string, StringIndex> stringRemapping;
StringIndex stringIndex;
{
@@ -1201,6 +1210,7 @@ int compareTraceIndices(const TraceIndex& lhs, const AccumulatedTraceData& lhsDa
POTENTIALLY_UNUSED void printTrace(const AccumulatedTraceData& data, TraceIndex index)
{
+ using namespace std;
do {
const auto trace = data.findTrace(index);
const auto& ip = data.findIp(trace.ipIndex);
@@ -1228,12 +1238,12 @@ void AccumulatedTraceData::diff(const AccumulatedTraceData& base)
// step 1: sort trace indices of allocations for efficient lookup
// step 2: while at it, also merge equal allocations
- vector<TraceIndex> allocationTraceNodes;
+ std::vector<TraceIndex> allocationTraceNodes;
allocationTraceNodes.reserve(allocations.size());
for (auto it = allocations.begin(); it != allocations.end();) {
const auto& allocation = *it;
auto sortedIt =
- lower_bound(allocationTraceNodes.begin(), allocationTraceNodes.end(), allocation.traceIndex,
+ std::lower_bound(allocationTraceNodes.begin(), allocationTraceNodes.end(), allocation.traceIndex,
[this](const TraceIndex& lhs, const TraceIndex& rhs) -> bool {
return compareTraceIndices(lhs, *this, rhs, *this, identity<InstructionPointer>) < 0;
});
@@ -1307,7 +1317,7 @@ void AccumulatedTraceData::diff(const AccumulatedTraceData& base)
// copy the rhs trace index and the data it references into the lhs data,
// recursively
- function<TraceIndex(TraceIndex)> copyTrace = [this, &base, remapIpIndex,
+ std::function<TraceIndex(TraceIndex)> copyTrace = [this, &base, remapIpIndex,
&copyTrace](TraceIndex rhsIndex) -> TraceIndex {
if (!rhsIndex) {
return rhsIndex;
diff --git a/src/analyze/gui/aboutdata.cpp b/src/analyze/gui/aboutdata.cpp
new file mode 100644
index 0000000..37ce232
--- /dev/null
+++ b/src/analyze/gui/aboutdata.cpp
@@ -0,0 +1,45 @@
+#include "aboutdata.h"
+
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
+#include <KLocalizedString>
+#endif
+
+#ifdef SAMSUNG_TIZEN_BRANCH
+const QString& AboutData::Organization = "Samsung Electronics Co.";
+
+const QString& AboutData::CopyrightStatement =
+ i18n("Copyright 2015, Milian Wolff. " \
+ "Copyright 2018, Samsung Electronics Co.");
+
+const QString& AboutData::ComponentName = QStringLiteral("TizenMemoryProfiler");
+
+const QString& AboutData::ShortName = QStringLiteral("Tizen Memory Profiler");
+
+const QString& AboutData::DisplayName = i18n("Tizen Memory Profiler");
+
+// Version corresponds to CMake @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@-@HEAPTRACK_VERSION_SUFFIX@
+const QString& AboutData::Version = QStringLiteral("1.1.0-0.1");
+
+const QString& AboutData::ShortDescription = i18n("A visualizer for Tizen Memory Profiler data files");
+
+const QString& AboutData::BugAddress = "TODO"; // TODO!!
+#else
+const QString& AboutData::Organization = "Milian Wolff";
+
+const QString& AboutData::CopyrightStatement = i18n("Copyright 2015, Milian Wolff <mail@milianw.de>");
+
+const QString& AboutData::ComponentName = QStringLiteral("heaptrack_gui");
+
+const QString& AboutData::ShortName = QStringLiteral("heaptrack");
+
+const QString& AboutData::DisplayName = i18n("Heaptrack GUI");
+
+// Version corresponds to CMake @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@
+const QString& AboutData::Version = QStringLiteral("1.1.0");
+
+const QString& AboutData::ShortDescription = i18n("A visualizer for heaptrack data files");
+
+const QString& AboutData::BugAddress = QStringLiteral("mail@milianw.de");
+#endif
diff --git a/src/analyze/gui/aboutdata.h b/src/analyze/gui/aboutdata.h
new file mode 100644
index 0000000..458a78f
--- /dev/null
+++ b/src/analyze/gui/aboutdata.h
@@ -0,0 +1,24 @@
+#ifndef ABOUTDATA_H
+#define ABOUTDATA_H
+
+#include <QString>
+
+class AboutData
+{
+public:
+ const static QString& Organization;
+ const static QString& ComponentName;
+ const static QString& ShortName;
+ const static QString& DisplayName;
+ const static QString& Version;
+ const static QString& ShortDescription;
+ const static QString& CopyrightStatement;
+ const static QString& BugAddress;
+
+ static QString applicationName()
+ {
+ return ComponentName + '_' + Version;
+ }
+};
+
+#endif // ABOUTDATA_H
diff --git a/src/analyze/gui/aboutdialog.cpp b/src/analyze/gui/aboutdialog.cpp
new file mode 100644
index 0000000..fbadfdb
--- /dev/null
+++ b/src/analyze/gui/aboutdialog.cpp
@@ -0,0 +1,69 @@
+#include "aboutdialog.h"
+#include "ui_aboutdialog.h"
+
+#include "aboutdata.h"
+#include "gui_config.h"
+
+#include <math.h>
+#include <QFontMetrics>
+
+AboutDialog::AboutDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::AboutDialog)
+{
+ ui->setupUi(this);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ setWindowTitle("About " + AboutData::DisplayName);
+
+ ui->textBrowser->viewport()->setAutoFillBackground(false);
+
+ ui->textBrowser->setHtml(QString(
+ "<h2>%1 v.%2</h2>" \
+ "<p>%3.</p>")
+ .arg(AboutData::DisplayName).arg(AboutData::Version)
+ .arg(AboutData::ShortDescription) +
+ QString(
+ "<p>%1.</p>")
+ .arg(AboutData::CopyrightStatement) +
+#ifdef SAMSUNG_TIZEN_BRANCH
+ QString(
+ "<p>Based on <a href=http://milianw.de/tag/heaptrack>Heaptrack memory profiler</a> " \
+ "created by Milian Wolff on terms of " \
+ "<a href=https://www.gnu.org/licenses/lgpl-3.0.en.html>LGPL</a>.</p>") +
+#endif
+ QString(
+ "<p>Uses <a href=https://www.qt.io>Qt framework</a> v.%1 libraries on terms of " \
+ "<a href=https://www.gnu.org/licenses/lgpl-3.0.en.html>LGPL</a>.</p>")
+ .arg(QT_VERSION_STR)
+#ifdef THREAD_WEAVER
+ + QString(
+ "<p>Uses <a href=https://cgit.kde.org/threadweaver.git>ThreadWeaver library</a> " \
+ "on terms of <a href=https://www.gnu.org/licenses/lgpl-3.0.en.html>LGPL</a>.</p>")
+#endif
+#ifdef QWT_FOUND
+ + QString(
+ "<p>The application is based in part on the work of the " \
+ "<a href=http://qwt.sf.net>Qwt project</a> on terms of " \
+ "<a href=http://qwt.sourceforge.net/qwtlicense.html>Qwt License</a>.</p>")
+#endif
+#ifdef WINDOWS
+ + QString(
+ "<p>Application icon (free for commercial use): Jack Cai " \
+ "(<a href=http://www.doublejdesign.co.uk>www.doublejdesign.co.uk</a>).</p>")
+#endif
+ );
+
+ QFontMetrics fm(ui->textBrowser->font());
+ QRect rect = fm.boundingRect("The application is based in part on the work of the Qwt project on terms of");
+ int m = ui->verticalLayout->margin();
+ int h = ui->buttonBox->height();
+ int textWidth = (int)round(rect.width() * 1.05);
+ int textHeight = (int)round(rect.height() * 1.03 * 17);
+ resize(std::max(420, 2 * m + textWidth), std::max(252, 2 * m + h + textHeight));
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
diff --git a/src/analyze/gui/aboutdialog.h b/src/analyze/gui/aboutdialog.h
new file mode 100644
index 0000000..d462de2
--- /dev/null
+++ b/src/analyze/gui/aboutdialog.h
@@ -0,0 +1,22 @@
+#ifndef ABOUTDIALOG_H
+#define ABOUTDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class AboutDialog;
+}
+
+class AboutDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AboutDialog(QWidget *parent = 0);
+ ~AboutDialog();
+
+private:
+ Ui::AboutDialog *ui;
+};
+
+#endif // ABOUTDIALOG_H
diff --git a/src/analyze/gui/aboutdialog.ui b/src/analyze/gui/aboutdialog.ui
new file mode 100644
index 0000000..e3fe09e
--- /dev/null
+++ b/src/analyze/gui/aboutdialog.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>426</width>
+ <height>254</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>352</width>
+ <height>198</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string/>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/analyze/gui/callercalleemodel.cpp b/src/analyze/gui/callercalleemodel.cpp
index 5ce63d9..429f775 100644
--- a/src/analyze/gui/callercalleemodel.cpp
+++ b/src/analyze/gui/callercalleemodel.cpp
@@ -18,7 +18,13 @@
#include "callercalleemodel.h"
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include <QTextStream>
@@ -161,7 +167,7 @@ QVariant CallerCalleeModel::headerData(int section, Qt::Orientation orientation,
"called. Function symbol and file "
"information "
"may be unknown when debug information was missing when "
- "heaptrack was run.</qt>");
+ "the memory profiler was run.</qt>");
case NUM_COLUMNS:
break;
}
@@ -183,7 +189,7 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.selfCost.allocated);
} else {
- return m_format.formatByteSize(row.selfCost.allocated, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.selfCost.allocated, 1);
}
case SelfAllocationsColumn:
return static_cast<qint64>(row.selfCost.allocations);
@@ -193,7 +199,7 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.selfCost.peak);
} else {
- return m_format.formatByteSize(row.selfCost.peak, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.selfCost.peak, 1);
}
case SelfPeakInstancesColumn:
return static_cast<qint64>(row.selfCost.peak_instances);
@@ -201,13 +207,13 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.selfCost.leaked);
} else {
- return m_format.formatByteSize(row.selfCost.leaked, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.selfCost.leaked, 1);
}
case InclusiveAllocatedColumn:
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.inclusiveCost.allocated);
} else {
- return m_format.formatByteSize(row.inclusiveCost.allocated, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.inclusiveCost.allocated, 1);
}
case InclusiveAllocationsColumn:
return static_cast<qint64>(row.inclusiveCost.allocations);
@@ -217,7 +223,7 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.inclusiveCost.peak);
} else {
- return m_format.formatByteSize(row.inclusiveCost.peak, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.inclusiveCost.peak, 1);
}
case InclusivePeakInstancesColumn:
return static_cast<qint64>(row.inclusiveCost.peak_instances);
@@ -225,7 +231,7 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(row.inclusiveCost.leaked);
} else {
- return m_format.formatByteSize(row.inclusiveCost.leaked, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row.inclusiveCost.leaked, 1);
}
case FunctionColumn:
return row.location->function;
@@ -260,23 +266,23 @@ QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
stream << '\n';
stream << i18n("inclusive: allocated %1 over %2 calls (%3 temporary, i.e. "
"%4%), peak at %5, leaked %6",
- m_format.formatByteSize(row.inclusiveCost.allocated, 1, KFormat::JEDECBinaryDialect),
+ Util::formatByteSize(row.inclusiveCost.allocated, 1),
row.inclusiveCost.allocations,
row.inclusiveCost.temporary, round(float(row.inclusiveCost.temporary) * 100.f * 100.f
/ std::max(int64_t(1), row.inclusiveCost.allocations))
/ 100.f,
- m_format.formatByteSize(row.inclusiveCost.peak, 1, KFormat::JEDECBinaryDialect),
- m_format.formatByteSize(row.inclusiveCost.leaked, 1, KFormat::JEDECBinaryDialect));
+ Util::formatByteSize(row.inclusiveCost.peak, 1),
+ Util::formatByteSize(row.inclusiveCost.leaked, 1));
stream << '\n';
stream << i18n(
"self: allocated %1 over %2 calls (%3 temporary, i.e. %4%), "
"peak at %5, leaked %6",
- m_format.formatByteSize(row.selfCost.allocated, 1, KFormat::JEDECBinaryDialect),
+ Util::formatByteSize(row.selfCost.allocated, 1),
row.selfCost.allocations, row.selfCost.temporary,
round(float(row.selfCost.temporary) * 100.f * 100.f / std::max(int64_t(1), row.selfCost.allocations))
/ 100.f,
- m_format.formatByteSize(row.selfCost.peak, 1, KFormat::JEDECBinaryDialect),
- m_format.formatByteSize(row.selfCost.leaked, 1, KFormat::JEDECBinaryDialect));
+ Util::formatByteSize(row.selfCost.peak, 1),
+ Util::formatByteSize(row.selfCost.leaked, 1));
stream << '\n';
stream << "</pre></qt>";
return tooltip;
diff --git a/src/analyze/gui/callercalleemodel.h b/src/analyze/gui/callercalleemodel.h
index b824873..da5e1ba 100644
--- a/src/analyze/gui/callercalleemodel.h
+++ b/src/analyze/gui/callercalleemodel.h
@@ -22,7 +22,11 @@
#include <QAbstractTableModel>
#include <QVector>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KFormat>
+#endif
#include "../allocationdata.h"
#include "locationdata.h"
@@ -89,7 +93,9 @@ public:
private:
QVector<CallerCalleeData> m_rows;
CallerCalleeData m_maxCost;
+#ifndef NO_K_LIB
KFormat m_format;
+#endif
};
#endif // CALLERCALLEEMODEL_H
diff --git a/src/analyze/gui/charthelpwindow.cpp b/src/analyze/gui/charthelpwindow.cpp
new file mode 100644
index 0000000..204762a
--- /dev/null
+++ b/src/analyze/gui/charthelpwindow.cpp
@@ -0,0 +1,53 @@
+#include "charthelpwindow.h"
+#include "chartwidget.h"
+
+#include <math.h>
+
+#include <QTextEdit>
+#include <QToolTip>
+
+ChartHelpWindow::ChartHelpWindow(QWidget *parent) :
+ QMainWindow(parent)
+{
+ setWindowTitle("Chart Help");
+ setWindowFlags(Qt::Tool);
+ setAttribute(Qt::WA_ShowWithoutActivating);
+ setAttribute(Qt::WA_DeleteOnClose);
+ setPalette(QToolTip::palette());
+ setWindowOpacity(0.9);
+ setAutoFillBackground(true);
+ auto textEdit = new QTextEdit(this);
+ textEdit->setReadOnly(true);
+ textEdit->setContextMenuPolicy(Qt::NoContextMenu);
+ textEdit->viewport()->setAutoFillBackground(false);
+ setCentralWidget(textEdit);
+ const auto text =
+ "<p>Use <u>Context Menu</u> (right click inside the chart to open) to control different chart options.</p>" \
+ "<p>Use <u>left mouse button</u> to <b>zoom in</b> to selection: press the button, drag " \
+ "to make a rectangular selection, release.</p>" \
+ "<p>Use <u>left mouse button</u> with modifier keys to:</p>" \
+ "<ul>" \
+ "<li><b>zoom out</b> (one step back) - <b>&lt;Shift&gt;</b>+click;" \
+ "<li><b>reset zoom</b> - <b>&lt;Ctrl&gt;</b>+click;" \
+ "<li><b>move (pan)</b> the chart - <b>&lt;Alt&gt;</b>+drag." \
+ "</ul>";
+ textEdit->setHtml(text);
+ QFontMetrics fm(textEdit->font());
+ QRect rect = fm.boundingRect("Use left mouse button to zoom in to selection: press the ");
+ int textWidth = std::max(292, (int)round(rect.width() * 1.03));
+ int textHeight = std::max(164, (int)round(rect.height() * 1.03 * 12));
+ setGeometry(0, 0, textWidth, textHeight);
+ setMinimumSize(120, 80);
+ setMaximumSize(std::max(400, textWidth * 2), std::max(280, textHeight * 2));
+}
+
+ChartHelpWindow::~ChartHelpWindow()
+{
+ ChartWidget::HelpWindow = nullptr;
+}
+
+void ChartHelpWindow::closeEvent(QCloseEvent *event)
+{
+ QMainWindow::closeEvent(event);
+ ChartOptions::GlobalOptions = ChartOptions::setOption(ChartOptions::GlobalOptions, ChartOptions::ShowHelp, false);
+}
diff --git a/src/analyze/gui/charthelpwindow.h b/src/analyze/gui/charthelpwindow.h
new file mode 100644
index 0000000..f1cab9b
--- /dev/null
+++ b/src/analyze/gui/charthelpwindow.h
@@ -0,0 +1,19 @@
+#ifndef HELPWIDGET_H
+#define HELPWIDGET_H
+
+#include <QMainWindow>
+#include <QWidget>
+
+class ChartHelpWindow: public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit ChartHelpWindow(QWidget *parent = 0);
+ ~ChartHelpWindow();
+
+protected:
+ virtual void closeEvent(QCloseEvent *event) override;
+};
+
+#endif // HELPWIDGET_H
diff --git a/src/analyze/gui/chartmodel.cpp b/src/analyze/gui/chartmodel.cpp
index 1db0ac9..45d34db 100644
--- a/src/analyze/gui/chartmodel.cpp
+++ b/src/analyze/gui/chartmodel.cpp
@@ -18,16 +18,26 @@
#include "chartmodel.h"
+#include "gui_config.h"
+
+#ifdef KChart_FOUND
#include <KChartGlobal>
#include <KChartLineAttributes>
+#endif
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KFormat>
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include <QBrush>
#include <QDebug>
#include <QPen>
-#include "util.h"
+#include <algorithm>
namespace {
QColor colorForColumn(int column, int columnCount)
@@ -52,14 +62,20 @@ ChartModel::Type ChartModel::type() const
QVariant ChartModel::headerData(int section, Qt::Orientation orientation, int role) const
{
- Q_ASSERT(orientation == Qt::Horizontal || section < columnCount());
+//!! Q_ASSERT(orientation == Qt::Horizontal || section < columnCount());
+ if (!((orientation == Qt::Horizontal || section < columnCount())))
+ {
+ return {};
+ }
+
if (orientation == Qt::Horizontal) {
+#ifdef KChart_FOUND
if (role == KChart::DatasetPenRole) {
return QVariant::fromValue(m_columnDataSetPens.at(section));
} else if (role == KChart::DatasetBrushRole) {
return QVariant::fromValue(m_columnDataSetBrushes.at(section));
}
-
+#endif
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
if (section == 0) {
return i18n("Elapsed Time");
@@ -90,6 +106,7 @@ QVariant ChartModel::data(const QModelIndex& index, int role) const
Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent()));
Q_ASSERT(!index.parent().isValid());
+#ifdef KChart_FOUND
if (role == KChart::LineAttributesRole) {
KChart::LineAttributes attributes;
attributes.setDisplayArea(true);
@@ -106,6 +123,7 @@ QVariant ChartModel::data(const QModelIndex& index, int role) const
} else if (role == KChart::DatasetBrushRole) {
return QVariant::fromValue(m_columnDataSetBrushes.at(index.column()));
}
+#endif
if (role != Qt::DisplayRole && role != Qt::ToolTipRole) {
return {};
@@ -125,52 +143,56 @@ QVariant ChartModel::data(const QModelIndex& index, int role) const
const QString time = Util::formatTime(data.timeStamp);
auto byteCost = [cost]() -> QString
{
- KFormat format;
- const auto formatted = format.formatByteSize(cost, 1, KFormat::JEDECBinaryDialect);
+ const auto formatted = Util::formatByteSize(cost, 1);
if (cost > 1024) {
return i18nc("%1: the formatted byte size, e.g. \"1.2KB\", %2: the raw byte size, e.g. \"1300\"",
- "%1 (%2 bytes)", formatted, cost);
+ "<b>%1</b> (%2 bytes)", formatted, cost);
} else {
- return formatted;
+ return QString("<b>%1</b>").arg(formatted);
}
};
if (column == 0) {
switch (m_type) {
case Allocations:
- return i18n("<qt>%1 allocations in total after %2</qt>", cost, time);
+ return i18n("<qt><b>%1</b> allocations in total after <b>%2</b></qt>", cost, time);
case Temporary:
- return i18n("<qt>%1 temporary allocations in total after %2</qt>", cost, time);
+ return i18n("<qt><b>%1</b> temporary allocations in total after <b>%2</b></qt>", cost, time);
case Instances:
- return i18n("<qt>%1 number of instances in total after %2</qt>", cost, time);
+ return i18n("<qt><b>%1</b> number of instances in total after <b>%2</b></qt>", cost, time);
case Consumed:
- return i18n("<qt>%1 consumed in total after %2</qt>",
+ return i18n("<qt>%1 consumed in total after <b>%2</b></qt>",
byteCost(), time);
case Allocated:
- return i18n("<qt>%1 allocated in total after %2</qt>",
+ return i18n("<qt>%1 allocated in total after <b>%2</b></qt>",
byteCost(), time);
}
} else {
- const auto label = m_data.labels.value(column).toHtmlEscaped();
+ auto label = m_data.labels.value(column);
+#ifdef QWT_FOUND
+ label = Util::wrapLabel(label, 96, 0, "&nbsp;<br>");
+#else
+ label = label.toHtmlEscaped(); // Qt wraps text in tooltips itself
+#endif
switch (m_type) {
case Allocations:
- return i18n("<qt>%2 allocations after %3 from:<p "
- "style='margin-left:10px;'>%1</p></qt>",
+ return i18n("<qt><b>%2</b> allocations after <b>%3</b> from: "
+ "<p style='margin-left:10px;'>%1</p></qt>",
label, cost, time);
case Temporary:
- return i18n("<qt>%2 temporary allocations after %3 from:<p "
- "style='margin-left:10px'>%1</p></qt>",
+ return i18n("<qt><b>%2</b> temporary allocations after <b>%3</b> from: "
+ "<p style='margin-left:10px'>%1</p></qt>",
label, cost, time);
case Instances:
- return i18n("<qt>%2 number of instances after %3 from:<p "
- "style='margin-left:10px'>%1</p></qt>",
+ return i18n("<qt><b>%2</b> number of instances after <b>%3</b> from: "
+ "<p style='margin-left:10px'>%1</p></qt>",
label, cost, time);
case Consumed:
- return i18n("<qt>%2 consumed after %3 from:<p "
- "style='margin-left:10px'>%1</p></qt>",
+ return i18n("<qt>%2 consumed after <b>%3</b> from: "
+ "<p style='margin-left:10px'>%1</p></qt>",
label, byteCost(), time);
case Allocated:
- return i18n("<qt>%2 allocated after %3 from:<p "
- "style='margin-left:10px'>%1</p></qt>",
+ return i18n("<qt>%2 allocated after <b>%3</b> from: "
+ "<p style='margin-left:10px'>%1</p></qt>",
label, byteCost(), time);
}
}
@@ -199,11 +221,21 @@ void ChartModel::resetData(const ChartData& data)
Q_ASSERT(m_data.labels.size() < ChartRows::MAX_NUM_COST);
beginResetModel();
m_data = data;
+ m_timestamps.clear();
+ const int rows = rowCount();
+ m_timestamps.reserve(rows);
+ for (int row = 0; row < rows; ++row)
+ {
+ m_timestamps.append(m_data.rows[row].timeStamp);
+ }
m_columnDataSetBrushes.clear();
m_columnDataSetPens.clear();
- const auto columns = columnCount();
+ const int columns = columnCount();
for (int i = 0; i < columns; ++i) {
auto color = colorForColumn(i, columns);
+#ifdef QWT_FOUND
+ color.setAlpha(i > 1 ? 127 : 50);
+#endif
m_columnDataSetBrushes << QBrush(color);
m_columnDataSetPens << QPen(color);
}
@@ -214,7 +246,55 @@ void ChartModel::clearData()
{
beginResetModel();
m_data = {};
+ m_timestamps = {};
m_columnDataSetBrushes = {};
m_columnDataSetPens = {};
endResetModel();
}
+
+qint64 ChartModel::getTimestamp(int row) const
+{
+ return m_timestamps[row];
+}
+
+qint64 ChartModel::getCost(int row, int column) const
+{
+ return m_data.rows[row].cost[column / 2];
+}
+
+QString ChartModel::getColumnLabel(int column) const
+{
+ return m_data.labels.value(column / 2);
+}
+
+const QPen& ChartModel::getColumnDataSetPen(int column) const
+{
+ return m_columnDataSetPens[column];
+}
+
+const QBrush& ChartModel::getColumnDataSetBrush(int column) const
+{
+ return m_columnDataSetBrushes[column];
+}
+
+int ChartModel::getRowForTimestamp(qreal timestamp) const
+{
+ // check if 'timestamp' is not greater than the maximum available timestamp
+ int lastIndex = m_timestamps.size() - 1;
+ if (!((lastIndex >= 0) && (timestamp <= m_timestamps[lastIndex])))
+ {
+ return -1;
+ }
+ int result;
+ // find the first element that is greater than 'timestamp'
+ auto up_it = std::upper_bound(m_timestamps.begin(), m_timestamps.end(), (qint64)timestamp);
+ if (up_it != m_timestamps.end())
+ {
+ result = up_it - m_timestamps.begin() - 1;
+ }
+ else // all elements are not greater than 'timestamp'
+ {
+ result = lastIndex; // due to check in the beginning of the function
+ }
+ return result;
+}
diff --git a/src/analyze/gui/chartmodel.h b/src/analyze/gui/chartmodel.h
index eee5f35..74900c5 100644
--- a/src/analyze/gui/chartmodel.h
+++ b/src/analyze/gui/chartmodel.h
@@ -71,6 +71,19 @@ public:
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+ qint64 getTimestamp(int row) const;
+ qint64 getCost(int row, int column) const;
+ QString getColumnLabel(int column) const;
+ const QPen& getColumnDataSetPen(int column) const;
+ const QBrush& getColumnDataSetBrush(int column) const;
+
+ // get an index of the chart row which timestamp is less than or equal to 'timestamp'
+ // and also it's the maximum from all such timestamps (i.e. the row's timestamp is the
+ // nearest to 'timestamp' to the left or is equal to 'timestamp') - if 'timestamp' is
+ // not less than the minimum timestamp and not greater than the maximum timestamp
+ // of all rows, otherwise return -1
+ int getRowForTimestamp(qreal timestamp) const;
+
public slots:
void resetData(const ChartData& data);
void clearData();
@@ -78,6 +91,7 @@ public slots:
private:
ChartData m_data;
Type m_type;
+ QVector<qint64> m_timestamps;
// we cache the pens and brushes as constructing them requires allocations
// otherwise
QVector<QPen> m_columnDataSetPens;
diff --git a/src/analyze/gui/chartmodel2qwtseriesdata.cpp b/src/analyze/gui/chartmodel2qwtseriesdata.cpp
new file mode 100644
index 0000000..24f4435
--- /dev/null
+++ b/src/analyze/gui/chartmodel2qwtseriesdata.cpp
@@ -0,0 +1,43 @@
+#include "chartmodel2qwtseriesdata.h"
+
+ChartModel2QwtSeriesData::ChartModel2QwtSeriesData(ChartModel *model, int column)
+ : m_model(model), m_column(column)
+{
+ updateBoundingRect();
+}
+
+void ChartModel2QwtSeriesData::updateBoundingRect()
+{
+ int rows = m_model->rowCount();
+ if (rows > 0)
+ {
+ m_boundingRect.setLeft(m_model->getTimestamp(0));
+ m_boundingRect.setTop(m_model->getCost(0, m_column));
+ m_boundingRect.setRight(m_model->getTimestamp(rows - 1));
+ qint64 maxCost = -1;
+ for (int row = 0; row < rows; ++row)
+ {
+ qint64 cost = m_model->getCost(row, m_column);
+ if (cost > maxCost)
+ {
+ maxCost = cost;
+ }
+ }
+ m_boundingRect.setBottom(maxCost);
+ }
+ else
+ {
+ m_boundingRect = {};
+ }
+}
+
+QPointF ChartModel2QwtSeriesData::sample(size_t i) const
+{
+ int row = (int)i;
+ return QPointF(m_model->getTimestamp(row), m_model->getCost(row, m_column));
+}
+
+QRectF ChartModel2QwtSeriesData::boundingRect() const
+{
+ return m_boundingRect;
+}
diff --git a/src/analyze/gui/chartmodel2qwtseriesdata.h b/src/analyze/gui/chartmodel2qwtseriesdata.h
new file mode 100644
index 0000000..968843c
--- /dev/null
+++ b/src/analyze/gui/chartmodel2qwtseriesdata.h
@@ -0,0 +1,29 @@
+#ifndef CHARTMODEL2QWTSERIESDATA_H
+#define CHARTMODEL2QWTSERIESDATA_H
+
+#include "chartmodel.h"
+#include "qwt_series_data.h"
+
+class ChartModel2QwtSeriesData : public QwtSeriesData<QPointF>
+{
+public:
+ ChartModel2QwtSeriesData(ChartModel *model, int column);
+
+ size_t size() const override
+ {
+ return m_model->rowCount();
+ }
+
+ QPointF sample(size_t i) const override;
+
+ virtual QRectF boundingRect() const override;
+
+private:
+ void updateBoundingRect();
+
+ ChartModel *m_model;
+ int m_column;
+ QRectF m_boundingRect;
+};
+
+#endif // CHARTMODEL2QWTSERIESDATA_H
diff --git a/src/analyze/gui/chartwidget.cpp b/src/analyze/gui/chartwidget.cpp
index fbbc05d..ddc1c4a 100644
--- a/src/analyze/gui/chartwidget.cpp
+++ b/src/analyze/gui/chartwidget.cpp
@@ -18,8 +18,10 @@
#include "chartwidget.h"
+#include <QMainWindow>
#include <QVBoxLayout>
+#if defined(KChart_FOUND)
#include <KChartChart>
#include <KChartPlotter>
@@ -30,15 +32,28 @@
#include <KChartGridAttributes>
#include <KChartHeaderFooter>
#include <KChartLegend>
+#elif defined(QWT_FOUND)
+#include "charthelpwindow.h"
+#include <algorithm>
+#include <QAction>
+#include <QContextMenuEvent>
+#include <QMenu>
+#endif
+
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KColorScheme>
#include <KFormat>
#include <KLocalizedString>
+#endif
#include "chartmodel.h"
#include "chartproxy.h"
#include "util.h"
+#ifdef KChart_FOUND
using namespace KChart;
namespace {
@@ -68,30 +83,88 @@ public:
const QString customizedLabel(const QString& label) const override
{
- KFormat format(QLocale::system());
- return format.formatByteSize(label.toDouble(), 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(label.toDouble(), 1);
}
};
}
+#elif defined(QWT_FOUND)
+QWidget* ChartWidget::HelpWindow;
+QWidget* ChartWidget::MainWindow;
+#endif // QWT_FOUND
ChartWidget::ChartWidget(QWidget* parent)
: QWidget(parent)
+#if defined(KChart_FOUND)
, m_chart(new Chart(this))
+#elif defined(QWT_FOUND)
+ , m_plot(new ChartWidgetQwtPlot(this, ChartOptions::GlobalOptions))
+ , m_contextMenuQwt(new ContextMenuQwt(this, false))
+#endif
+#ifdef SHOW_TABLES
+ , m_tableViewTotal(new QTableView(this))
+ , m_tableViewNoTotal(new QTableView(this))
+#endif
{
auto layout = new QVBoxLayout(this);
+#if defined(KChart_FOUND)
layout->addWidget(m_chart);
+#elif defined(QWT_FOUND)
+ layout->addWidget(m_plot);
+
+ connectContextMenu();
+#endif
+#ifdef SHOW_TABLES
+ auto hLayout = new QHBoxLayout();
+ hLayout->addWidget(m_tableViewTotal);
+ hLayout->addWidget(m_tableViewNoTotal);
+ layout->addLayout(hLayout);
+ layout->setStretch(0, 100);
+ layout->setStretch(1, 100);
+#endif
setLayout(layout);
+#ifdef KChart_FOUND
auto* coordinatePlane = dynamic_cast<CartesianCoordinatePlane*>(m_chart->coordinatePlane());
Q_ASSERT(coordinatePlane);
coordinatePlane->setRubberBandZoomingEnabled(true);
coordinatePlane->setAutoAdjustGridToZoom(true);
+#endif
}
ChartWidget::~ChartWidget() = default;
+#ifdef QWT_FOUND
+void ChartWidget::updateOnSelected(QWidget *mainWindow)
+{
+ MainWindow = mainWindow;
+ m_plot->setOptions(ChartOptions::GlobalOptions);
+ if (m_plot->hasOption(ChartOptions::ShowHelp))
+ {
+ showHelp();
+ }
+}
+
+void ChartWidget::connectContextMenu()
+{
+ connect(m_contextMenuQwt->resetZoomAction(), &QAction::triggered, this, &ChartWidget::resetZoom);
+ connect(m_contextMenuQwt->showTotalAction(), &QAction::triggered, this, &ChartWidget::toggleShowTotal);
+ connect(m_contextMenuQwt->showUnresolvedAction(), &QAction::triggered, this, &ChartWidget::toggleShowUnresolved);
+ connect(m_contextMenuQwt->showLegendAction(), &QAction::triggered, this, &ChartWidget::toggleShowLegend);
+ connect(m_contextMenuQwt->showCurveBordersAction(), &QAction::triggered, this, &ChartWidget::toggleShowCurveBorders);
+ connect(m_contextMenuQwt->showSymbolsAction(), &QAction::triggered, this, &ChartWidget::toggleShowSymbols);
+ connect(m_contextMenuQwt->showVLinesAction(), &QAction::triggered, this, &ChartWidget::toggleShowVLines);
+ connect(m_contextMenuQwt->exportChartAction(), &QAction::triggered, this, [=]() {
+ Util::exportChart(this, *m_plot, m_plot->model()->headerData(1, Qt::Horizontal).toString());
+ });
+ connect(m_contextMenuQwt->showHelpAction(), &QAction::triggered, this, &ChartWidget::toggleShowHelp);
+
+ setFocusPolicy(Qt::StrongFocus);
+}
+#endif
+
void ChartWidget::setModel(ChartModel* model, bool minimalMode)
{
+#ifdef KChart_FOUND
auto* coordinatePlane = dynamic_cast<CartesianCoordinatePlane*>(m_chart->coordinatePlane());
Q_ASSERT(coordinatePlane);
foreach (auto diagram, coordinatePlane->diagrams()) {
@@ -104,6 +177,7 @@ void ChartWidget::setModel(ChartModel* model, bool minimalMode)
grid.setSubGridVisible(false);
coordinatePlane->setGlobalGridAttributes(grid);
}
+#endif
switch (model->type()) {
case ChartModel::Consumed:
@@ -113,31 +187,40 @@ void ChartWidget::setModel(ChartModel* model, bool minimalMode)
setToolTip(i18n("<qt>Shows the number of instances allocated from specific functions over time.</qt>"));
break;
case ChartModel::Allocated:
- setToolTip(i18n("<qt>Displays total memory allocated over time. "
+ setToolTip(i18n("<qt>Displays the total memory allocated over time. "
"This value ignores deallocations and just measures heap "
"allocation throughput.</qt>"));
break;
case ChartModel::Allocations:
- setToolTip(i18n("<qt>Shows number of memory allocations over time.</qt>"));
+ setToolTip(i18n("<qt>Shows the number of memory allocations over time.</qt>"));
break;
case ChartModel::Temporary:
- setToolTip(i18n("<qt>Shows number of temporary memory allocations over time. "
+ setToolTip(i18n("<qt>Shows the number of temporary memory allocations over time. "
"A temporary allocation is one that is followed immediately by its "
"corresponding deallocation, without other allocations happening "
"in-between.</qt>"));
break;
}
+#if defined(KChart_FOUND) || defined(SHOW_TABLES)
+ ChartProxy *totalProxy, *proxy;
+#endif
+#if defined(KChart_FOUND)
{
auto totalPlotter = new Plotter(this);
totalPlotter->setAntiAliasing(true);
- auto totalProxy = new ChartProxy(true, this);
+ totalProxy = new ChartProxy(true, this);
totalProxy->setSourceModel(model);
totalPlotter->setModel(totalProxy);
totalPlotter->setType(Plotter::Stacked);
+#ifdef NO_K_LIB
+ QPalette pal;
+ const QPen foreground(pal.color(QPalette::Active, QPalette::Foreground));
+#else
KColorScheme scheme(QPalette::Active, KColorScheme::Window);
const QPen foreground(scheme.foreground().color());
+#endif
auto bottomAxis = new TimeAxis(totalPlotter);
auto axisTextAttributes = bottomAxis->textAttributes();
axisTextAttributes.setPen(foreground);
@@ -176,11 +259,26 @@ void ChartWidget::setModel(ChartModel* model, bool minimalMode)
plotter->setAntiAliasing(true);
plotter->setType(Plotter::Stacked);
- auto proxy = new ChartProxy(false, this);
+ proxy = new ChartProxy(false, this);
proxy->setSourceModel(model);
plotter->setModel(proxy);
coordinatePlane->addDiagram(plotter);
}
+#elif defined(QWT_FOUND)
+ connect(model, SIGNAL(modelReset()), this, SLOT(modelReset()));
+ m_plot->setModel(model);
+#ifdef SHOW_TABLES
+ totalProxy = new ChartProxy(true, this);
+ totalProxy->setSourceModel(model);
+
+ proxy = new ChartProxy(false, this);
+ proxy->setSourceModel(model);
+#endif // SHOW_TABLES
+#endif // QWT_FOUND, KChart_FOUND
+#ifdef SHOW_TABLES
+ m_tableViewTotal->setModel(totalProxy);
+ m_tableViewNoTotal->setModel(proxy);
+#endif
}
QSize ChartWidget::sizeHint() const
@@ -188,4 +286,99 @@ QSize ChartWidget::sizeHint() const
return {400, 50};
}
+#ifdef QWT_FOUND
+void ChartWidget::modelReset()
+{
+ m_plot->rebuild(true);
+}
+
+#ifndef QT_NO_CONTEXTMENU
+void ChartWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+ QMenu menu(this);
+ m_plot->setOption(ChartOptions::ShowHelp,
+ ChartOptions::hasOption(ChartOptions::GlobalOptions, ChartOptions::ShowHelp));
+ m_contextMenuQwt->initializeMenu(menu, m_plot->options(), m_plot->isEmpty());
+ menu.exec(event->globalPos());
+}
+#endif // QT_NO_CONTEXTMENU
+
+void ChartWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (!m_contextMenuQwt->handleKeyPress(event))
+ {
+ QWidget::keyPressEvent(event);
+ }
+}
+
+void ChartWidget::resetZoom()
+{
+ m_plot->resetZoom();
+}
+
+void ChartWidget::toggleShowTotal()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowTotal);
+}
+
+void ChartWidget::toggleShowUnresolved()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowUnresolved);
+}
+
+void ChartWidget::toggleShowLegend()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowLegend);
+}
+
+void ChartWidget::toggleShowCurveBorders()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowCurveBorders);
+}
+
+void ChartWidget::toggleShowSymbols()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowSymbols);
+}
+
+void ChartWidget::toggleShowVLines()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowVLines);
+}
+
+void ChartWidget::toggleShowHelp()
+{
+ bool checked = !ChartOptions::hasOption(ChartOptions::GlobalOptions, ChartOptions::ShowHelp);
+ if (checked)
+ {
+ showHelp();
+ }
+ else
+ {
+ if (HelpWindow != nullptr)
+ {
+ delete HelpWindow;
+ HelpWindow = nullptr;
+ }
+ }
+ ChartOptions::GlobalOptions = ChartOptions::setOption(ChartOptions::GlobalOptions,
+ ChartOptions::ShowHelp, checked);
+}
+
+void ChartWidget::showHelp()
+{
+ if (HelpWindow == nullptr)
+ {
+ HelpWindow = new ChartHelpWindow(MainWindow);
+ QPoint p = mapToGlobal(pos());
+ HelpWindow->move(p.x() + 32, p.y() + 32);
+ }
+ HelpWindow->show();
+}
+#endif // QWT_FOUND
+
+#ifdef KChart_FOUND
+// build errors occur in some environments if including .moc unconditionally
+// (e.g. Qt 5.11.0 MSVC2017 64bit, Release build only)
#include "chartwidget.moc"
+#endif
diff --git a/src/analyze/gui/chartwidget.h b/src/analyze/gui/chartwidget.h
index dcf64ea..fbca5d6 100644
--- a/src/analyze/gui/chartwidget.h
+++ b/src/analyze/gui/chartwidget.h
@@ -19,13 +19,29 @@
#ifndef CHARTWIDGET_H
#define CHARTWIDGET_H
+#include "gui_config.h"
+
+#include <memory>
#include <QWidget>
+//!! for debugging
+//#define SHOW_TABLES
+
+#ifdef SHOW_TABLES
+#include <QTableView>
+#endif
+
class ChartModel;
+#if defined(KChart_FOUND)
namespace KChart {
class Chart;
}
+#elif defined(QWT_FOUND)
+#include "chartwidgetqwtplot.h"
+#include "contextmenuqwt.h"
+class QAction;
+#endif
class QAbstractItemModel;
@@ -40,8 +56,47 @@ public:
QSize sizeHint() const override;
+#ifdef QWT_FOUND
+ void updateOnSelected(QWidget *mainWindow);
+
+ static QWidget* HelpWindow;
+ static QWidget* MainWindow;
+
+public slots:
+ void modelReset();
+protected:
+#ifndef QT_NO_CONTEXTMENU
+ virtual void contextMenuEvent(QContextMenuEvent *event) override;
+#endif
+ // workaround for handling the context menu shortcuts
+ virtual void keyPressEvent(QKeyEvent *event) override;
+#endif // QWT_FOUND
+
private:
+#if defined(KChart_FOUND)
KChart::Chart* m_chart;
+#elif defined(QWT_FOUND)
+private slots:
+ void resetZoom();
+ void toggleShowTotal();
+ void toggleShowUnresolved();
+ void toggleShowLegend();
+ void toggleShowCurveBorders();
+ void toggleShowSymbols();
+ void toggleShowVLines();
+ void toggleShowHelp();
+private:
+ void connectContextMenu();
+
+ void showHelp();
+
+ ChartWidgetQwtPlot* m_plot;
+ std::unique_ptr<ContextMenuQwt> m_contextMenuQwt;
+#endif // QWT_FOUND, KChart_FOUND
+#ifdef SHOW_TABLES
+ QTableView* m_tableViewTotal;
+ QTableView* m_tableViewNoTotal;
+#endif
};
#endif // CHARTWIDGET_H
diff --git a/src/analyze/gui/chartwidgetqwtplot.cpp b/src/analyze/gui/chartwidgetqwtplot.cpp
new file mode 100644
index 0000000..44dfd0a
--- /dev/null
+++ b/src/analyze/gui/chartwidgetqwtplot.cpp
@@ -0,0 +1,433 @@
+#include "chartwidgetqwtplot.h"
+#include "chartmodel.h"
+#include "chartmodel2qwtseriesdata.h"
+#include "util.h"
+
+#include <qwt_legend.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_grid.h>
+#include <qwt_plot_marker.h>
+#include <qwt_plot_panner.h>
+#include <qwt_plot_textlabel.h>
+#include <qwt_plot_zoomer.h>
+#include <qwt_scale_draw.h>
+#include <qwt_symbol.h>
+
+#include <QToolTip>
+
+#include <limits>
+
+ChartOptions::Options ChartOptions::GlobalOptions(
+ ChartOptions::ShowHelp |
+ ChartOptions::ShowTotal | ChartOptions::ShowUnresolved |
+ ChartOptions::ShowLegend | ChartOptions::ShowCurveBorders);
+
+class TimeScaleDraw: public QwtScaleDraw
+{
+ virtual QwtText label(double value) const
+ {
+ return Util::formatTime((qint64)value);
+ }
+};
+
+class SizeScaleDraw: public QwtScaleDraw
+{
+ virtual QwtText label(double value) const
+ {
+ return Util::formatByteSize(value);
+ }
+};
+
+class Zoomer: public QwtPlotZoomer
+{
+public:
+ Zoomer(ChartWidgetQwtPlot *plot)
+ : QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yRight, plot->canvas()),
+ m_plot(plot)
+ {
+// setRubberBandPen(QColor(Qt::darkGreen));
+ setRubberBandPen(QColor(0, 0x60, 0));
+ setTrackerMode(QwtPlotPicker::AlwaysOn);
+ }
+
+protected:
+ virtual QwtText trackerTextF(const QPointF &pos) const
+ {
+// qDebug() << "Zoomer: (" << pos.x() << "; " << pos.y() << ")";
+ if ((pos.x() < 0) || (pos.y() < 0))
+ {
+ return {};
+ }
+ QwtText text;
+ text.setRenderFlags((text.renderFlags() & ~Qt::AlignHorizontal_Mask) | Qt::AlignLeft);
+ text.setBorderRadius(6);
+ QString tooltip;
+ if (m_plot->getCurveTooltip(pos, tooltip))
+ {
+ text.setText("<p style='margin-left:4px'>" + tooltip + "</p> ");
+ text.setColor(Qt::white);
+ QPen bandPen = rubberBandPen();
+ text.setBorderPen(bandPen);
+ QColor c = bandPen.color();
+ c.setAlpha(170);
+ text.setBackgroundBrush(c);
+ m_plot->clearTooltip();
+ }
+ else // show default text
+ {
+ QString value;
+ if (m_plot->isSizeModel())
+ {
+ value = Util::formatByteSize(pos.y());
+ }
+ else
+ {
+ value = QString::number((qint64)pos.y());
+ }
+ text.setText(QString("<p style='margin-left:4px'><b>%1 : %2</b></p> ")
+ .arg(Util::formatTime((qint64)pos.x())).arg(value));
+ text.setColor(Qt::yellow);
+ static QPen bluePen(QColor(0, 0, 0xA0));
+ text.setBorderPen(bluePen);
+ QColor c = bluePen.color();
+ c.setAlpha(190);
+ text.setBackgroundBrush(c);
+ m_plot->restoreTooltip();
+ }
+ return text;
+ }
+private:
+ ChartWidgetQwtPlot *m_plot;
+};
+
+ChartOptions::Options ChartOptions::setOption(Options options, Options option, bool isOn)
+{
+ return (isOn ? (options | option) : Options(options & ~option));
+}
+
+ChartOptions::Options ChartOptions::setOption(Options option, bool isOn)
+{
+ setOptions(ChartOptions::setOption(m_options, option, isOn));
+ return m_options;
+}
+
+ChartOptions::Options ChartOptions::toggleOption(Options option)
+{
+ return setOption(option, !hasOption(option));
+}
+
+ChartWidgetQwtPlot::ChartWidgetQwtPlot(QWidget *parent, Options options)
+ : QwtPlot(parent), ChartOptions(options), m_model(nullptr), m_isSizeModel(false),
+ m_zoomer(nullptr), m_panner(nullptr)
+{
+ setCanvasBackground(Qt::white);
+ enableAxis(QwtPlot::yLeft, false);
+
+ m_vLinePen.setStyle(Qt::DashLine);
+ m_vLinePen.setColor(Qt::gray);
+}
+
+void ChartWidgetQwtPlot::setModel(ChartModel* model)
+{
+ m_model = model;
+ m_isSizeModel = (model != nullptr) &&
+ !(model->type() == ChartModel::Allocations ||
+ model->type() == ChartModel::Instances ||
+ model->type() == ChartModel::Temporary);
+}
+
+void ChartWidgetQwtPlot::setOptions(Options options)
+{
+ if (m_options != options)
+ {
+ bool oldShowTotal = hasOption(ShowTotal);
+ bool oldShowUnresolved = hasOption(ShowUnresolved);
+ m_options = options;
+ rebuild(oldShowTotal != hasOption(ShowTotal) || (oldShowUnresolved != hasOption(ShowUnresolved)));
+ }
+}
+
+void ChartWidgetQwtPlot::rebuild(bool resetZoomAndPan)
+{
+ const int MaxTitleLineLength = 48;
+
+ detachItems();
+
+ if (resetZoomAndPan)
+ {
+ if (!m_xScaleDiv.isEmpty())
+ {
+ setAxisScaleDiv(QwtPlot::xBottom, m_xScaleDiv);
+ }
+ if (!m_yScaleDiv.isEmpty())
+ {
+ setAxisScaleDiv(QwtPlot::yRight, m_yScaleDiv);
+ }
+ setAxisAutoScale(QwtPlot::xBottom, true);
+ setAxisAutoScale(QwtPlot::yRight, true);
+ }
+
+ if (!m_model)
+ {
+ return;
+ }
+
+ insertLegend(hasOption(ShowLegend) ? new QwtLegend() : nullptr);
+
+ setAxisTitle(QwtPlot::xBottom, m_model->headerData(0).toString());
+ setAxisTitle(QwtPlot::yRight, m_model->headerData(1).toString());
+ setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
+ if (m_isSizeModel)
+ {
+ setAxisScaleDraw(QwtPlot::yRight, new SizeScaleDraw());
+ }
+
+ int column = 1;
+ if (!hasOption(ShowTotal))
+ {
+ column += 2;
+ }
+ int curvesCount = 0;
+ int curvesAdded = 0;
+ int unresolvedCount = 0;
+ int columns = m_model->columnCount();
+ for (; column < columns; column += 2)
+ {
+ ++curvesCount;
+ QString columnLabel = m_model->getColumnLabel(column);
+ if (!hasOption(ShowUnresolved) &&
+ Util::isUnresolvedFunction(columnLabel)) // column label starts with a function name
+ {
+ ++unresolvedCount;
+ continue;
+ }
+
+ auto adapter = new ChartModel2QwtSeriesData(m_model, column);
+
+ QRectF bounds = adapter->boundingRect();
+ qint64 maxCost = bounds.bottom();
+
+ QString titleEnd = QString(" (max=<b>%1</b>)").arg(m_isSizeModel
+ ? Util::formatByteSize(maxCost) : QString::number(maxCost));
+ int lastLineExtra = titleEnd.length() - 2 * 3; // minus the two "<b>" tags length
+ auto curve = new QwtPlotCurve(
+ Util::wrapLabel(columnLabel, MaxTitleLineLength, lastLineExtra) +
+ titleEnd);
+ curve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
+ curve->setYAxis(QwtPlot::yRight);
+
+ static QPen blackPen;
+
+ curve->setPen(hasOption(ShowCurveBorders)
+ ? blackPen : m_model->getColumnDataSetPen(column - 1));
+
+ curve->setBrush(m_model->getColumnDataSetBrush(column - 1));
+
+ curve->setSamples(adapter);
+
+ if (hasOption(ShowSymbols))
+ {
+ QwtSymbol *symbol = new QwtSymbol(QwtSymbol::Ellipse,
+ QBrush(Qt::white), QPen(Qt::black, 2), QSize(6, 6));
+ curve->setSymbol(symbol);
+ }
+
+ curve->attach(this);
+
+ ++curvesAdded;
+ }
+
+ if (curvesAdded > 0)
+ {
+ enableAxis(QwtPlot::xBottom);
+ enableAxis(QwtPlot::yRight);
+
+ auto grid = new QwtPlotGrid();
+ grid->setPen(QPen(Qt::lightGray));
+ grid->attach(this);
+
+ if (hasOption(ShowVLines))
+ {
+ int rows = m_model->rowCount();
+ for (int row = 1; row < rows; ++row)
+ {
+ auto marker = new QwtPlotMarker();
+ marker->setLinePen(m_vLinePen);
+ marker->setLineStyle(QwtPlotMarker::VLine);
+ marker->setXValue(m_model->getTimestamp(row));
+ marker->attach(this);
+ }
+ }
+
+ if (!m_zoomer)
+ {
+ m_zoomer = new Zoomer(this);
+ // LeftButton for zooming
+ // Shift+LeftButton: zoom out by 1
+ // Ctrl+LeftButton: zoom out to full size
+ m_zoomer->setMousePattern(QwtEventPattern::MouseSelect2, Qt::LeftButton, Qt::ControlModifier);
+ m_zoomer->setMousePattern(QwtEventPattern::MouseSelect3, Qt::LeftButton, Qt::ShiftModifier);
+ }
+
+ if (!m_panner)
+ {
+ m_panner = new QwtPlotPanner(canvas());
+ // Alt+LeftButton for panning
+ m_panner->setMouseButton(Qt::LeftButton, Qt::AltModifier);
+ }
+ }
+ else
+ {
+ QString hint = QString("<h2>Nothing to display.</h2>");
+ if (curvesCount > 0)
+ {
+ QString reason;
+ if (!hasOption(ShowTotal))
+ {
+ reason = "Show total is off";
+ }
+ if (unresolvedCount > 0)
+ {
+ if (!reason.isEmpty())
+ {
+ reason += ". ";
+ }
+ reason += QString("Skipped <b>%1</b> unresolved functions").arg(unresolvedCount);
+ }
+ hint += QString("<p>%1.</p>").arg(reason);
+ hint += QString("<p><i>Please use the context menu to control the chart display options.</i></p>");
+ }
+ auto hintLabel = new QwtPlotTextLabel();
+ hintLabel->setText(hint);
+ hintLabel->attach(this);
+
+ delete m_zoomer;
+ m_zoomer = nullptr;
+
+ delete m_panner;
+ m_panner = nullptr;
+
+ enableAxis(QwtPlot::xBottom, false);
+ enableAxis(QwtPlot::yRight, false);
+ }
+
+ replot();
+
+ if (resetZoomAndPan)
+ {
+ if (m_zoomer)
+ {
+ m_zoomer->setZoomBase(false);
+ }
+ m_xScaleDiv = axisScaleDiv(QwtPlot::xBottom);
+ m_yScaleDiv = axisScaleDiv(QwtPlot::yRight);
+ }
+}
+
+void ChartWidgetQwtPlot::resetZoom()
+{
+ bool doReplot = false;
+ if (m_zoomer && (m_zoomer->zoomRectIndex() != 0))
+ {
+ m_zoomer->zoom(0);
+ doReplot = true;
+ }
+ if (!m_xScaleDiv.isEmpty() && (m_xScaleDiv != axisScaleDiv(QwtPlot::xBottom)))
+ {
+ setAxisScaleDiv(QwtPlot::xBottom, m_xScaleDiv);
+ doReplot = true;
+ }
+ if (!m_yScaleDiv.isEmpty() && (m_yScaleDiv != axisScaleDiv(QwtPlot::yRight)))
+ {
+ setAxisScaleDiv(QwtPlot::yRight, m_yScaleDiv);
+ doReplot = true;
+ }
+ if (doReplot)
+ {
+ replot();
+ }
+}
+
+bool ChartWidgetQwtPlot::getCurveTooltip(const QPointF &position, QString &tooltip) const
+{
+ qreal timestamp = position.x();
+ qreal cost = position.y();
+ if ((timestamp < 0) || (cost < 0))
+ {
+ return false;
+ }
+ int row = m_model->getRowForTimestamp(timestamp);
+ if (row < 0)
+ {
+ return false;
+ }
+ qint64 rowTimestamp = m_model->getTimestamp(row); // rowTimestamp <= timestamp
+ // find a column which value (cost) for the row found is greater than or equal to
+ // 'cost' and at the same time this value is the smallest from all such values
+ qreal minCostFound = std::numeric_limits<qreal>::max();
+ int columnFound = -1;
+ int column = 1;
+ if (!hasOption(ShowTotal))
+ {
+ column += 2;
+ }
+ int columns = m_model->columnCount();
+ int rowCount = m_model->rowCount();
+ for (; column < columns; column += 2)
+ {
+ if (!hasOption(ShowUnresolved) &&
+ Util::isUnresolvedFunction(m_model->getColumnLabel(column))) // column label starts with a function name
+ {
+ continue;
+ }
+ qint64 columnCost = m_model->getCost(row, column);
+ qreal intermediateCost = columnCost;
+ if ((rowTimestamp < timestamp) && (row + 1 < rowCount))
+ {
+ // solve linear equation to find line-approximated 'intermediateCost'
+ qreal x1, y1, x2, y2;
+ x1 = rowTimestamp;
+ y1 = columnCost;
+ x2 = m_model->getTimestamp(row + 1);
+ y2 = m_model->getCost(row + 1, column);
+ qreal dx = x2 - x1;
+ if (dx > 1e-9) // avoid division by zero or near-zero
+ {
+ qreal a = (y2 - y1) / dx;
+ qreal b = (x2 * y1 - x1 * y2) / dx;
+ intermediateCost = a * timestamp + b;
+ }
+ }
+ if ((cost <= intermediateCost) && (intermediateCost <= minCostFound))
+ {
+ minCostFound = intermediateCost;
+ columnFound = column;
+ }
+ }
+ if (columnFound >= 0)
+ {
+//qDebug() << "timestamp: " << timestamp << " ms; row timestamp: " << m_model->getTimestamp(row) << " ms; cost="
+// << cost << "; row=" << row << "; minCostFound=" << minCostFound;
+ tooltip = m_model->data(m_model->index(row, columnFound), Qt::ToolTipRole).toString();
+ return true;
+ }
+ return false;
+}
+
+void ChartWidgetQwtPlot::clearTooltip()
+{
+ if (m_plotTooltip.isEmpty())
+ {
+ m_plotTooltip = parentWidget()->toolTip();
+ }
+ parentWidget()->setToolTip(QString());
+ QToolTip::hideText();
+}
+
+void ChartWidgetQwtPlot::restoreTooltip()
+{
+ if (!m_plotTooltip.isEmpty())
+ {
+ parentWidget()->setToolTip(m_plotTooltip);
+ }
+}
diff --git a/src/analyze/gui/chartwidgetqwtplot.h b/src/analyze/gui/chartwidgetqwtplot.h
new file mode 100644
index 0000000..071f278
--- /dev/null
+++ b/src/analyze/gui/chartwidgetqwtplot.h
@@ -0,0 +1,108 @@
+#ifndef CHARTWIDGETQWTPLOT_H
+#define CHARTWIDGETQWTPLOT_H
+
+#include <qwt_plot.h>
+#include <qwt_scale_div.h>
+
+#include <QPen>
+
+class ChartModel;
+class Zoomer;
+class QwtPanner;
+
+class ChartOptions
+{
+public:
+ enum Options
+ {
+ None = 0,
+ ShowHelp = 0x01,
+ ShowTotal = 0x02,
+ ShowUnresolved = 0x04,
+ ShowLegend = 0x10,
+ ShowCurveBorders = 0x20,
+ ShowSymbols = 0x40,
+ ShowVLines = 0x80
+ };
+
+ static Options GlobalOptions;
+
+ explicit ChartOptions(Options options) { m_options = options; }
+
+ static bool hasOption(Options options, Options option) { return (options & option) != 0; }
+
+ static Options setOption(Options options, Options option, bool isOn);
+
+ Options options() const { return m_options; }
+
+ bool hasOption(Options option) const { return hasOption(m_options, option); }
+
+ Options setOption(Options option, bool isOn);
+
+ Options toggleOption(Options option);
+
+ virtual void setOptions(Options options) = 0;
+
+protected:
+ Options m_options;
+
+private:
+ ChartOptions() { }
+};
+
+class ChartWidgetQwtPlot : public QwtPlot, public ChartOptions
+{
+public:
+ explicit ChartWidgetQwtPlot(QWidget *parent, Options options);
+
+ void setModel(ChartModel* model);
+
+ ChartModel* model() const { return m_model; }
+
+ bool isSizeModel() const { return m_isSizeModel; }
+
+ bool isEmpty() const { return (m_zoomer == nullptr); }
+
+ virtual void setOptions(Options options) override;
+
+ void rebuild(bool resetZoomAndPan);
+
+ void resetZoom();
+
+ bool getCurveTooltip(const QPointF &position, QString &tooltip) const;
+
+private:
+ friend class Zoomer;
+
+ void clearTooltip();
+
+ void restoreTooltip();
+
+ ChartModel *m_model;
+
+ bool m_isSizeModel;
+
+ QPen m_vLinePen;
+
+ Zoomer *m_zoomer;
+
+ QwtPanner *m_panner;
+
+ QwtScaleDiv m_xScaleDiv, m_yScaleDiv;
+
+ QString m_plotTooltip;
+};
+
+inline ChartWidgetQwtPlot::Options operator | (ChartWidgetQwtPlot::Options i, ChartWidgetQwtPlot::Options f)
+{ return ChartWidgetQwtPlot::Options(int(i) | int(f)); }
+
+inline ChartWidgetQwtPlot::Options operator & (ChartWidgetQwtPlot::Options i, ChartWidgetQwtPlot::Options f)
+{ return ChartWidgetQwtPlot::Options(int(i) & int(f)); }
+
+inline ChartWidgetQwtPlot::Options& operator |= (ChartWidgetQwtPlot::Options &i, ChartWidgetQwtPlot::Options f)
+{ return (i = (ChartWidgetQwtPlot::Options(int(i) | int(f)))); }
+
+inline ChartWidgetQwtPlot::Options& operator &= (ChartWidgetQwtPlot::Options &i, ChartWidgetQwtPlot::Options f)
+{ return (i = (ChartWidgetQwtPlot::Options(int(i) & int(f)))); }
+
+#endif // CHARTWIDGETQWTPLOT_H
diff --git a/src/analyze/gui/contextmenuqwt.cpp b/src/analyze/gui/contextmenuqwt.cpp
new file mode 100644
index 0000000..e90c480
--- /dev/null
+++ b/src/analyze/gui/contextmenuqwt.cpp
@@ -0,0 +1,199 @@
+#include "contextmenuqwt.h"
+#include "noklib.h"
+
+#include <QKeyEvent>
+#include <QMenu>
+
+ContextMenuQwt::ContextMenuQwt(QObject *parent, bool isHistogram)
+{
+ m_showTotalAction = new QAction(i18n("Show &Total"), parent);
+ m_showTotalAction->setStatusTip(i18n("Show the total amount curve"));
+ m_showTotalAction->setCheckable(true);
+
+ m_showUnresolvedAction = new QAction(i18n("Show &Unresolved"), parent);
+ m_showUnresolvedAction->setStatusTip(i18n("Show unresolved functions' curves"));
+ m_showUnresolvedAction->setCheckable(true);
+
+ m_exportChartAction = new QAction(i18n("&Export Chart..."), parent);
+ m_exportChartAction->setStatusTip(i18n("Export the current chart to a file."));
+
+ // shortcuts don't work under Windows (Qt 5.10.0) so need to use a workaround (manual
+ // processing in keyPressEvent)
+ m_showTotalAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_T));
+ m_showUnresolvedAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_U));
+ m_exportChartAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_E));
+
+#if QT_VERSION >= 0x050A00
+ m_showTotalAction->setShortcutVisibleInContextMenu(true);
+ m_showUnresolvedAction->setShortcutVisibleInContextMenu(true);
+ m_exportChartAction->setShortcutVisibleInContextMenu(true);
+#endif
+
+ if (!isHistogram)
+ {
+ m_resetZoomAction = new QAction(i18n("Reset Zoom and Pan"), parent);
+ m_resetZoomAction->setStatusTip(i18n("Reset the chart zoom and pan"));
+
+ m_showLegendAction = new QAction(i18n("Show &Legend"), parent);
+ m_showLegendAction->setStatusTip(i18n("Show the chart legend"));
+ m_showLegendAction->setCheckable(true);
+
+ m_showCurveBordersAction = new QAction(i18n("Show Curve &Borders"), parent);
+ m_showCurveBordersAction->setStatusTip(i18n("Show curve borders (as black lines)"));
+ m_showCurveBordersAction->setCheckable(true);
+
+ m_showSymbolsAction = new QAction(i18n("Show &Symbols"), parent);
+ m_showSymbolsAction->setStatusTip(i18n("Show symbols (the chart data points)"));
+ m_showSymbolsAction->setCheckable(true);
+
+ m_showVLinesAction = new QAction(i18n("Show &Vertical Lines"), parent);
+ m_showVLinesAction->setStatusTip(i18n("Show vertical lines corresponding to timestamps"));
+ m_showVLinesAction->setCheckable(true);
+
+ m_showHelpAction = new QAction(i18n("Show Chart &Help"), parent);
+ m_showHelpAction->setStatusTip(i18n("Show a window with breif help information inside the chart."));
+ m_showHelpAction->setCheckable(true);
+
+ m_resetZoomAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_R));
+ m_showLegendAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_L));
+ m_showCurveBordersAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_B));
+ m_showSymbolsAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_S));
+ m_showVLinesAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_V));
+
+#if QT_VERSION >= 0x050A00
+ m_resetZoomAction->setShortcutVisibleInContextMenu(true);
+ m_showLegendAction->setShortcutVisibleInContextMenu(true);
+ m_showCurveBordersAction->setShortcutVisibleInContextMenu(true);
+ m_showSymbolsAction->setShortcutVisibleInContextMenu(true);
+ m_showVLinesAction->setShortcutVisibleInContextMenu(true);
+#endif
+ }
+ else
+ {
+ m_resetZoomAction = nullptr;
+ m_showLegendAction = nullptr;
+ m_showCurveBordersAction = nullptr;
+ m_showSymbolsAction = nullptr;
+ m_showVLinesAction = nullptr;
+ m_showHelpAction = nullptr;
+ }
+}
+
+void ContextMenuQwt::initializeMenu(QMenu& menu, ChartOptions::Options options, bool isEmpty) const
+{
+ if (m_resetZoomAction)
+ {
+ m_resetZoomAction->setEnabled(!isEmpty);
+ menu.addAction(m_resetZoomAction);
+ menu.addSeparator();
+ }
+ if (m_showTotalAction)
+ {
+ m_showTotalAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowTotal));
+ menu.addAction(m_showTotalAction);
+ }
+ if (m_showUnresolvedAction)
+ {
+ m_showUnresolvedAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowUnresolved));
+ menu.addAction(m_showUnresolvedAction);
+ }
+ if ((m_showTotalAction != nullptr) || (m_showUnresolvedAction != nullptr))
+ {
+ menu.addSeparator();
+ }
+ if (m_showLegendAction)
+ {
+ m_showLegendAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowLegend));
+ menu.addAction(m_showLegendAction);
+ }
+ if (m_showCurveBordersAction)
+ {
+ m_showCurveBordersAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowCurveBorders));
+ menu.addAction(m_showCurveBordersAction);
+ }
+ if (m_showSymbolsAction)
+ {
+ m_showSymbolsAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowSymbols));
+ menu.addAction(m_showSymbolsAction);
+ }
+ if (m_showVLinesAction)
+ {
+ m_showVLinesAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowVLines));
+ menu.addAction(m_showVLinesAction);
+ }
+ if (m_exportChartAction)
+ {
+ menu.addSeparator();
+ m_exportChartAction->setEnabled(!isEmpty);
+ menu.addAction(m_exportChartAction);
+ }
+ if (m_showHelpAction)
+ {
+ menu.addSeparator();
+ m_showHelpAction->setChecked(ChartOptions::hasOption(options, ChartOptions::ShowHelp));
+ menu.addAction(m_showHelpAction);
+ }
+}
+
+bool ContextMenuQwt::handleKeyPress(QKeyEvent *event)
+{
+ if (event->modifiers() & Qt::AltModifier)
+ {
+ switch (event->key())
+ {
+ case Qt::Key_R:
+ if (m_resetZoomAction)
+ {
+ m_resetZoomAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_T:
+ if (m_showTotalAction)
+ {
+ m_showTotalAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_U:
+ if (m_showUnresolvedAction)
+ {
+ m_showUnresolvedAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_L:
+ if (m_showLegendAction)
+ {
+ m_showLegendAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_S:
+ if (m_showSymbolsAction)
+ {
+ m_showSymbolsAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_V:
+ if (m_showVLinesAction)
+ {
+ m_showVLinesAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_B:
+ if (m_showCurveBordersAction)
+ {
+ m_showCurveBordersAction->activate(QAction::Trigger);
+ }
+ break;
+ case Qt::Key_E:
+ if (m_exportChartAction)
+ {
+ m_exportChartAction->activate(QAction::Trigger);
+ }
+ break;
+ default:
+ return false;
+ }
+ event->accept();
+ return true;
+ }
+ return false;
+}
diff --git a/src/analyze/gui/contextmenuqwt.h b/src/analyze/gui/contextmenuqwt.h
new file mode 100644
index 0000000..aaaf2e8
--- /dev/null
+++ b/src/analyze/gui/contextmenuqwt.h
@@ -0,0 +1,41 @@
+#ifndef CONTEXTMENUQWT_H
+#define CONTEXTMENUQWT_H
+
+#include "chartwidgetqwtplot.h"
+
+#include <QAction>
+#include <QKeyEvent>
+#include <QMenu>
+
+class ContextMenuQwt
+{
+public:
+ ContextMenuQwt(QObject *parent, bool isHistogram);
+
+ QAction* resetZoomAction() const { return m_resetZoomAction; }
+ QAction* showTotalAction() const { return m_showTotalAction; }
+ QAction* showUnresolvedAction() const { return m_showUnresolvedAction; }
+ QAction* showLegendAction() const { return m_showLegendAction; }
+ QAction* showSymbolsAction() const { return m_showSymbolsAction; }
+ QAction* showVLinesAction() const { return m_showVLinesAction; }
+ QAction* showCurveBordersAction() const { return m_showCurveBordersAction; }
+ QAction* exportChartAction() const { return m_exportChartAction; }
+ QAction* showHelpAction() const { return m_showHelpAction; }
+
+ void initializeMenu(QMenu& menu, ChartOptions::Options options, bool isEmpty) const;
+
+ bool handleKeyPress(QKeyEvent *event);
+
+private:
+ QAction* m_resetZoomAction;
+ QAction* m_showTotalAction;
+ QAction* m_showUnresolvedAction;
+ QAction* m_showLegendAction;
+ QAction* m_showSymbolsAction;
+ QAction* m_showVLinesAction;
+ QAction* m_showCurveBordersAction;
+ QAction* m_exportChartAction;
+ QAction* m_showHelpAction;
+};
+
+#endif // CONTEXTMENUQWT_H
diff --git a/src/analyze/gui/flamegraph.cpp b/src/analyze/gui/flamegraph.cpp
index b680b49..4d1a5bd 100644
--- a/src/analyze/gui/flamegraph.cpp
+++ b/src/analyze/gui/flamegraph.cpp
@@ -21,6 +21,7 @@
#include <cmath>
#include <QAction>
+#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCursor>
@@ -37,10 +38,21 @@
#include <QWheelEvent>
#include <QLineEdit>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#ifdef THREAD_WEAVER
+#include <threadweaver.h>
+#endif // THREAD_WEAVER
+#else
#include <KColorScheme>
#include <KLocalizedString>
#include <KStandardAction>
#include <ThreadWeaver/ThreadWeaver>
+#endif // NO_K_LIB
+
+#include "util.h"
+
+//#define DEBUG_MAX_DEPTH // to investigate which flame graph depth is safe (i.e. doesn't cause stack overflow)
enum CostType
{
@@ -201,7 +213,6 @@ QString FrameGraphicsItem::description() const
// we build the tooltip text on demand, which is much faster than doing that
// for potentially thousands of items when we load the data
QString tooltip;
- KFormat format;
qint64 totalCost = 0;
{
auto item = this;
@@ -231,7 +242,7 @@ QString FrameGraphicsItem::description() const
i18nc("%1: peak consumption in bytes, %2: relative number, %3: "
"function label",
"%1 (%2%) contribution to peak consumption in %3 and below.",
- format.formatByteSize(m_cost, 1, KFormat::JEDECBinaryDialect), fraction, function);
+ Util::formatByteSize(m_cost, 1), fraction, function);
break;
case PeakInstances:
tooltip =
@@ -242,12 +253,12 @@ QString FrameGraphicsItem::description() const
break;
case Leaked:
tooltip = i18nc("%1: leaked bytes, %2: relative number, %3: function label", "%1 (%2%) leaked in %3 and below.",
- format.formatByteSize(m_cost, 1, KFormat::JEDECBinaryDialect), fraction, function);
+ Util::formatByteSize(m_cost, 1), fraction, function);
break;
case Allocated:
tooltip = i18nc("%1: allocated bytes, %2: relative number, %3: function label",
"%1 (%2%) allocated in %3 and below.",
- format.formatByteSize(m_cost, 1, KFormat::JEDECBinaryDialect), fraction, function);
+ Util::formatByteSize(m_cost, 1), fraction, function);
break;
}
@@ -366,11 +377,51 @@ FrameGraphicsItem* findItemByFunction(const QList<QGraphicsItem*>& items, const
/**
* Convert the top-down graph into a tree of FrameGraphicsItem.
*/
-void toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem* parent, int64_t AllocationData::Stats::*member,
+struct ToGraphicsItemsContext
+{
+ int64_t AllocationData::Stats::*member;
+ double costThreshold;
+ bool collapseRecursion;
+ int depth = 0;
+};
+
+static const int MaxRecursionDepth = 600; // 800: crashes
+#ifdef DEBUG_MAX_DEPTH
+static int maxDepthReached;
+static int lastMaxDepthReached;
+#endif
+
+// returns 'true' if flame graph was created completely, returns 'false' if its depth was limited by
+bool toGraphicsItems(const QVector<RowData>& data, ToGraphicsItemsContext& context, FrameGraphicsItem* parent);
+
+bool toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem* parent, int64_t AllocationData::Stats::*member,
const double costThreshold, bool collapseRecursion)
{
+ // pack parameters to a structure to remove stack usage during recursion
+ ToGraphicsItemsContext ctx;
+ ctx.member = member;
+ ctx.costThreshold = costThreshold;
+ ctx.collapseRecursion = collapseRecursion;
+#ifdef DEBUG_MAX_DEPTH
+ maxDepthReached = 0;
+ lastMaxDepthReached = 0;
+#endif
+ bool result = toGraphicsItems(data, ctx, parent);
+#ifdef DEBUG_MAX_DEPTH
+ qDebug() << "Max flame graph depth: " << maxDepthReached;
+ if (!result)
+ {
+ qDebug() << ". Flame graph depth limited to " << MaxRecursionDepth;
+ }
+#endif
+ return result;
+}
+
+bool toGraphicsItems(const QVector<RowData>& data, ToGraphicsItemsContext& context, FrameGraphicsItem* parent)
+{
+ bool result = true;
foreach (const auto& row, data) {
- if (collapseRecursion
+ if (context.collapseRecursion
&& row.location->function != unresolvedFunctionName()
&& row.location->function != untrackedFunctionName()
&& row.location->function == parent->function())
@@ -380,7 +431,7 @@ void toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem* parent, in
auto item = findItemByFunction(parent->childItems(), row.location->function);
if (!item) {
AllocationData::CoreCLRType clrType = row.stackType;
- item = new FrameGraphicsItem(row.cost.*member, row.location->function, clrType, parent);
+ item = new FrameGraphicsItem(row.cost.*context.member, row.location->function, clrType, parent);
item->setPen(parent->pen());
switch (clrType)
@@ -411,12 +462,40 @@ void toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem* parent, in
}
}
} else {
- item->setCost(item->cost() + row.cost.*member);
+ item->setCost(item->cost() + row.cost.*context.member);
}
- if (item->cost() > costThreshold) {
- toGraphicsItems(row.children, item, member, costThreshold, collapseRecursion);
+ if (item->cost() > context.costThreshold) {
+ ++context.depth;
+
+ if (!row.children.isEmpty())
+ {
+ if (context.depth <= MaxRecursionDepth)
+ {
+#ifdef DEBUG_MAX_DEPTH
+ if (context.depth > maxDepthReached)
+ {
+ maxDepthReached = context.depth;
+ if (maxDepthReached - lastMaxDepthReached >= 100)
+ {
+ lastMaxDepthReached = maxDepthReached;
+ qDebug() << "Depth: " << maxDepthReached;
+ }
+ }
+#endif
+ if (!toGraphicsItems(row.children, context, item))
+ {
+ result = false;
+ }
+ }
+ else
+ {
+ result = false;
+ }
+ }
+ --context.depth;
}
}
+ return result;
}
int64_t AllocationData::Stats::*memberForType(CostType type)
@@ -439,8 +518,10 @@ int64_t AllocationData::Stats::*memberForType(CostType type)
}
FrameGraphicsItem* parseData(const QVector<RowData>& topDownData, CostType type, double costThreshold,
- bool collapseRecursion)
+ bool collapseRecursion, bool& depthLimited)
{
+ depthLimited = false;
+
auto member = memberForType(type);
double totalCost = 0;
@@ -448,10 +529,14 @@ FrameGraphicsItem* parseData(const QVector<RowData>& topDownData, CostType type,
totalCost += frame.cost.*member;
}
+#ifdef NO_K_LIB
+ QPalette pal;
+ const QPen pen(pal.color(QPalette::Active, QPalette::Foreground));
+#else
KColorScheme scheme(QPalette::Active);
const QPen pen(scheme.foreground().color());
+#endif
- KFormat format;
QString label;
switch (type) {
case Allocations:
@@ -461,22 +546,26 @@ FrameGraphicsItem* parseData(const QVector<RowData>& topDownData, CostType type,
label = i18n("%1 temporary allocations in total", totalCost);
break;
case Peak:
- label = i18n("%1 contribution to peak consumption", format.formatByteSize(totalCost, 1, KFormat::JEDECBinaryDialect));
+ label = i18n("%1 contribution to peak consumption", Util::formatByteSize(totalCost, 1));
break;
case PeakInstances:
label = i18n("%1 contribution to peak number of instances", totalCost);
break;
case Leaked:
- label = i18n("%1 leaked in total", format.formatByteSize(totalCost, 1, KFormat::JEDECBinaryDialect));
+ label = i18n("%1 leaked in total", Util::formatByteSize(totalCost, 1));
break;
case Allocated:
- label = i18n("%1 allocated in total", format.formatByteSize(totalCost, 1, KFormat::JEDECBinaryDialect));
+ label = i18n("%1 allocated in total", Util::formatByteSize(totalCost, 1));
break;
}
auto rootItem = new FrameGraphicsItem(totalCost, type, label, AllocationData::CoreCLRType::nonCoreCLR);
+#ifdef NO_K_LIB
+ rootItem->setBrush(pal.color(QPalette::Active, QPalette::Background));
+#else
rootItem->setBrush(scheme.background());
+#endif
rootItem->setPen(pen);
- toGraphicsItems(topDownData, rootItem, member, totalCost * costThreshold / 100, collapseRecursion);
+ depthLimited = !toGraphicsItems(topDownData, rootItem, member, totalCost * costThreshold / 100, collapseRecursion);
return rootItem;
}
@@ -635,10 +724,12 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags)
layout()->addWidget(m_displayLabel);
layout()->addWidget(m_searchResultsLabel);
+#ifndef NO_K_LIB
m_backAction = KStandardAction::back(this, SLOT(navigateBack()), this);
addAction(m_backAction);
m_forwardAction = KStandardAction::forward(this, SLOT(navigateForward()), this);
addAction(m_forwardAction);
+#endif
m_resetAction = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18n("Reset View"), this);
m_resetAction->setShortcut(Qt::Key_Escape);
connect(m_resetAction, &QAction::triggered, this, [this]() {
@@ -698,6 +789,38 @@ bool FlameGraph::eventFilter(QObject* object, QEvent* event)
return ret;
}
+#if NO_K_LIB
+bool FlameGraph::handleKeyPress(QKeyEvent *event)
+{
+ if (m_view->hasFocus() || (qApp->focusWidget() == nullptr))
+ {
+ if (event->modifiers() & Qt::AltModifier)
+ {
+ switch (event->key())
+ {
+ case Qt::Key_Backspace:
+ case Qt::Key_Right:
+ navigateForward();
+ return true;
+ case Qt::Key_Left:
+ navigateBack();
+ return true;
+ }
+ }
+ else
+ {
+ switch (event->key())
+ {
+ case Qt::Key_Backspace:
+ navigateBack();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
void FlameGraph::setTopDownData(const TreeData& topDownData)
{
m_topDownData = topDownData;
@@ -717,23 +840,31 @@ void FlameGraph::clearData()
m_topDownData = {};
m_bottomUpData = {};
- setData(nullptr);
+ setData(nullptr, false);
}
void FlameGraph::showData()
{
- setData(nullptr);
+ setData(nullptr, false);
m_buildingScene = true;
- using namespace ThreadWeaver;
auto data = m_showBottomUpData ? m_bottomUpData : m_topDownData;
bool collapseRecursion = m_collapseRecursion;
auto source = m_costSource->currentData().value<CostType>();
auto threshold = m_costThreshold;
+#ifndef THREAD_WEAVER
+ bool depthLimited;
+ auto parsedData = parseData(data, source, threshold, collapseRecursion, depthLimited);
+ setData(parsedData, depthLimited);
+#else
+ using namespace ThreadWeaver;
stream() << make_job([data, source, threshold, collapseRecursion, this]() {
- auto parsedData = parseData(data, source, threshold, collapseRecursion);
- QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData));
+ bool depthLimited;
+ auto parsedData = parseData(data, source, threshold, collapseRecursion, depthLimited);
+ QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData),
+ Q_ARG(bool, depthLimited));
});
+#endif
}
void FlameGraph::setTooltipItem(const FrameGraphicsItem* item)
@@ -750,16 +881,26 @@ void FlameGraph::setTooltipItem(const FrameGraphicsItem* item)
void FlameGraph::updateTooltip()
{
- const auto text = m_tooltipItem ? m_tooltipItem->description() : QString();
+ auto text = m_tooltipItem ? m_tooltipItem->description() : QString();
m_displayLabel->setToolTip(text);
const auto metrics = m_displayLabel->fontMetrics();
+ if (m_depthLimited)
+ {
+ if (text.endsWith('.'))
+ {
+ text.resize(text.length() - 1);
+ }
+ text = text.toHtmlEscaped() + QString(
+ i18n(" <b>(graph maximum depth limited to %1)</b>")).arg(MaxRecursionDepth);
+ }
m_displayLabel->setText(metrics.elidedText(text, Qt::ElideRight, m_displayLabel->width()));
}
-void FlameGraph::setData(FrameGraphicsItem* rootItem)
+void FlameGraph::setData(FrameGraphicsItem* rootItem, bool depthLimited)
{
m_scene->clear();
m_buildingScene = false;
+ m_depthLimited = depthLimited;
m_tooltipItem = nullptr;
m_rootItem = rootItem;
m_selectionHistory.clear();
@@ -821,6 +962,13 @@ void FlameGraph::selectItem(FrameGraphicsItem* item)
// then layout all items below the selected on
layoutItems(item);
+ // 1) trying to fix a bug (?): sometimes the flamegraph is displayed at a wrong position initially
+ // (observed after switching to the flamegraph tab soon after the application starts with
+ // a data file specified in the command line);
+ // 2) QGraphicsScene::sceneRect never shrinks automatically so we need to update it manually
+ // if we want to update the ranges and visibility of the scrollbars
+ m_scene->setSceneRect(m_scene->itemsBoundingRect());
+
// and make sure it's visible
m_view->centerOn(item);
@@ -839,7 +987,6 @@ void FlameGraph::setSearchValue(const QString& value)
m_searchResultsLabel->hide();
} else {
QString label;
- KFormat format;
const auto costFraction = fraction(match.directCost, m_rootItem->cost());
switch (m_costSource->currentData().value<CostType>()) {
case Allocations:
@@ -852,8 +999,8 @@ void FlameGraph::setSearchValue(const QString& value)
case Leaked:
case Allocated:
label = i18n("%1 (%2% of total of %3) matched by search.",
- format.formatByteSize(match.directCost, 1, KFormat::JEDECBinaryDialect), costFraction,
- format.formatByteSize(m_rootItem->cost(), 1, KFormat::JEDECBinaryDialect));
+ Util::formatByteSize(match.directCost, 1), costFraction,
+ Util::formatByteSize(m_rootItem->cost(), 1));
break;
}
m_searchResultsLabel->setText(label);
@@ -877,7 +1024,11 @@ void FlameGraph::navigateForward()
void FlameGraph::updateNavigationActions()
{
- m_backAction->setEnabled(m_selectedItem > 0);
- m_forwardAction->setEnabled(m_selectedItem + 1 < m_selectionHistory.size());
+ if (m_backAction) {
+ m_backAction->setEnabled(m_selectedItem > 0);
+ }
+ if (m_forwardAction) {
+ m_forwardAction->setEnabled(m_selectedItem + 1 < m_selectionHistory.size());
+ }
m_resetAction->setEnabled(m_selectedItem > 0);
}
diff --git a/src/analyze/gui/flamegraph.h b/src/analyze/gui/flamegraph.h
index 973f32f..c7ec9bc 100644
--- a/src/analyze/gui/flamegraph.h
+++ b/src/analyze/gui/flamegraph.h
@@ -43,12 +43,16 @@ public:
void setBottomUpData(const TreeData& bottomUpData);
void clearData();
-
+#if NO_K_LIB
+ // handling back and forward shortcuts:
+ // keyPressEvent doesn't receive arrow keys so the main window calls
+ // this function from its event filter
+ bool handleKeyPress(QKeyEvent* event);
+#endif
protected:
bool eventFilter(QObject* object, QEvent* event) override;
-
private slots:
- void setData(FrameGraphicsItem* rootItem);
+ void setData(FrameGraphicsItem* rootItem, bool depthLimited);
void setSearchValue(const QString& value);
void navigateBack();
void navigateForward();
@@ -83,6 +87,7 @@ private:
bool m_buildingScene = false;
// cost threshold in percent, items below that value will not be shown
double m_costThreshold = 0.1;
+ bool m_depthLimited = false;
};
#endif // FLAMEGRAPH_H
diff --git a/src/analyze/gui/gui.cpp b/src/analyze/gui/gui.cpp
index 25c7b63..5716c28 100644
--- a/src/analyze/gui/gui.cpp
+++ b/src/analyze/gui/gui.cpp
@@ -18,10 +18,16 @@
#include <QApplication>
#include <QCommandLineParser>
+#include <QMessageBox>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KAboutData>
#include <KLocalizedString>
+#endif
+#include "aboutdata.h"
#include "../accumulatedtracedata.h"
#include "../allocationdata.h"
#include "mainwindow.h"
@@ -30,12 +36,16 @@ int main(int argc, char** argv)
{
QApplication app(argc, argv);
- KLocalizedString::setApplicationDomain("heaptrack");
+#ifndef NO_K_LIB
+ KLocalizedString::setApplicationDomain(AboutData::ShortName.toStdString().c_str());
- KAboutData aboutData(QStringLiteral("heaptrack_gui"), i18n("Heaptrack GUI"), QStringLiteral("0.1"),
- i18n("A visualizer for heaptrack data files."), KAboutLicense::LGPL,
- i18n("Copyright 2015, Milian Wolff <mail@milianw.de>"), QString(),
- QStringLiteral("mail@milianw.de"));
+ const auto LicenseType = KAboutLicense::LGPL;
+
+ typedef AboutData A;
+
+ KAboutData aboutData(A::ComponentName, A::DisplayName, A::Version, A::ShortDescription,
+ LicenseType, A::CopyrightStatement, QString(),
+ QString(), A::BugAddress);
aboutData.addAuthor(i18n("Milian Wolff"), i18n("Original author, maintainer"), QStringLiteral("mail@milianw.de"),
QStringLiteral("http://milianw.de"));
@@ -43,12 +53,15 @@ int main(int argc, char** argv)
aboutData.setOrganizationDomain("kde.org");
KAboutData::setApplicationData(aboutData);
+#endif
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("office-chart-area")));
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
+#ifndef NO_K_LIB
aboutData.setupCommandLine(&parser);
+#endif
QCommandLineOption diffOption{{QStringLiteral("d"), QStringLiteral("diff")},
i18n("Base profile data to compare other files to."),
@@ -75,7 +88,9 @@ int main(int argc, char** argv)
parser.addOption(showCoreCLRPartOption);
parser.process(app);
+#ifndef NO_K_LIB
aboutData.processCommandLine(&parser);
+#endif
bool isShowMalloc = parser.isSet(showMallocOption);
bool isShowManaged = parser.isSet(showManagedOption);
@@ -91,7 +106,12 @@ int main(int argc, char** argv)
+ (isShowPrivateClean ? 1 : 0)
+ (isShowShared ? 1 : 0) != 1) {
- qFatal("One of --malloc, --managed, --private_dirty, --private_clean or --shared options is necessary. Please, use exactly only one of the options for each start of GUI.");
+ const auto msg = "One of --malloc, --managed, --private_dirty, --private_clean or --shared options is necessary. " \
+ "Please, use exactly only one of the options for each start of GUI.";
+
+ QMessageBox::critical(nullptr, AboutData::DisplayName + " Error", msg, QMessageBox::Ok);
+
+ qFatal(msg);
return 1;
} else if (isShowMalloc)
diff --git a/src/analyze/gui/gui.qrc b/src/analyze/gui/gui.qrc
new file mode 100644
index 0000000..007d3c6
--- /dev/null
+++ b/src/analyze/gui/gui.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/" >
+ <file>icons/fileopen.png</file>
+ </qresource>
+</RCC>
diff --git a/src/analyze/gui/gui_config.h b/src/analyze/gui/gui_config.h
new file mode 100644
index 0000000..0e43f80
--- /dev/null
+++ b/src/analyze/gui/gui_config.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef HEAPTRACK_GUI_CONFIG_H
+#define HEAPTRACK_GUI_CONFIG_H
+
+#if !defined(NO_K_CHART)
+#define KChart_FOUND 1
+#elif defined(QWT)
+#define QWT_FOUND 1
+#endif
+
+#endif // HEAPTRACK_CONFIG_H
diff --git a/src/analyze/gui/histogrammodel.cpp b/src/analyze/gui/histogrammodel.cpp
index cb1e092..3367922 100644
--- a/src/analyze/gui/histogrammodel.cpp
+++ b/src/analyze/gui/histogrammodel.cpp
@@ -18,10 +18,20 @@
#include "histogrammodel.h"
+#include "gui_config.h"
+
+#ifdef KChart_FOUND
#include <KChartGlobal>
+#endif
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KFormat>
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include <QBrush>
#include <QColor>
@@ -57,11 +67,13 @@ QVariant HistogramModel::data(const QModelIndex& index, int role) const
if (!hasIndex(index.row(), index.column(), index.parent())) {
return {};
}
+#ifdef KChart_FOUND
if (role == KChart::DatasetBrushRole) {
return QVariant::fromValue(QBrush(colorForColumn(index.column(), columnCount())));
} else if (role == KChart::DatasetPenRole) {
return QVariant::fromValue(QPen(Qt::black));
}
+#endif
if (role != Qt::DisplayRole && role != Qt::ToolTipRole) {
return {};
@@ -71,14 +83,33 @@ QVariant HistogramModel::data(const QModelIndex& index, int role) const
const auto& column = row.columns[index.column()];
if (role == Qt::ToolTipRole) {
if (index.column() == 0) {
- return i18n("%1 allocations in total", column.allocations);
+ return i18n("<b>%1</b> allocations in total", column.allocations);
+ }
+ if (!column.location) {
+ return {};
}
+ QString tooltip;
if (!column.location->file.isEmpty()) {
- return i18n("%1 allocations from %2 at %3:%4 in %5", column.allocations, column.location->function,
- column.location->file, column.location->line, column.location->module);
+ tooltip = i18n("%1 allocations from %2 at %3:%4 in %5", column.allocations, column.location->function,
+ column.location->file, column.location->line, column.location->module);
}
- return i18n("%1 allocations from %2 in %3", column.allocations, column.location->function,
- column.location->module);
+ else
+ {
+ tooltip = i18n("%1 allocations from %2 in %3", column.allocations, column.location->function,
+ column.location->module);
+ }
+#ifdef QWT_FOUND
+ tooltip = Util::wrapLabel(tooltip, 96, 0, "&nbsp;<br>");
+#else
+ tooltip = tooltip.toHtmlEscaped(); // Qt wraps text in tooltips itself
+#endif
+ // enclose first word in <b> and </b> tags
+ int i = tooltip.indexOf(' ');
+ if (i >= 0)
+ {
+ tooltip = "<b>" + tooltip.left(i) + "</b>" + tooltip.mid(i);
+ }
+ return tooltip;
}
return column.allocations;
}
@@ -112,3 +143,15 @@ void HistogramModel::clearData()
m_data = {};
endResetModel();
}
+
+QColor HistogramModel::getColumnColor(int column) const
+{
+ return colorForColumn(column, columnCount());
+}
+
+LocationData::Ptr HistogramModel::getLocationData(int row, int column) const
+{
+ const auto& rowData = m_data.at(row);
+ const auto& columnData = rowData.columns[column];
+ return columnData.location;
+}
diff --git a/src/analyze/gui/histogrammodel.h b/src/analyze/gui/histogrammodel.h
index 835a835..d91f270 100644
--- a/src/analyze/gui/histogrammodel.h
+++ b/src/analyze/gui/histogrammodel.h
@@ -64,6 +64,10 @@ public:
void resetData(const HistogramData& data);
void clearData();
+ QColor getColumnColor(int column) const;
+
+ LocationData::Ptr getLocationData(int row, int column) const;
+
private:
HistogramData m_data;
};
diff --git a/src/analyze/gui/histogramwidget.cpp b/src/analyze/gui/histogramwidget.cpp
index 44b1695..0933094 100644
--- a/src/analyze/gui/histogramwidget.cpp
+++ b/src/analyze/gui/histogramwidget.cpp
@@ -17,10 +17,12 @@
*/
#include "histogramwidget.h"
+#include "chartwidget.h"
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
+#if defined(KChart_FOUND)
#include <KChartBarDiagram>
#include <KChartChart>
@@ -31,16 +33,28 @@
#include <KChartGridAttributes>
#include <KChartHeaderFooter>
#include <KChartLegend>
+#elif defined(QWT_FOUND)
+#include <QMenu>
+#endif
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KColorScheme>
#include <KFormat>
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include "histogrammodel.h"
+#ifdef KChart_FOUND
using namespace KChart;
+#endif
namespace {
+#if defined(KChart_FOUND)
class SizeAxis : public CartesianAxis
{
Q_OBJECT
@@ -52,10 +66,10 @@ public:
const QString customizedLabel(const QString& label) const override
{
- KFormat format(QLocale::system());
- return format.formatByteSize(label.toDouble(), 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(label.toDouble(), 1);
}
};
+#endif
class HistogramProxy : public QSortFilterProxyModel
{
@@ -85,22 +99,53 @@ private:
HistogramWidget::HistogramWidget(QWidget* parent)
: QWidget(parent)
+#if defined(KChart_FOUND)
, m_chart(new KChart::Chart(this))
, m_total(new BarDiagram(this))
, m_detailed(new BarDiagram(this))
+#elif defined(QWT_FOUND)
+ , m_plot(new HistogramWidgetQwtPlot(this, ChartOptions::GlobalOptions))
+ , m_contextMenuQwt(new ContextMenuQwt(this, true))
+#endif
+#ifdef SHOW_TABLES
+ , m_tableViewTotal(new QTableView(this))
+ , m_tableViewNoTotal(new QTableView(this))
+#endif
{
auto layout = new QVBoxLayout(this);
+#if defined(KChart_FOUND)
layout->addWidget(m_chart);
+#elif defined(QWT_FOUND)
+ layout->addWidget(m_plot);
+
+ connectContextMenu();
+#endif
+#ifdef SHOW_TABLES
+ auto hLayout = new QHBoxLayout();
+ hLayout->addWidget(m_tableViewTotal);
+ hLayout->addWidget(m_tableViewNoTotal);
+ hLayout->setStretch(0, 25);
+ hLayout->setStretch(1, 75);
+ layout->addLayout(hLayout);
+ layout->setStretch(0, 100);
+ layout->setStretch(1, 100);
+#endif
setLayout(layout);
+#ifdef KChart_FOUND
auto* coordinatePlane = dynamic_cast<CartesianCoordinatePlane*>(m_chart->coordinatePlane());
Q_ASSERT(coordinatePlane);
{
m_total->setAntiAliasing(true);
+#ifdef NO_K_LIB
+ QPalette pal;
+ const QPen foreground(pal.color(QPalette::Active, QPalette::Foreground));
+#else
KColorScheme scheme(QPalette::Active, KColorScheme::Window);
QPen foreground(scheme.foreground().color());
+#endif
auto bottomAxis = new CartesianAxis(m_total);
auto axisTextAttributes = bottomAxis->textAttributes();
axisTextAttributes.setPen(foreground);
@@ -131,22 +176,96 @@ HistogramWidget::HistogramWidget(QWidget* parent)
m_detailed->setType(BarDiagram::Stacked);
}
+#endif
}
HistogramWidget::~HistogramWidget() = default;
-void HistogramWidget::setModel(QAbstractItemModel* model)
+void HistogramWidget::setModel(HistogramModel *model)
{
+#if defined(KChart_FOUND) || defined(SHOW_TABLES)
+ HistogramProxy *totalProxy, *proxy;
+#endif
+#if defined(KChart_FOUND)
{
- auto proxy = new HistogramProxy(true, this);
- proxy->setSourceModel(model);
- m_total->setModel(proxy);
+ totalProxy = new HistogramProxy(true, this);
+ totalProxy->setSourceModel(model);
+ m_total->setModel(totalProxy);
}
{
- auto proxy = new HistogramProxy(false, this);
+ proxy = new HistogramProxy(false, this);
proxy->setSourceModel(model);
m_detailed->setModel(proxy);
}
+#elif defined(QWT_FOUND)
+ connect(model, SIGNAL(modelReset()), this, SLOT(modelReset()));
+ m_plot->setModel(model);
+#ifdef SHOW_TABLES
+ totalProxy = new HistogramProxy(true, this);
+ totalProxy->setSourceModel(model);
+
+ proxy = new HistogramProxy(false, this);
+ proxy->setSourceModel(model);
+#endif // SHOW_TABLES
+#endif // QWT_FOUND, KChart_FOUND
+#ifdef SHOW_TABLES
+ m_tableViewTotal->setModel(totalProxy);
+ m_tableViewNoTotal->setModel(proxy);
+#endif
+}
+
+#ifdef QWT_FOUND
+void HistogramWidget::updateOnSelected()
+{
+ m_plot->setOptions(ChartOptions::GlobalOptions);
+}
+
+void HistogramWidget::modelReset()
+{
+ m_plot->rebuild();
+}
+
+void HistogramWidget::connectContextMenu()
+{
+ connect(m_contextMenuQwt->showTotalAction(), &QAction::triggered, this, &HistogramWidget::toggleShowTotal);
+ connect(m_contextMenuQwt->showUnresolvedAction(), &QAction::triggered, this, &HistogramWidget::toggleShowUnresolved);
+ connect(m_contextMenuQwt->exportChartAction(), &QAction::triggered, this, [=]() {
+ Util::exportChart(this, *m_plot, "Allocation Histogram");
+ });
+
+ setFocusPolicy(Qt::StrongFocus);
+}
+
+#ifndef QT_NO_CONTEXTMENU
+void HistogramWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+ QMenu menu(this);
+ m_contextMenuQwt->initializeMenu(menu, m_plot->options(), false);
+ menu.exec(event->globalPos());
+}
+#endif
+
+void HistogramWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (!m_contextMenuQwt->handleKeyPress(event))
+ {
+ QWidget::keyPressEvent(event);
+ }
+}
+
+void HistogramWidget::toggleShowTotal()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowTotal);
+}
+
+void HistogramWidget::toggleShowUnresolved()
+{
+ ChartOptions::GlobalOptions = m_plot->toggleOption(ChartOptions::ShowUnresolved);
}
+#endif // QWT_FOUND
+#ifdef KChart_FOUND
+// build errors occur in some environments if including .moc unconditionally
+// (e.g. Qt 5.11.0 MSVC2017 64bit, Release build only)
#include "histogramwidget.moc"
+#endif
diff --git a/src/analyze/gui/histogramwidget.h b/src/analyze/gui/histogramwidget.h
index 5818c4f..45aa1f2 100644
--- a/src/analyze/gui/histogramwidget.h
+++ b/src/analyze/gui/histogramwidget.h
@@ -19,14 +19,29 @@
#ifndef HISTOGRAMWIDGET_H
#define HISTOGRAMWIDGET_H
+#include "gui_config.h"
+
+#include <memory>
#include <QWidget>
+//!! for debugging
+//#define SHOW_TABLES
+
+#ifdef SHOW_TABLES
+#include <QTableView>
+#endif
+
+#if defined(KChart_FOUND)
namespace KChart {
class Chart;
class BarDiagram;
}
+#elif defined(QWT_FOUND)
+#include "histogramwidgetqwtplot.h"
+#include "contextmenuqwt.h"
+#endif
-class QAbstractItemModel;
+class HistogramModel;
class HistogramWidget : public QWidget
{
@@ -35,12 +50,41 @@ public:
explicit HistogramWidget(QWidget* parent = nullptr);
virtual ~HistogramWidget();
- void setModel(QAbstractItemModel* model);
+ void setModel(HistogramModel* model);
+
+#ifdef QWT_FOUND
+ void updateOnSelected();
+
+public slots:
+ void modelReset();
+protected:
+#ifndef QT_NO_CONTEXTMENU
+ virtual void contextMenuEvent(QContextMenuEvent *event) override;
+#endif
+ // workaround for handling the context menu shortcuts
+ virtual void keyPressEvent(QKeyEvent *event) override;
+#endif // QWT_FOUND
private:
+#if defined(KChart_FOUND)
KChart::Chart* m_chart;
KChart::BarDiagram* m_total;
KChart::BarDiagram* m_detailed;
+#elif defined(QWT_FOUND)
+private slots:
+ void toggleShowTotal();
+ void toggleShowUnresolved();
+
+ void connectContextMenu();
+
+private:
+ HistogramWidgetQwtPlot* m_plot;
+ std::unique_ptr<ContextMenuQwt> m_contextMenuQwt;
+#endif
+#ifdef SHOW_TABLES
+ QTableView* m_tableViewTotal;
+ QTableView* m_tableViewNoTotal;
+#endif
};
#endif // HISTOGRAMWIDGET_H
diff --git a/src/analyze/gui/histogramwidgetqwtplot.cpp b/src/analyze/gui/histogramwidgetqwtplot.cpp
new file mode 100644
index 0000000..5870eb2
--- /dev/null
+++ b/src/analyze/gui/histogramwidgetqwtplot.cpp
@@ -0,0 +1,368 @@
+#include "histogramwidgetqwtplot.h"
+#include "histogrammodel.h"
+#include "util.h"
+#ifdef NO_K_LIB
+#include "noklib.h"
+#endif
+
+#include <math.h>
+
+#include <qwt_column_symbol.h>
+#include <qwt_plot_grid.h>
+#include <qwt_plot_multi_barchart.h>
+#include <qwt_plot_picker.h>
+#include <qwt_scale_draw.h>
+
+#include <QHash>
+#include <QPair>
+#include <QVector>
+
+class HistogramScaleDraw: public QwtScaleDraw
+{
+public:
+ HistogramScaleDraw(QVector<QString>& rowNames) : m_rowNames(rowNames) { }
+
+ virtual QwtText label(double value) const
+ {
+ qint64 index = (qint64)value;
+ if ((index >= 0) && (index < m_rowNames.size()))
+ {
+ return m_rowNames[index];
+ }
+ return QwtScaleDraw::label(value);
+ }
+
+private:
+ QVector<QString> m_rowNames;
+};
+
+class BarSizes
+{
+public:
+ BarSizes()
+ {
+ const int BarCount = 9;
+ m_barRects.reserve(BarCount);
+ m_barLeftRight.reserve(BarCount);
+ }
+
+ void clear()
+ {
+ m_barRects.clear();
+ m_barLeftRight.clear();
+ }
+
+ void setBarSize(int sampleIndex, int barIndex, const QwtColumnRect &qwtRect)
+ {
+ if (m_barRects.size() <= sampleIndex)
+ {
+ m_barRects.resize(sampleIndex + 1);
+ m_barLeftRight.resize(sampleIndex + 1);
+ }
+ QRectF rectF(qwtRect.toRect());
+ QRect rect(floor(rectF.left()), floor(rectF.top()), ceil(rectF.width()), ceil(rectF.height()));
+ m_barRects[sampleIndex][barIndex] = rect;
+ m_barLeftRight[sampleIndex] = QPoint(rect.left(), rect.right());
+ }
+
+ bool findBar(const QPoint &pos, int& sampleIndex, int& barIndex) const
+ {
+ sampleIndex = -1;
+ for (int i = 0, count = m_barLeftRight.size(); i < count; ++i)
+ {
+ if ((pos.x() >= m_barLeftRight[i].x()) && (pos.x() <= m_barLeftRight[i].y()))
+ {
+ sampleIndex = i;
+ break;
+ }
+ }
+ if (sampleIndex < 0)
+ {
+ barIndex = -1;
+ return false;
+ }
+ // why deltaY = 1: relax the search condition (otherwise 'total' will be reported on inner edges)
+ barIndex = findBarIndex(sampleIndex, pos.y(), 1);
+ return (barIndex >= 0);
+ }
+
+private:
+ // find an index of a bar of the sample with 'sampleIndex' index which contains
+ // 'posY' y-coordinate; deltaY > 0 allows to increase the effective bar height
+ int findBarIndex(int sampleIndex, int posY, int deltaY) const
+ {
+ int result = -1;
+ const QHash<int, QRect>& sampleBars = m_barRects[sampleIndex];
+ QHash<int, QRect>::const_iterator it = sampleBars.constBegin();
+ while (it != sampleBars.constEnd())
+ {
+ const QRect& rect = it.value();
+ if ((posY >= rect.top()) && (posY <= (rect.bottom() + deltaY)))
+ {
+ result = it.key();
+ break;
+ }
+ ++it;
+ }
+ return result;
+ }
+
+ // 'm_barRects' vector index is 'sampleIndex'; each value is the map
+ // from 'barIndex' to the bar coordinates last passed to 'drawBar'
+ QVector<QHash<int, QRect>> m_barRects;
+
+ // 'm_barLeftRight' vector index is 'sampleIndex'; each value stores
+ // the left and right x-coordinates of this sample's bars
+ QVector<QPoint> m_barLeftRight;
+};
+
+class Picker: public QwtPlotPicker
+{
+public:
+ Picker(HistogramWidgetQwtPlot *plot)
+ : QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yRight, plot->canvas()), m_plot(plot)
+ {
+ setTrackerMode(QwtPlotPicker::AlwaysOn);
+ }
+
+ virtual void reset() override
+ {
+ QwtPlotPicker::reset();
+ m_totalBarSizes.clear();
+ m_barSizes.clear();
+ }
+
+ BarSizes m_totalBarSizes;
+ BarSizes m_barSizes;
+
+protected:
+ virtual QwtText trackerText(const QPoint &pos) const
+ {
+// qDebug() << "Picker: (" << pos.x() << "; " << pos.y() << ")";
+ HistogramModel *model = m_plot->model();
+ if (!model)
+ {
+ return {};
+ }
+ int sampleIndex, barIndex;
+ QString s;
+ if (m_barSizes.findBar(pos, sampleIndex, barIndex))
+ {
+ s = m_plot->getBarText(false, sampleIndex, barIndex);
+// s += QString("<br> Sample: %1. Bar: %2").arg(sampleIndex).arg(barIndex);
+ }
+ else if (m_plot->hasOption(ChartOptions::ShowTotal) &&
+ m_totalBarSizes.findBar(pos, sampleIndex, barIndex))
+ {
+ s = m_plot->getBarText(true, sampleIndex, barIndex);
+// s += QString("<br> (Total) Sample: %1. Bar: %2").arg(sampleIndex).arg(barIndex);
+ }
+ else
+ {
+ return {};
+ }
+ QwtText text("<p style='margin-left:4px'>" + s + "</p> ");
+ text.setRenderFlags((text.renderFlags() & ~Qt::AlignHorizontal_Mask) | Qt::AlignLeft);
+ text.setColor(Qt::white);
+// QColor c(Qt::darkGreen);
+ QColor c(0, 0x60, 0);
+ text.setBorderPen(QPen(c));
+ text.setBorderRadius(6);
+ c.setAlpha(170);
+ text.setBackgroundBrush(c);
+ return text;
+ }
+
+private:
+ HistogramWidgetQwtPlot *m_plot;
+};
+
+class MultiBarChart: public QwtPlotMultiBarChart
+{
+public:
+ MultiBarChart(BarSizes *barSizes) : m_barSizes(barSizes)
+ {
+ }
+
+protected:
+ virtual void drawBar( QPainter *painter, int sampleIndex,
+ int barIndex, const QwtColumnRect &rect) const
+ {
+// qDebug() << "drawBar: (sampleIndex=" << sampleIndex << "; barIndex=" << barIndex << "; " << rect.toRect() << ")";
+ QwtPlotMultiBarChart::drawBar(painter, sampleIndex, barIndex, rect);
+
+ m_barSizes->setBarSize(sampleIndex, barIndex, rect);
+ }
+
+private:
+ BarSizes *m_barSizes;
+};
+
+HistogramWidgetQwtPlot::HistogramWidgetQwtPlot(QWidget *parent, Options options)
+ : QwtPlot(parent), ChartOptions(options), m_model(nullptr), m_picker(new Picker(this))
+{
+ setCanvasBackground(Qt::white);
+ enableAxis(QwtPlot::yRight);
+ enableAxis(QwtPlot::yLeft, false);
+ setAxisTitle(QwtPlot::yRight, i18n("Number of Allocations"));
+ setAxisTitle(QwtPlot::xBottom, i18n("Requested Allocation Size"));
+}
+
+void HistogramWidgetQwtPlot::setOptions(Options options)
+{
+ if (m_options != options)
+ {
+ m_options = options;
+ rebuild();
+ }
+}
+
+void HistogramWidgetQwtPlot::rebuild()
+{
+ const double BarLayoutHint = 0.33;
+
+ detachItems();
+ m_picker->reset();
+
+ if (!m_model)
+ {
+ return;
+ }
+
+ auto grid = new QwtPlotGrid();
+ grid->setPen(QPen(Qt::lightGray));
+ grid->attach(this);
+
+ setAxisAutoScale(QwtPlot::yRight);
+
+ MultiBarChart *totalBarChart = nullptr;
+ if (hasOption(ShowTotal))
+ {
+ totalBarChart = new MultiBarChart(&m_picker->m_totalBarSizes);
+ totalBarChart->setStyle(QwtPlotMultiBarChart::Stacked);
+ totalBarChart->setLayoutHint(BarLayoutHint);
+ totalBarChart->setLayoutPolicy(QwtPlotMultiBarChart::ScaleSamplesToAxes);
+ }
+
+ auto barChart = new MultiBarChart(&m_picker->m_barSizes);
+ barChart->setStyle(QwtPlotMultiBarChart::Stacked);
+ barChart->setLayoutHint(BarLayoutHint);
+ barChart->setLayoutPolicy(QwtPlotMultiBarChart::ScaleSamplesToAxes);
+
+ QVector<QString> rowNames;
+ QVector<QVector<double>> totalSeries;
+ QVector<QVector<double>> series;
+ int rows = m_model->rowCount();
+ int columns = m_model->columnCount();
+ int maxColumn = 0;
+ for (int row = 0; row < rows; ++row)
+ {
+ rowNames.append(m_model->headerData(row, Qt::Vertical).toString());
+
+ if (totalBarChart)
+ {
+ QVector<double> totalValues;
+ totalValues.append(m_model->data(m_model->index(row, 0)).toDouble());
+ totalSeries.append(totalValues);
+ }
+
+ QVector<double> values;
+ for (int column = 1; column < columns; ++column)
+ {
+ if (!hasOption(ShowUnresolved))
+ {
+ LocationData::Ptr locData = m_model->getLocationData(row, column);
+ if (locData && Util::isUnresolvedFunction(locData->function))
+ {
+ continue;
+ }
+ }
+ double allocations = m_model->data(m_model->index(row, column)).toDouble();
+ if (allocations == 0) // columns are sorted by allocations descending
+ {
+ break;
+ }
+ values.append(allocations);
+ if (column > maxColumn)
+ {
+ maxColumn = column;
+ }
+ }
+ if (values.isEmpty())
+ {
+ values.append(0);
+ }
+ series.append(values);
+ }
+
+ for (int column = (totalBarChart ? 0 : 1); column <= maxColumn; ++column)
+ {
+ auto symbol = new QwtColumnSymbol(QwtColumnSymbol::Box);
+ symbol->setLineWidth(2);
+ symbol->setPalette(QPalette(m_model->getColumnColor(column)));
+ if (column > 0)
+ {
+ barChart->setSymbol(column - 1, symbol);
+ }
+ else
+ {
+ totalBarChart->setSymbol(0, symbol);
+ }
+ }
+
+ if (totalBarChart)
+ {
+ totalBarChart->setSamples(totalSeries);
+ totalBarChart->setAxes(QwtPlot::xBottom, QwtPlot::yRight);
+ totalBarChart->attach(this);
+ }
+
+ barChart->setSamples(series);
+
+ auto bottomScale = new HistogramScaleDraw(rowNames);
+ bottomScale->enableComponent(QwtScaleDraw::Backbone, false);
+ bottomScale->enableComponent(QwtScaleDraw::Ticks, false);
+ setAxisScaleDraw(QwtPlot::xBottom, bottomScale);
+
+ barChart->setAxes(QwtPlot::xBottom, QwtPlot::yRight);
+
+ barChart->attach(this);
+
+ replot();
+}
+
+QString HistogramWidgetQwtPlot::getBarText(bool isTotal, int sampleIndex, int barIndex) const
+{
+ QString result;
+ if (isTotal)
+ {
+ result = m_model->data(m_model->index(sampleIndex, 0), Qt::ToolTipRole).toString();
+ }
+ else
+ {
+ if (hasOption(ShowUnresolved))
+ {
+ result = m_model->data(m_model->index(sampleIndex, barIndex + 1), Qt::ToolTipRole).toString();
+ }
+ else // skip unresolved functions
+ {
+ int indexOfResolved = 0;
+ int columns = m_model->columnCount();
+ for (int column = 1; column < columns; ++column)
+ {
+ LocationData::Ptr locData = m_model->getLocationData(sampleIndex, column);
+ if (locData && Util::isUnresolvedFunction(locData->function))
+ {
+ continue;
+ }
+ if (indexOfResolved == barIndex)
+ {
+ result = m_model->data(m_model->index(sampleIndex, column), Qt::ToolTipRole).toString();
+ break;
+ }
+ ++indexOfResolved;
+ }
+ }
+ }
+ return result;
+}
diff --git a/src/analyze/gui/histogramwidgetqwtplot.h b/src/analyze/gui/histogramwidgetqwtplot.h
new file mode 100644
index 0000000..e42aad6
--- /dev/null
+++ b/src/analyze/gui/histogramwidgetqwtplot.h
@@ -0,0 +1,36 @@
+#ifndef HISTOGRAMWIDGETQWTPLOT_H
+#define HISTOGRAMWIDGETQWTPLOT_H
+
+#include "chartwidgetqwtplot.h"
+
+#include <qwt_plot.h>
+
+#include <QString>
+
+class HistogramModel;
+class Picker;
+
+class HistogramWidgetQwtPlot : public QwtPlot, public ChartOptions
+{
+public:
+ explicit HistogramWidgetQwtPlot(QWidget *parent, Options options);
+
+ void setModel(HistogramModel *model) { m_model = model; }
+
+ HistogramModel *model() const { return m_model; }
+
+ virtual void setOptions(Options options) override;
+
+ void rebuild();
+
+private:
+ friend class Picker;
+
+ QString getBarText(bool isTotal, int sampleIndex, int barIndex) const;
+
+ HistogramModel *m_model;
+
+ Picker *m_picker;
+};
+
+#endif // HISTOGRAMWIDGETQWTPLOT_H
diff --git a/src/analyze/gui/icons/fileopen.png b/src/analyze/gui/icons/fileopen.png
new file mode 100644
index 0000000..33e0d63
--- /dev/null
+++ b/src/analyze/gui/icons/fileopen.png
Binary files differ
diff --git a/src/analyze/gui/icons/if_diagram_v2-14_37134.ico b/src/analyze/gui/icons/if_diagram_v2-14_37134.ico
new file mode 100644
index 0000000..d06f03e
--- /dev/null
+++ b/src/analyze/gui/icons/if_diagram_v2-14_37134.ico
Binary files differ
diff --git a/src/analyze/gui/locationdata.h b/src/analyze/gui/locationdata.h
index 2a96b99..e5beb5c 100644
--- a/src/analyze/gui/locationdata.h
+++ b/src/analyze/gui/locationdata.h
@@ -25,7 +25,11 @@
#include <boost/functional/hash.hpp>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KLocalizedString>
+#endif
struct LocationData
{
@@ -81,7 +85,7 @@ inline uint qHash(const LocationData& location, uint seed_ = 0)
boost::hash_combine(seed, qHash(location.file));
boost::hash_combine(seed, qHash(location.module));
boost::hash_combine(seed, location.line);
- return seed;
+ return (uint)seed;
}
inline uint qHash(const LocationData::Ptr& location, uint seed = 0)
diff --git a/src/analyze/gui/mainwindow.cpp b/src/analyze/gui/mainwindow.cpp
index b1a489e..72108ba 100644
--- a/src/analyze/gui/mainwindow.cpp
+++ b/src/analyze/gui/mainwindow.cpp
@@ -18,20 +18,33 @@
#include "mainwindow.h"
-#include <ui_mainwindow.h>
-
#include <cmath>
+#include <cstdlib>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#include <ui_mainwindow_noklib.h>
+#include <QAbstractButton>
+#include <QFileDialog>
+#include <QSettings>
+#else
+#include <ui_mainwindow.h>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KRecursiveFilterProxyModel>
#include <KStandardAction>
+#endif
+
+#include "aboutdata.h"
+#include "aboutdialog.h"
+#include "util.h"
#include <QAction>
#include <QDebug>
#include <QDesktopServices>
#include <QFileDialog>
#include <QMenu>
+#include <QMoveEvent>
#include <QStatusBar>
#include "../accumulatedtracedata.h"
@@ -47,7 +60,7 @@
#include "gui_config.h"
-#if KChart_FOUND
+#if USE_CHART
#include "chartmodel.h"
#include "chartproxy.h"
#include "chartwidget.h"
@@ -106,7 +119,7 @@ void setupTopView(TreeModel* source, QTreeView* view, TopProxy::Type type)
addContextMenu(view, TreeModel::LocationRole);
}
-#if KChart_FOUND
+#if USE_CHART
void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type, const Parser* parser,
void (Parser::*dataReady)(const ChartData&), MainWindow* window)
{
@@ -226,13 +239,37 @@ MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, m_ui(new Ui::MainWindow)
, m_parser(new Parser(this))
+#ifndef NO_K_LIB
, m_config(KSharedConfig::openConfig(QStringLiteral("heaptrack_gui")))
+#endif
{
m_ui->setupUi(this);
+#ifdef NO_K_LIB
+ QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
+ restoreGeometry(settings.value("mainWindowGeometry").toByteArray());
+ // create docks, toolbars, etc…
+ restoreState(settings.value("mainWindowState").toByteArray());
+#ifdef QWT_FOUND
+ settings.beginGroup("Charts");
+ QVariant value = settings.value("Options");
+ bool ok;
+ int options = value.toInt(&ok);
+ if (ok)
+ {
+ ChartOptions::GlobalOptions = ChartOptions::Options(options);
+ }
+ settings.endGroup();
+#if QT_VERSION >= 0x050A00
+ // seems it doesn't help under Windows (Qt 5.10.0)
+ QCoreApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
+#endif
+#endif // QWT_FOUND
+#else
auto group = m_config->group(Config::Groups::MainWindow);
auto state = group.readEntry(Config::Entries::State, QByteArray());
restoreState(state, MAINWINDOW_VERSION);
+#endif // NO_K_LIB
m_ui->pages->setCurrentWidget(m_ui->openPage);
// TODO: proper progress report
@@ -264,12 +301,12 @@ MainWindow::MainWindow(QWidget* parent)
m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->bottomUpTab), true);
});
connect(m_parser, &Parser::objectTreeBottomUpDataAvailable, this, [=](const ObjectTreeData& data) {
- int maxGC = 0;
+ quint32 maxGC = 0;
for (const ObjectRowData& row: data) {
if (maxGC < row.gcNum)
maxGC = row.gcNum;
}
- for (int gc = 0; gc < maxGC; gc++) {
+ for (quint32 gc = 0; gc < maxGC; gc++) {
m_ui->filterGC->addItem(QString::number(gc+1));
}
objectTreeModel->resetData(data);
@@ -297,7 +334,6 @@ MainWindow::MainWindow(QWidget* parent)
bottomUpModelFilterOutLeaves->setSummary(data);
topDownModel->setSummary(data);
callerCalleeModel->setSummary(data);
- KFormat format;
QString textLeft;
QString textCenter;
QString textRight;
@@ -305,7 +341,13 @@ MainWindow::MainWindow(QWidget* parent)
const double peakTimeS = 0.001 * data.peakTime;
{
QTextStream stream(&textLeft);
- const auto debuggee = insertWordWrapMarkers(data.debuggee);
+ QString debuggee = data.debuggee;
+ int i = debuggee.indexOf(" __AUL_SDK__");
+ if (i >= 0)
+ {
+ debuggee.resize(i); // Tizen: remove part which is not human-readable
+ }
+ debuggee = insertWordWrapMarkers(debuggee);
stream << "<qt><dl>"
<< (data.fromAttached ? i18n("<dt><b>debuggee</b>:</dt><dd "
"style='font-family:monospace;'>%1 <i>(attached)</i></dd>",
@@ -316,7 +358,7 @@ MainWindow::MainWindow(QWidget* parent)
// xgettext:no-c-format
<< i18n("<dt><b>total runtime</b>:</dt><dd>%1s</dd>", totalTimeS)
<< i18n("<dt><b>total system memory</b>:</dt><dd>%1</dd>",
- format.formatByteSize(data.totalSystemMemory, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.totalSystemMemory, 1))
<< "</dl></qt>";
}
@@ -334,8 +376,8 @@ MainWindow::MainWindow(QWidget* parent)
qint64(data.cost.temporary / totalTimeS))
<< i18n("<dt><b>bytes allocated in total</b> (ignoring "
"deallocations):</dt><dd>%1 (%2/s)</dd>",
- format.formatByteSize(data.cost.allocated, 2, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.cost.allocated / totalTimeS, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.cost.allocated, 2),
+ Util::formatByteSize(data.cost.allocated / totalTimeS, 1))
<< "</dl></qt>";
}
if (AccumulatedTraceData::isShowCoreCLRPartOption)
@@ -347,20 +389,21 @@ MainWindow::MainWindow(QWidget* parent)
stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
"after %2s</dd>"
"</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (unknown)</dd>",
- format.formatByteSize(data.cost.peak, 1, KFormat::JEDECBinaryDialect),
+ Util::formatByteSize(data.cost.peak, 1),
peakTimeS,
- format.formatByteSize(data.CoreCLRPart.peak, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.nonCoreCLRPart.peak, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.unknownPart.peak, 1, KFormat::JEDECBinaryDialect))
- << i18n("<dt><b>peak RSS</b> (including heaptrack "
- "overhead):</dt><dd>%1</dd>",
- format.formatByteSize(data.peakRSS, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.CoreCLRPart.peak, 1),
+ Util::formatByteSize(data.nonCoreCLRPart.peak, 1),
+ Util::formatByteSize(data.unknownPart.peak, 1))
+ << i18n("<dt><b>peak RSS</b> (including %1 "
+ "overhead):</dt><dd>%2</dd>",
+ AboutData::ShortName,
+ Util::formatByteSize(data.peakRSS, 1))
<< i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>"
"</dt><dd>%2 (CoreCLR), %3 (non-CoreCLR), %4 (unknown)</dd>",
- format.formatByteSize(data.cost.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.CoreCLRPart.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.nonCoreCLRPart.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.unknownPart.leaked, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.cost.leaked, 1),
+ Util::formatByteSize(data.CoreCLRPart.leaked, 1),
+ Util::formatByteSize(data.nonCoreCLRPart.leaked, 1),
+ Util::formatByteSize(data.unknownPart.leaked, 1))
<< "</dl></qt>";
}
else
@@ -368,22 +411,23 @@ MainWindow::MainWindow(QWidget* parent)
stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
"after %2s</dd>"
"</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (sbrk heap), %6 (unknown)</dd>",
- format.formatByteSize(data.cost.peak, 1, KFormat::JEDECBinaryDialect),
+ Util::formatByteSize(data.cost.peak, 1),
peakTimeS,
- format.formatByteSize(data.CoreCLRPart.peak, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.nonCoreCLRPart.peak, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.untrackedPart.peak, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.unknownPart.peak, 1, KFormat::JEDECBinaryDialect))
- << i18n("<dt><b>peak RSS</b> (including heaptrack "
- "overhead):</dt><dd>%1</dd>",
- format.formatByteSize(data.peakRSS, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.CoreCLRPart.peak, 1),
+ Util::formatByteSize(data.nonCoreCLRPart.peak, 1),
+ Util::formatByteSize(data.untrackedPart.peak, 1),
+ Util::formatByteSize(data.unknownPart.peak, 1))
+ << i18n("<dt><b>peak RSS</b> (including %1 "
+ "overhead):</dt><dd>%2</dd>",
+ AboutData::ShortName,
+ Util::formatByteSize(data.peakRSS, 1))
<< i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>"
"</dt><dd>%2 (CoreCLR), %3 (non-CoreCLR), %4 (sbrk heap), %5 (unknown)</dd>",
- format.formatByteSize(data.cost.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.CoreCLRPart.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.nonCoreCLRPart.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.untrackedPart.leaked, 1, KFormat::JEDECBinaryDialect),
- format.formatByteSize(data.unknownPart.leaked, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.cost.leaked, 1),
+ Util::formatByteSize(data.CoreCLRPart.leaked, 1),
+ Util::formatByteSize(data.nonCoreCLRPart.leaked, 1),
+ Util::formatByteSize(data.untrackedPart.leaked, 1),
+ Util::formatByteSize(data.unknownPart.leaked, 1))
<< "</dl></qt>";
}
}
@@ -392,13 +436,14 @@ MainWindow::MainWindow(QWidget* parent)
QTextStream stream(&textRight);
stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
"after %2s</dd>",
- format.formatByteSize(data.cost.peak, 1, KFormat::JEDECBinaryDialect),
+ Util::formatByteSize(data.cost.peak, 1),
peakTimeS)
- << i18n("<dt><b>peak RSS</b> (including heaptrack "
- "overhead):</dt><dd>%1</dd>",
- format.formatByteSize(data.peakRSS, 1, KFormat::JEDECBinaryDialect))
+ << i18n("<dt><b>peak RSS</b> (including %1 "
+ "overhead):</dt><dd>%2</dd>",
+ AboutData::ShortName,
+ Util::formatByteSize(data.peakRSS, 1))
<< i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>",
- format.formatByteSize(data.cost.leaked, 1, KFormat::JEDECBinaryDialect))
+ Util::formatByteSize(data.cost.leaked, 1))
<< "</dl></qt>";
}
@@ -415,7 +460,6 @@ MainWindow::MainWindow(QWidget* parent)
layout->insertWidget(idx, m_ui->loadingProgress);
layout->insertWidget(idx + 1, m_ui->progressLabel);
m_ui->progressLabel->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
- m_closeAction->setEnabled(true);
m_openAction->setEnabled(true);
};
connect(m_parser, &Parser::finished, this, removeProgress);
@@ -426,7 +470,7 @@ MainWindow::MainWindow(QWidget* parent)
});
m_ui->messages->hide();
-#if KChart_FOUND
+#if USE_CHART
addChartTab(m_ui->tabWidget, i18n("Consumed"), ChartModel::Consumed, m_parser, &Parser::consumedChartDataAvailable,
this);
@@ -460,7 +504,9 @@ MainWindow::MainWindow(QWidget* parent)
m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(sizesTab), true);
});
}
-#endif
+#endif // USE_CHART
+
+ connect(m_ui->aboutAction, &QAction::triggered, this, &MainWindow::about);
auto costDelegate = new CostDelegate(this);
@@ -476,6 +522,7 @@ MainWindow::MainWindow(QWidget* parent)
setupObjectTreeModel(objectTreeModel, m_ui->objectTreeResults, m_ui->filterClass, m_ui->filterGC);
auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
+ m_ui->messages->hide();
if (path.isEmpty()) {
return allowEmpty;
}
@@ -492,7 +539,7 @@ MainWindow::MainWindow(QWidget* parent)
}
return false;
};
-
+#ifndef NO_K_LIB
auto validateInput = [this, validateInputFile]() {
m_ui->messages->hide();
m_ui->buttonBox->setEnabled(validateInputFile(m_ui->openFile->url().toLocalFile(), false)
@@ -507,6 +554,29 @@ MainWindow::MainWindow(QWidget* parent)
const auto base = m_ui->compareTo->url().toLocalFile();
loadFile(path, base);
});
+#else
+ m_ui->buttonBox->setEnabled(true);
+
+ auto validateInputAndLoadFile = [this, validateInputFile]() {
+ const auto path = m_ui->openFileEdit->text();
+ if (!validateInputFile(path, false)) {
+ return;
+ }
+ Q_ASSERT(!path.isEmpty());
+ const auto base = m_ui->compareToEdit->text();
+ if (!validateInputFile(base, true)) {
+ return;
+ }
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+ loadFile(path, base);
+ QApplication::restoreOverrideCursor();
+ };
+
+ connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, validateInputAndLoadFile);
+
+ connect(m_ui->openFileButton1, &QPushButton::clicked, this, &MainWindow::selectOpenFile);
+ connect(m_ui->openFileButton2, &QPushButton::clicked, this, &MainWindow::selectCompareToFile);
+#endif
setupStacks();
@@ -543,37 +613,49 @@ MainWindow::MainWindow(QWidget* parent)
m_ui->widget_12->hide();
}
- setWindowTitle(i18n("Heaptrack"));
+ setWindowTitle(AboutData::ShortName);
// closing the current file shows the stack page to open a new one
+#ifdef NO_K_LIB
+ m_openAction = new QAction(i18n("&Open..."), this);
+ m_openAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_O));
+ connect(m_openAction, &QAction::triggered, this, &MainWindow::closeFile);
+ m_openAction->setEnabled(false);
+ m_openNewAction = new QAction(i18n("&New"), this);
+ m_openNewAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N));
+ connect(m_openNewAction, &QAction::triggered, this, &MainWindow::openNewFile);
+ m_quitAction = new QAction(i18n("&Quit"), this);
+ connect(m_quitAction, &QAction::triggered, qApp, &QApplication::quit);
+
+ qApp->installEventFilter(this);
+#else
m_openAction = KStandardAction::open(this, SLOT(closeFile()), this);
m_openAction->setEnabled(false);
- m_ui->menu_File->addAction(m_openAction);
m_openNewAction = KStandardAction::openNew(this, SLOT(openNewFile()), this);
- m_ui->menu_File->addAction(m_openNewAction);
- m_closeAction = KStandardAction::close(this, SLOT(close()), this);
- m_ui->menu_File->addAction(m_closeAction);
m_quitAction = KStandardAction::quit(qApp, SLOT(quit()), this);
+#endif
+ m_ui->menu_File->addAction(m_openAction);
+ m_ui->menu_File->addAction(m_openNewAction);
m_ui->menu_File->addAction(m_quitAction);
}
MainWindow::~MainWindow()
{
- auto state = saveState(MAINWINDOW_VERSION);
- auto group = m_config->group(Config::Groups::MainWindow);
- group.writeEntry(Config::Entries::State, state);
+#ifdef NO_K_LIB
+ qApp->removeEventFilter(this);
+#endif
}
void MainWindow::loadFile(const QString& file, const QString& diffBase)
{
// TODO: support canceling of ongoing parse jobs
- m_closeAction->setEnabled(false);
m_ui->loadingLabel->setText(i18n("Loading file %1, please wait...", file));
if (diffBase.isEmpty()) {
- setWindowTitle(i18nc("%1: file name that is open", "Heaptrack - %1", QFileInfo(file).fileName()));
+ setWindowTitle(i18nc("%1: application name; %2: file name that is open", "%1 - %2",
+ AboutData::ShortName, QFileInfo(file).fileName()));
m_diffMode = false;
} else {
- setWindowTitle(i18nc("%1, %2: file names that are open", "Heaptrack - %1 compared to %2",
- QFileInfo(file).fileName(), QFileInfo(diffBase).fileName()));
+ setWindowTitle(i18nc("%1: application name; %2, %3: file names that are open", "%1 - %2 compared to %3",
+ AboutData::ShortName, QFileInfo(file).fileName(), QFileInfo(diffBase).fileName()));
m_diffMode = true;
}
m_ui->pages->setCurrentWidget(m_ui->loadingPage);
@@ -600,6 +682,12 @@ void MainWindow::closeFile()
emit clearData();
}
+void MainWindow::about()
+{
+ AboutDialog dlg(this);
+ dlg.exec();
+}
+
void MainWindow::showError(const QString& message)
{
m_ui->messages->setText(message);
@@ -643,9 +731,106 @@ void MainWindow::setupStacks()
auto tree = (widget == m_ui->topDownTab) ? m_ui->topDownResults : m_ui->bottomUpResults;
fillFromIndex(tree->selectionModel()->currentIndex());
}
+#ifdef QWT_FOUND
+ const auto chartWidget = dynamic_cast<ChartWidget*>(widget);
+ if (chartWidget) {
+ chartWidget->updateOnSelected(this);
+ chartWidget->setFocus(); // to handle keyboard events in the widget
+ }
+ else {
+ if (ChartWidget::HelpWindow != nullptr) {
+ ChartWidget::HelpWindow->hide();
+ }
+ const auto histogramWidget = dynamic_cast<HistogramWidget*>(widget);
+ if (histogramWidget) {
+ histogramWidget->updateOnSelected();
+ histogramWidget->setFocus();
+ }
+ }
+#endif
};
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, tabChanged);
connect(m_parser, &Parser::bottomUpDataAvailable, this, [tabChanged]() { tabChanged(0); });
m_ui->stacksDock->setVisible(false);
}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+#ifdef NO_K_LIB
+ QMainWindow::closeEvent(event);
+ {
+ QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
+ settings.setValue("mainWindowGeometry", saveGeometry());
+ settings.setValue("mainWindowState", saveState());
+#ifdef QWT_FOUND
+ settings.beginGroup("Charts");
+ settings.setValue("Options", ChartOptions::GlobalOptions);
+ settings.endGroup();
+#endif // QWT_FOUND
+ }
+#else
+ {
+ auto state = saveState(MAINWINDOW_VERSION);
+ auto group = m_config->group(Config::Groups::MainWindow);
+ group.writeEntry(Config::Entries::State, state);
+ }
+#endif // !NO_K_LIB
+ // the simplest and safest way to close this application (without deep redesing) is to terminate it,
+ // otherwise a crash is possible if the main window is being closed (and destroyed) while some background
+ // operations (e.g. parsing a source file) are still in progress; it happens because the code running
+ // in other threads may emit signals to already destroyed objects
+ quick_exit(0); // faster alternative to 'exit'
+}
+
+#ifdef NO_K_LIB
+bool MainWindow::eventFilter(QObject* object, QEvent* event)
+{
+ // could process arrow keys (left/right) for flamegraph (to implement back/forward) only from here
+ if ((event->type() == QEvent::KeyPress) &&
+ (m_ui->tabWidget->currentWidget() == m_ui->flameGraphTab))
+ {
+ // Qt5: sometimes (e.g. if Alt is pressed) 'object' is QWidgetWindow which is not a part
+ // of Qt public API so trying to detect it indirectly (see 2nd condition below)
+ if ((object == this) || (object->parent() == nullptr))
+ {
+ if (m_ui->flameGraphTab->handleKeyPress(static_cast<QKeyEvent*>(event)))
+ {
+ return true;
+ }
+ }
+ }
+ return QMainWindow::eventFilter(object, event);
+}
+
+static void selectFile(QWidget *parent, QLineEdit *fileNameEdit)
+{
+ QString fileName = QFileDialog::getOpenFileName(parent, "Select Data File",
+ "", "GZip files (*.gz);; All files (*)");
+ if (!fileName.isEmpty())
+ {
+ fileNameEdit->setText(fileName);
+ }
+}
+
+void MainWindow::selectOpenFile()
+{
+ selectFile(this, m_ui->openFileEdit);
+}
+
+void MainWindow::selectCompareToFile()
+{
+ selectFile(this, m_ui->compareToEdit);
+}
+#endif
+
+#ifdef QWT_FOUND
+void MainWindow::moveEvent(QMoveEvent *event)
+{
+ if (ChartWidget::HelpWindow != nullptr)
+ {
+ ChartWidget::HelpWindow->move(ChartWidget::HelpWindow->pos() +
+ (event->pos() - event->oldPos()));
+ }
+}
+#endif
diff --git a/src/analyze/gui/mainwindow.h b/src/analyze/gui/mainwindow.h
index b0d9b8d..29a0d25 100644
--- a/src/analyze/gui/mainwindow.h
+++ b/src/analyze/gui/mainwindow.h
@@ -19,9 +19,13 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
+#include "gui_config.h"
+
#include <QMainWindow>
+#ifndef NO_K_LIB
#include <KSharedConfig>
+#endif
namespace Ui {
class MainWindow;
@@ -42,22 +46,36 @@ public slots:
void loadFile(const QString& path, const QString& diffBase = {});
void openNewFile();
void closeFile();
+ void about();
signals:
void clearData();
+protected:
+ virtual void closeEvent(QCloseEvent *event) override;
+#ifdef NO_K_LIB
+ virtual bool eventFilter(QObject* object, QEvent* event) override;
+public slots:
+ void selectOpenFile();
+ void selectCompareToFile();
+#endif
+#ifdef QWT_FOUND
+protected:
+ virtual void moveEvent(QMoveEvent *event) override;
+#endif
private:
void showError(const QString& message);
void setupStacks();
QScopedPointer<Ui::MainWindow> m_ui;
Parser* m_parser;
+#ifndef NO_K_LIB
KSharedConfig::Ptr m_config;
+#endif
bool m_diffMode = false;
QAction* m_openAction = nullptr;
QAction* m_openNewAction = nullptr;
- QAction* m_closeAction = nullptr;
QAction* m_quitAction = nullptr;
};
diff --git a/src/analyze/gui/mainwindow.ui b/src/analyze/gui/mainwindow.ui
index 4f289c8..7d74135 100644
--- a/src/analyze/gui/mainwindow.ui
+++ b/src/analyze/gui/mainwindow.ui
@@ -864,7 +864,20 @@
</property>
</widget>
<addaction name="menu_File"/>
+ <widget class="QMenu" name="menu_Help">
+ <property name="title">
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="aboutAction"/>
+ </widget>
+ <addaction name="menu_File"/>
+ <addaction name="menu_Help"/>
</widget>
+ <action name="aboutAction">
+ <property name="text">
+ <string>&amp;About</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/src/analyze/gui/mainwindow_noklib.ui b/src/analyze/gui/mainwindow_noklib.ui
new file mode 100644
index 0000000..c5a4c97
--- /dev/null
+++ b/src/analyze/gui/mainwindow_noklib.ui
@@ -0,0 +1,912 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1196</width>
+ <height>796</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="messages"/>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="pages">
+ <widget class="QWidget" name="openPage">
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Open Memory Profiler Data</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="1" column="0">
+ <widget class="QLabel" name="openFileLabel">
+ <property name="text">
+ <string>Profile &amp;Data:</string>
+ </property>
+ <property name="buddy">
+ <cstring>openFileEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <item>
+ <widget class="QLineEdit" name="openFileEdit">
+ <property name="toolTip">
+ <string>&lt;qt&gt;&lt;p&gt;This field specifies the primary memory profiler data file. These files are called &lt;tt&gt;heaptrack.$APP.$PID.gz&lt;/tt&gt;. You can produce such a file by profiling your application, e.g. via:&lt;/p&gt;
+ &lt;pre&gt;&lt;code&gt;heaptrack &amp;lt;yourapplication&amp;gt; ...&lt;/code&gt;&lt;/pre&gt;
+ &lt;p&gt;Or, alternatively, you can attach to a running process via&lt;/p&gt;
+ &lt;pre&gt;&lt;code&gt;heaptrack --pid $(pidof &amp;lt;yourapplication&amp;gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/qt&gt;</string>
+ </property>
+ <property name="text">
+ <string>heaptrack.*.*.gz (PLEASE ENTER OR SELECT path/to/heaptrack.$APP.$PID.gz)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openFileButton1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="gui.qrc">
+ <normaloff>:/icons/fileopen.png</normaloff>:/icons/fileopen.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="compareToLabel">
+ <property name="text">
+ <string>Compare to:</string>
+ </property>
+ <property name="buddy">
+ <cstring>compareToEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QLineEdit" name="compareToEdit">
+ <property name="toolTip">
+ <string>&lt;qt&gt;You can optionally specify a second memory profiler data file to compare to. If set, this file will be used as a base and its cost gets subtracted from the primary data costs.&lt;/qt&gt;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openFileButton2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="icon">
+ <iconset resource="gui.qrc">
+ <normaloff>:/icons/fileopen.png</normaloff>:/icons/fileopen.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Open</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="loadingPage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="loadingLabel">
+ <property name="text">
+ <string notr="true">Loading file, please wait...</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="loadingProgress">
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="progressLabel">
+ <property name="text">
+ <string notr="true">Progress Message...</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="resultsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="summaryTab">
+ <attribute name="title">
+ <string>Summary</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QWidget" name="widget_10" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLabel" name="summaryLeft">
+ <property name="text">
+ <string notr="true">summary</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="summaryCenter">
+ <property name="text">
+ <string notr="true">summary</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="summaryRight">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">summary goes here</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="widget_5" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QLabel" name="topPeakLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that allocated the most memory at a given time.</string>
+ </property>
+ <property name="text">
+ <string>Peak Contributions</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topPeak">
+ <property name="toolTip">
+ <string>List of functions that allocated the most memory at a given time.</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget_12" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="QLabel" name="topPeakInstancesLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that allocated the most instances (allocated and not freed up to at a given time).</string>
+ </property>
+ <property name="text">
+ <string>Peak Instances Number Contributions</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topPeakInstances">
+ <property name="toolTip">
+ <string>List of functions that allocated the most instances (allocated and not freed up to at a given time).</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget_6" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QLabel" name="topLeakedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that leak the most memory.</string>
+ </property>
+ <property name="text">
+ <string>Largest Memory Leaks</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topLeaked">
+ <property name="toolTip">
+ <string>List of functions that leak the most memory.</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget_7" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="QLabel" name="topAllocationsLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that allocate memory most often.</string>
+ </property>
+ <property name="text">
+ <string>Most Memory Allocations</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topAllocations">
+ <property name="toolTip">
+ <string>List of functions that allocate memory most often.</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget_8" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QLabel" name="topTemporaryLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that produced the most temporary memory allocations.</string>
+ </property>
+ <property name="text">
+ <string>Most Temporary Allocations</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topTemporary">
+ <property name="toolTip">
+ <string>List of functions that produced the most temporary memory allocations.</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget_9" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QLabel" name="topAllocatedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="toolTip">
+ <string>List of functions that allocated the most memory overall, ignoring deallocations.</string>
+ </property>
+ <property name="text">
+ <string>Most Memory Allocated</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topAllocated">
+ <property name="toolTip">
+ <string>List of functions that allocated the most memory overall, ignoring deallocations.</string>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="bottomUpTab">
+ <attribute name="title">
+ <string>Bottom-Up</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="bottomUpFilterFunction">
+ <property name="placeholderText">
+ <string>filter by function...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="bottomUpFilterFile">
+ <property name="placeholderText">
+ <string>filter by file...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="bottomUpFilterModule">
+ <property name="placeholderText">
+ <string>filter by module...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="bottomUpResults">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="indentation">
+ <number>10</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>true</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="callerCalleeTab">
+ <attribute name="title">
+ <string>Caller / Callee</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <item>
+ <widget class="QWidget" name="widget_11" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="callerCalleeFilterFunction">
+ <property name="placeholderText">
+ <string>filter by function...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="callerCalleeFilterFile">
+ <property name="placeholderText">
+ <string>filter by file...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="callerCalleeFilterModule">
+ <property name="placeholderText">
+ <string>filter by module...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="callerCalleeResults">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="topDownTab">
+ <attribute name="title">
+ <string>Top-Down</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="topDownFilterFunction">
+ <property name="placeholderText">
+ <string>filter by function...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="topDownFilterFile">
+ <property name="placeholderText">
+ <string>filter by file...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="topDownFilterModule">
+ <property name="placeholderText">
+ <string>filter by module...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="topDownResults">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="indentation">
+ <number>10</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>true</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="heapTab">
+ <attribute name="title">
+ <string>Managed Heap</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>GC #</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="filterGC"/>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="filterClass">
+ <property name="placeholderText">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="objectTreeResults">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>100</number>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="FlameGraph" name="flameGraphTab">
+ <attribute name="title">
+ <string>Flame Graph</string>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QDockWidget" name="stacksDock">
+ <property name="features">
+ <set>QDockWidget::NoDockWidgetFeatures</set>
+ </property>
+ <property name="windowTitle">
+ <string>S&amp;tacks</string>
+ </property>
+ <attribute name="dockWidgetArea">
+ <number>2</number>
+ </attribute>
+ <widget class="QWidget" name="stacksDockContents">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QWidget" name="widget_3" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="stackSpinnerLabel">
+ <property name="text">
+ <string>Selected Stack:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="stackSpinner"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="stacksTree">
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1196</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ </widget>
+ <widget class="QMenu" name="menu_Help">
+ <property name="title">
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="aboutAction"/>
+ </widget>
+ <addaction name="menu_File"/>
+ <addaction name="menu_Help"/>
+ </widget>
+ <action name="aboutAction">
+ <property name="text">
+ <string>&amp;About</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>FlameGraph</class>
+ <extends>QWidget</extends>
+ <header>flamegraph.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="gui.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/analyze/gui/noklib.h b/src/analyze/gui/noklib.h
new file mode 100644
index 0000000..bf42184
--- /dev/null
+++ b/src/analyze/gui/noklib.h
@@ -0,0 +1,91 @@
+#ifndef NOKLIB_H
+#define NOKLIB_H
+
+#include <QtCore/QString>
+
+inline QString i18n(const char *text)
+{
+ return text;
+}
+
+template <typename A1>
+inline QString i18n(const char *text, const A1 &a1)
+{
+ return QString(text).arg(a1);
+}
+
+template <typename A1, typename A2>
+inline QString i18n(const char *text, const A1 &a1, const A2 &a2)
+{
+ return QString(text).arg(a1).arg(a2);
+}
+
+template <typename A1, typename A2, typename A3>
+inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3);
+}
+
+template <typename A1, typename A2, typename A3, typename A4>
+inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4);
+}
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5>
+inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4).arg(a5);
+}
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
+inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4).arg(a5).arg(a6);
+}
+
+template <typename A1>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1)
+{
+ return QString(text).arg(a1);
+}
+
+template <typename A1, typename A2>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1, const A2 &a2)
+{
+ return QString(text).arg(a1).arg(a2);
+}
+
+template <typename A1, typename A2, typename A3>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1, const A2 &a2, const A3 &a3)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3);
+}
+
+template <typename A1, typename A2, typename A3, typename A4>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4);
+}
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4,
+ const A5 &a5)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4).arg(a5);
+}
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
+inline QString i18nc(const char * /*comment*/, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4,
+ const A5 &a5, const A6 &a6)
+{
+ return QString(text).arg(a1).arg(a2).arg(a3).arg(a4).arg(a5).arg(a6);
+}
+
+template <typename A1>
+inline QString i18np(const char * /*singular*/, const char *plural, const A1 &a1)
+{
+ return QString(plural).arg(a1);
+}
+
+#endif // NOKLIB_H
diff --git a/src/analyze/gui/objecttreemodel.cpp b/src/analyze/gui/objecttreemodel.cpp
index cea03d1..b9d6a41 100644
--- a/src/analyze/gui/objecttreemodel.cpp
+++ b/src/analyze/gui/objecttreemodel.cpp
@@ -21,8 +21,14 @@
#include <QDebug>
#include <QTextStream>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KFormat>
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include <cmath>
@@ -122,12 +128,12 @@ QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const
if (role == SortRole) {
return static_cast<qint64>(row->allocated);
}
- return m_format.formatByteSize(row->allocated, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row->allocated, 1);
case ReferencedSizeColumn:
if (role == SortRole) {
return static_cast<qint64>(row->referenced);
}
- return m_format.formatByteSize(row->referenced, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row->referenced, 1);
case GCNumColumn:
return static_cast<quint64>(row->gcNum);
case NUM_COLUMNS:
diff --git a/src/analyze/gui/objecttreemodel.h b/src/analyze/gui/objecttreemodel.h
index 2202da5..f011f9f 100644
--- a/src/analyze/gui/objecttreemodel.h
+++ b/src/analyze/gui/objecttreemodel.h
@@ -22,7 +22,10 @@
#include <QAbstractItemModel>
#include <QVector>
+#ifndef NO_K_LIB
#include <KFormat>
+#endif
+
#include <string.h>
struct ObjectRowData
@@ -96,7 +99,9 @@ private:
int rowOf(const ObjectRowData* row) const;
ObjectTreeData m_data;
+#ifndef NO_K_LIB
KFormat m_format;
+#endif
};
#endif // OBJECTTREEMODEL_H
diff --git a/src/analyze/gui/objecttreeproxy.cpp b/src/analyze/gui/objecttreeproxy.cpp
index 53e32e0..a638e94 100644
--- a/src/analyze/gui/objecttreeproxy.cpp
+++ b/src/analyze/gui/objecttreeproxy.cpp
@@ -19,10 +19,19 @@
#include "objecttreeproxy.h"
ObjectTreeProxy::ObjectTreeProxy(int nameColumn, int gcColumn, QObject* parent)
+#ifdef NO_K_LIB
+ : QSortFilterProxyModel(parent)
+#else
: KRecursiveFilterProxyModel(parent)
+#endif
, m_nameColumn(nameColumn)
, m_gcColumn(gcColumn)
{
+#if QT_VERSION >= 0x050A00
+ setRecursiveFilteringEnabled(true);
+#else
+ #pragma message("Qt 5.10+ is required, otherwise text filtering in GUI will not work as expected")
+#endif
}
ObjectTreeProxy::~ObjectTreeProxy() = default;
diff --git a/src/analyze/gui/objecttreeproxy.h b/src/analyze/gui/objecttreeproxy.h
index 843fa74..8bb4911 100644
--- a/src/analyze/gui/objecttreeproxy.h
+++ b/src/analyze/gui/objecttreeproxy.h
@@ -19,9 +19,18 @@
#ifndef OBJECTTREEPROXY_H
#define OBJECTTREEPROXY_H
+#ifdef NO_K_LIB
+#include <QSortFilterProxyModel>
+#else
#include <KRecursiveFilterProxyModel>
-
-class ObjectTreeProxy final : public KRecursiveFilterProxyModel
+#endif
+
+class ObjectTreeProxy final : public
+#ifdef NO_K_LIB
+ QSortFilterProxyModel
+#else
+ KRecursiveFilterProxyModel
+#endif
{
Q_OBJECT
public:
@@ -33,7 +42,11 @@ public slots:
void setGCFilter(int gcFilter);
private:
+#ifdef NO_K_LIB
+ bool acceptRow(int source_row, const QModelIndex& source_parent) const;
+#else
bool acceptRow(int source_row, const QModelIndex& source_parent) const override;
+#endif
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
int m_nameColumn;
diff --git a/src/analyze/gui/parser.cpp b/src/analyze/gui/parser.cpp
index f73fcb3..940ba9a 100644
--- a/src/analyze/gui/parser.cpp
+++ b/src/analyze/gui/parser.cpp
@@ -18,8 +18,15 @@
#include "parser.h"
+#ifdef NO_K_LIB
+#include "noklib.h"
+#ifdef THREAD_WEAVER
+#include <threadweaver.h>
+#endif // THREAD_WEAVER
+#else
#include <KLocalizedString>
#include <ThreadWeaver/ThreadWeaver>
+#endif // NO_K_LIB
#include <QDebug>
@@ -851,108 +858,137 @@ Parser::~Parser() = default;
void Parser::parse(const QString& path, const QString& diffBase)
{
- using namespace ThreadWeaver;
- stream() << make_job([this, path, diffBase]() {
- const auto stdPath = path.toStdString();
- auto data = make_shared<ParserData>();
- emit progressMessageAvailable(i18n("parsing data..."));
-
- if (!diffBase.isEmpty()) {
- ParserData diffData;
- auto readBase =
- async(launch::async, [&diffData, diffBase]() { return diffData.read(diffBase.toStdString()); });
- if (!data->read(stdPath)) {
- emit failedToOpen(path);
- return;
- }
- if (!readBase.get()) {
- emit failedToOpen(diffBase);
- return;
- }
- data->diff(diffData);
- data->stringCache.diffMode = true;
- } else if (!data->read(stdPath)) {
+#ifndef THREAD_WEAVER
+ parseJob(path, diffBase);
+#else
+ ThreadWeaver::stream() << ThreadWeaver::make_job([this, path, diffBase]() {
+ parseJob(path, diffBase);
+ });
+#endif
+}
+
+void Parser::parseJob(const QString& path, const QString& diffBase)
+{
+ const auto stdPath = path.toStdString();
+ auto data = make_shared<ParserData>();
+ emit progressMessageAvailable(i18n("parsing data..."));
+
+ if (!diffBase.isEmpty()) {
+ ParserData diffData;
+ auto readBase =
+ async(launch::async, [&diffData, diffBase]() { return diffData.read(diffBase.toStdString()); });
+ if (!data->read(stdPath)) {
emit failedToOpen(path);
return;
}
+ if (!readBase.get()) {
+ emit failedToOpen(diffBase);
+ return;
+ }
+ data->diff(diffData);
+ data->stringCache.diffMode = true;
+ } else if (!data->read(stdPath)) {
+ emit failedToOpen(path);
+ return;
+ }
- data->updateStringCache();
+ data->updateStringCache();
- AllocationData::Stats *partCoreclr;
- AllocationData::Stats *partNonCoreclr;
- AllocationData::Stats *partUntracked;
- AllocationData::Stats *partUnknown;
+ AllocationData::Stats *partCoreclr;
+ AllocationData::Stats *partNonCoreclr;
+ AllocationData::Stats *partUntracked;
+ AllocationData::Stats *partUnknown;
- if (AllocationData::display == AllocationData::DisplayId::malloc)
- {
- partCoreclr = &data->partCoreclr;
- partNonCoreclr = &data->partNonCoreclr;
- partUntracked = &data->partUntracked;
- partUnknown = &data->partUnknown;
- }
- else
- {
- partCoreclr = &data->partCoreclrMMAP;
- partNonCoreclr = &data->partNonCoreclrMMAP;
- partUntracked = &data->partUntrackedMMAP;
- partUnknown = &data->partUnknownMMAP;
- }
+ if (AllocationData::display == AllocationData::DisplayId::malloc)
+ {
+ partCoreclr = &data->partCoreclr;
+ partNonCoreclr = &data->partNonCoreclr;
+ partUntracked = &data->partUntracked;
+ partUnknown = &data->partUnknown;
+ }
+ else
+ {
+ partCoreclr = &data->partCoreclrMMAP;
+ partNonCoreclr = &data->partNonCoreclrMMAP;
+ partUntracked = &data->partUntrackedMMAP;
+ partUnknown = &data->partUnknownMMAP;
+ }
- emit summaryAvailable({QString::fromStdString(data->debuggee), *data->totalCost.getDisplay(), data->totalTime, data->getPeakTime(),
- data->peakRSS * 1024,
- data->systemInfo.pages * data->systemInfo.pageSize, data->fromAttached,
- *partCoreclr, *partNonCoreclr, *partUntracked, *partUnknown});
+ emit summaryAvailable({QString::fromStdString(data->debuggee), *data->totalCost.getDisplay(), data->totalTime, data->getPeakTime(),
+ data->peakRSS * 1024,
+ data->systemInfo.pages * data->systemInfo.pageSize, data->fromAttached,
+ *partCoreclr, *partNonCoreclr, *partUntracked, *partUnknown});
- emit progressMessageAvailable(i18n("merging allocations..."));
- // merge allocations before modifying the data again
- const auto mergedAllocations = mergeAllocations(*data, true);
- emit bottomUpDataAvailable(mergedAllocations);
+ emit progressMessageAvailable(i18n("merging allocations..."));
+ // merge allocations before modifying the data again
+ const auto mergedAllocations = mergeAllocations(*data, true);
+ emit bottomUpDataAvailable(mergedAllocations);
- if (!AccumulatedTraceData::isHideUnmanagedStackParts) {
- emit bottomUpFilterOutLeavesDataAvailable(mergeAllocations(*data, false));
- } else {
- emit bottomUpFilterOutLeavesDataAvailable(mergedAllocations);
- }
-
- if (AllocationData::display == AllocationData::DisplayId::managed) {
- const auto objectTreeBottomUpData = buildObjectTree(*data);
- emit objectTreeBottomUpDataAvailable(objectTreeBottomUpData);
- }
-
- // also calculate the size histogram
- emit progressMessageAvailable(i18n("building size histogram..."));
- const auto sizeHistogram = buildSizeHistogram(*data);
- emit sizeHistogramDataAvailable(sizeHistogram);
- // now data can be modified again for the chart data evaluation
-
- const auto diffMode = data->stringCache.diffMode;
- emit progressMessageAvailable(i18n("building charts..."));
- auto parallel = new Collection;
- *parallel << make_job([this, mergedAllocations]() {
- const auto topDownData = toTopDownData(mergedAllocations);
- emit topDownDataAvailable(topDownData);
- }) << make_job([this, mergedAllocations, diffMode]() {
- const auto callerCalleeData = toCallerCalleeData(mergedAllocations, diffMode);
- emit callerCalleeDataAvailable(callerCalleeData);
- });
- if (!data->stringCache.diffMode) {
- // only build charts when we are not diffing
- *parallel << make_job([this, data, stdPath]() {
- // this mutates data, and thus anything running in parallel must
- // not access data
- data->prepareBuildCharts();
- data->read(stdPath);
- emit consumedChartDataAvailable(data->consumedChartData);
- emit instancesChartDataAvailable(data->instancesChartData);
- emit allocationsChartDataAvailable(data->allocationsChartData);
- emit allocatedChartDataAvailable(data->allocatedChartData);
- emit temporaryChartDataAvailable(data->temporaryChartData);
- });
- }
-
- auto sequential = new Sequence;
- *sequential << parallel << make_job([this]() { emit finished(); });
-
- stream() << sequential;
- });
+ if (!AccumulatedTraceData::isHideUnmanagedStackParts) {
+ emit bottomUpFilterOutLeavesDataAvailable(mergeAllocations(*data, false));
+ } else {
+ emit bottomUpFilterOutLeavesDataAvailable(mergedAllocations);
+ }
+
+ if (AllocationData::display == AllocationData::DisplayId::managed) {
+ const auto objectTreeBottomUpData = buildObjectTree(*data);
+ emit objectTreeBottomUpDataAvailable(objectTreeBottomUpData);
+ }
+
+ // also calculate the size histogram
+ emit progressMessageAvailable(i18n("building size histogram..."));
+ const auto sizeHistogram = buildSizeHistogram(*data);
+ emit sizeHistogramDataAvailable(sizeHistogram);
+ // now data can be modified again for the chart data evaluation
+
+ const auto diffMode = data->stringCache.diffMode;
+ emit progressMessageAvailable(i18n("building charts..."));
+#ifdef THREAD_WEAVER
+ using namespace ThreadWeaver;
+ auto parallel = new Collection;
+ *parallel << make_job([this, mergedAllocations]()
+#endif
+ {
+ const auto topDownData = toTopDownData(mergedAllocations);
+ emit topDownDataAvailable(topDownData);
+ }
+#ifdef THREAD_WEAVER
+ ) << make_job([this, mergedAllocations, diffMode]()
+#endif
+ {
+ const auto callerCalleeData = toCallerCalleeData(mergedAllocations, diffMode);
+ emit callerCalleeDataAvailable(callerCalleeData);
+ }
+#ifdef THREAD_WEAVER
+ );
+#endif
+ if (!data->stringCache.diffMode) {
+ // only build charts when we are not diffing
+#ifdef THREAD_WEAVER
+ *parallel << make_job([this, data, stdPath]()
+#endif
+ {
+ // this mutates data, and thus anything running in parallel must
+ // not access data
+ data->prepareBuildCharts();
+ data->read(stdPath);
+ emit consumedChartDataAvailable(data->consumedChartData);
+ emit instancesChartDataAvailable(data->instancesChartData);
+ emit allocationsChartDataAvailable(data->allocationsChartData);
+ emit allocatedChartDataAvailable(data->allocatedChartData);
+ emit temporaryChartDataAvailable(data->temporaryChartData);
+ }
+#ifdef THREAD_WEAVER
+ );
+#endif
+ }
+
+#ifndef THREAD_WEAVER
+ emit finished();
+#else
+ auto sequential = new Sequence;
+ *sequential << parallel << make_job([this]() { emit finished(); });
+
+ stream() << sequential;
+#endif
}
diff --git a/src/analyze/gui/parser.h b/src/analyze/gui/parser.h
index 7503b55..2554626 100644
--- a/src/analyze/gui/parser.h
+++ b/src/analyze/gui/parser.h
@@ -54,6 +54,9 @@ signals:
void objectTreeBottomUpDataAvailable(const ObjectTreeData& data);
void finished();
void failedToOpen(const QString& path);
+
+private:
+ void parseJob(const QString& path, const QString& diffBase);
};
#endif // PARSER_H
diff --git a/src/analyze/gui/stacksmodel.cpp b/src/analyze/gui/stacksmodel.cpp
index 9e29e16..2c01fdd 100644
--- a/src/analyze/gui/stacksmodel.cpp
+++ b/src/analyze/gui/stacksmodel.cpp
@@ -19,7 +19,11 @@
#include "stacksmodel.h"
#include "treemodel.h"
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KLocalizedString>
+#endif
#include <QDebug>
diff --git a/src/analyze/gui/topproxy.cpp b/src/analyze/gui/topproxy.cpp
index ad6cbb6..909729d 100644
--- a/src/analyze/gui/topproxy.cpp
+++ b/src/analyze/gui/topproxy.cpp
@@ -18,8 +18,6 @@
#include "topproxy.h"
-#include <KLocalizedString>
-
namespace {
TreeModel::Columns toSource(TopProxy::Type type)
{
diff --git a/src/analyze/gui/treemodel.cpp b/src/analyze/gui/treemodel.cpp
index 09b6b6c..3738a89 100644
--- a/src/analyze/gui/treemodel.cpp
+++ b/src/analyze/gui/treemodel.cpp
@@ -21,8 +21,14 @@
#include <QDebug>
#include <QTextStream>
+#ifdef NO_K_LIB
+#include "noklib.h"
+#else
#include <KFormat>
#include <KLocalizedString>
+#endif
+
+#include "util.h"
#include <cmath>
@@ -143,7 +149,7 @@ QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int rol
"called. Function symbol and file "
"information "
"may be unknown when debug information was missing when "
- "heaptrack was run.</qt>");
+ "the memory profiler was run.</qt>");
case NUM_COLUMNS:
break;
}
@@ -165,7 +171,7 @@ QVariant TreeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(abs(row->cost.allocated));
}
- return m_format.formatByteSize(row->cost.allocated, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row->cost.allocated, 1);
case AllocationsColumn:
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(abs(row->cost.allocations));
@@ -180,7 +186,7 @@ QVariant TreeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(abs(row->cost.peak));
} else {
- return m_format.formatByteSize(row->cost.peak, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row->cost.peak, 1);
}
case PeakInstancesColumn:
if (role == SortRole || role == MaxCostRole) {
@@ -191,7 +197,7 @@ QVariant TreeModel::data(const QModelIndex& index, int role) const
if (role == SortRole || role == MaxCostRole) {
return static_cast<qint64>(abs(row->cost.leaked));
} else {
- return m_format.formatByteSize(row->cost.leaked, 1, KFormat::JEDECBinaryDialect);
+ return Util::formatByteSize(row->cost.leaked, 1);
}
case FunctionColumn:
return row->location->function;
@@ -225,7 +231,6 @@ QVariant TreeModel::data(const QModelIndex& index, int role) const
}
stream << '\n';
stream << '\n';
- KFormat format;
const auto allocatedFraction =
QString::number(double(row->cost.allocated) * 100. / m_maxCost.cost.allocated, 'g', 3);
const auto peakFraction = QString::number(double(row->cost.peak) * 100. / m_maxCost.cost.peak, 'g', 3);
@@ -237,11 +242,11 @@ QVariant TreeModel::data(const QModelIndex& index, int role) const
const auto temporaryFractionTotal =
QString::number(double(row->cost.temporary) * 100. / m_maxCost.cost.temporary, 'g', 3);
stream << i18n("allocated: %1 (%2% of total)\n",
- format.formatByteSize(row->cost.allocated, 1, KFormat::JEDECBinaryDialect), allocatedFraction);
+ Util::formatByteSize(row->cost.allocated, 1), allocatedFraction);
stream << i18n("peak contribution: %1 (%2% of total)\n",
- format.formatByteSize(row->cost.peak, 1, KFormat::JEDECBinaryDialect), peakFraction);
+ Util::formatByteSize(row->cost.peak, 1), peakFraction);
stream << i18n("leaked: %1 (%2% of total)\n",
- format.formatByteSize(row->cost.leaked, 1, KFormat::JEDECBinaryDialect), leakedFraction);
+ Util::formatByteSize(row->cost.leaked, 1), leakedFraction);
stream << i18n("allocations: %1 (%2% of total)\n", row->cost.allocations, allocationsFraction);
stream << i18n("temporary: %1 (%2% of allocations, %3% of total)\n", row->cost.temporary, temporaryFraction,
temporaryFractionTotal);
diff --git a/src/analyze/gui/treemodel.h b/src/analyze/gui/treemodel.h
index a8903f7..f46da65 100644
--- a/src/analyze/gui/treemodel.h
+++ b/src/analyze/gui/treemodel.h
@@ -22,7 +22,9 @@
#include <QAbstractItemModel>
#include <QVector>
+#ifndef NO_K_LIB
#include <KFormat>
+#endif
#include "../allocationdata.h"
#include "locationdata.h"
@@ -96,7 +98,9 @@ private:
TreeData m_data;
RowData m_maxCost;
// TODO: update via global event filter when the locale changes (changeEvent)
+#ifndef NO_K_LIB
KFormat m_format;
+#endif
};
#endif // TREEMODEL_H
diff --git a/src/analyze/gui/treeproxy.cpp b/src/analyze/gui/treeproxy.cpp
index 93d96b7..db4a33b 100644
--- a/src/analyze/gui/treeproxy.cpp
+++ b/src/analyze/gui/treeproxy.cpp
@@ -19,11 +19,20 @@
#include "treeproxy.h"
TreeProxy::TreeProxy(int functionColumn, int fileColumn, int moduleColumn, QObject* parent)
+#ifdef NO_K_LIB
+ : QSortFilterProxyModel(parent)
+#else
: KRecursiveFilterProxyModel(parent)
+#endif
, m_functionColumn(functionColumn)
, m_fileColumn(fileColumn)
, m_moduleColumn(moduleColumn)
{
+#if QT_VERSION >= 0x050A00
+ setRecursiveFilteringEnabled(true);
+#else
+ #pragma message("Qt 5.10+ is required, otherwise text filtering in GUI will not work as expected")
+#endif
}
TreeProxy::~TreeProxy() = default;
@@ -72,3 +81,10 @@ bool TreeProxy::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
}
return true;
}
+
+#ifdef NO_K_LIB
+bool TreeProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ return acceptRow(sourceRow, sourceParent);
+}
+#endif
diff --git a/src/analyze/gui/treeproxy.h b/src/analyze/gui/treeproxy.h
index 46e2247..219b0d4 100644
--- a/src/analyze/gui/treeproxy.h
+++ b/src/analyze/gui/treeproxy.h
@@ -19,9 +19,18 @@
#ifndef TREEPROXY_H
#define TREEPROXY_H
+#ifdef NO_K_LIB
+#include <QSortFilterProxyModel>
+#else
#include <KRecursiveFilterProxyModel>
-
-class TreeProxy final : public KRecursiveFilterProxyModel
+#endif
+
+class TreeProxy final : public
+#ifdef NO_K_LIB
+ QSortFilterProxyModel
+#else
+ KRecursiveFilterProxyModel
+#endif
{
Q_OBJECT
public:
@@ -34,7 +43,12 @@ public slots:
void setModuleFilter(const QString& moduleFilter);
private:
+#ifdef NO_K_LIB
+ bool acceptRow(int source_row, const QModelIndex& source_parent) const;
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+#else
bool acceptRow(int source_row, const QModelIndex& source_parent) const override;
+#endif
int m_functionColumn;
int m_fileColumn;
diff --git a/src/analyze/gui/util.cpp b/src/analyze/gui/util.cpp
index 48c260d..8ec5a2e 100644
--- a/src/analyze/gui/util.cpp
+++ b/src/analyze/gui/util.cpp
@@ -18,9 +18,22 @@
#include "util.h"
+#include <QRegularExpression>
#include <QString>
-QString Util::formatTime(qint64 ms)
+#ifndef NO_K_LIB
+#include <KFormat>
+#endif
+
+#ifdef QWT_FOUND
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QMessageBox>
+#endif
+
+namespace Util {
+
+QString formatTime(qint64 ms)
{
if (ms > 60000) {
// minutes
@@ -30,3 +43,157 @@ QString Util::formatTime(qint64 ms)
return QString::number(double(ms) / 1000, 'g', 3) + QLatin1Char('s');
}
}
+
+QString formatByteSize(double size, int precision)
+{
+#ifndef NO_K_LIB
+ static KFormat format;
+ return format.formatByteSize(size, precision, KFormat::JEDECBinaryDialect);
+#else
+ int32_t divider = 0;
+ QString suffix;
+ if (size < 1024)
+ {
+ suffix = "B";
+ }
+ else
+ {
+ const int32_t LimitMB = 1024 * 1024;
+ if (size < LimitMB)
+ {
+ divider = 1024;
+ suffix = "KB";
+ }
+ else
+ {
+ const int32_t LimitGB = LimitMB * 1024;
+ if (size < LimitGB)
+ {
+ divider = LimitMB;
+ suffix = "MB";
+ }
+ else
+ {
+ divider = LimitGB;
+ suffix = "GB";
+ }
+ }
+ }
+ QString result;
+ if (divider > 1)
+ {
+ size /= divider;
+ }
+ else if ((int32_t)size == size)
+ {
+ precision = 0;
+ }
+ result = QString::number(size, 'f', precision);
+ result += ' ';
+ result += suffix;
+ return result;
+#endif
+}
+
+QString wrapLabel(QString label, int maxLineLength, int lastLineExtra,
+ const QString& delimiter)
+{
+ int labelLength = label.size();
+ if (labelLength + lastLineExtra <= maxLineLength)
+ {
+ return label.toHtmlEscaped();
+ }
+ static QRegularExpression delimBefore("[(<]");
+ static QRegularExpression delimAfter("[- .,)>\\/]");
+ QString result;
+ do
+ {
+ int i = -1;
+ int wrapAfter = 0;
+ int i1 = label.indexOf(delimBefore, maxLineLength);
+ int i2 = label.indexOf(delimAfter, maxLineLength - 1);
+ if (i1 >= 0)
+ {
+ if (i2 >= 0)
+ {
+ if (i2 < i1)
+ {
+ i = i2;
+ wrapAfter = 1;
+ }
+ else
+ {
+ i = i1;
+ }
+ }
+ else
+ {
+ i = i1;
+ }
+ }
+ else
+ {
+ i = i2;
+ wrapAfter = 1;
+ }
+ if (i < 0)
+ {
+ break;
+ }
+ i += wrapAfter;
+ result += label.left(i).toHtmlEscaped();
+ label.remove(0, i);
+ if (label.isEmpty()) // special: avoid <br> at the end
+ {
+ return result;
+ }
+ result += delimiter;
+ labelLength -= i;
+ }
+ while (labelLength + lastLineExtra > maxLineLength);
+ result += label.toHtmlEscaped();
+ return result;
+}
+
+#ifdef QWT_FOUND
+bool isUnresolvedFunction(const QString &functionName)
+{
+ return functionName.startsWith("<unresolved function>");
+}
+
+bool exportChart(QWidget *parent, QWidget &chartWidget, const QString &chartName)
+{
+ QString selectedFilter;
+ QString saveFilename = QFileDialog::getSaveFileName(parent, "Save Chart As",
+ chartName, "PNG (*.png);; BMP (*.bmp);; JPEG (*.jpg *.jpeg)", &selectedFilter);
+ if (!saveFilename.isEmpty())
+ {
+ QFileInfo fi(saveFilename);
+ if (fi.suffix().isEmpty()) // can be on some platforms
+ {
+ int i = selectedFilter.indexOf("*.");
+ if (i >= 0)
+ {
+ static QRegularExpression delimiters("[ )]");
+ i += 2;
+ int j = selectedFilter.indexOf(delimiters, i);
+ if (j > i)
+ {
+ --i;
+ QString suffix = selectedFilter.mid(i, j - i);
+ saveFilename += suffix;
+ }
+ }
+ }
+ if (chartWidget.grab().save(saveFilename, nullptr, 90))
+ {
+ return true;
+ }
+ QMessageBox::warning(parent, "Error",
+ QString("Cannot save the chart to \"%1\".").arg(saveFilename), QMessageBox::Ok);
+ }
+ return false;
+}
+#endif
+
+} // namespace Util
diff --git a/src/analyze/gui/util.h b/src/analyze/gui/util.h
index f19e227..f2dfaa5 100644
--- a/src/analyze/gui/util.h
+++ b/src/analyze/gui/util.h
@@ -19,14 +19,29 @@
#ifndef UTIL_H
#define UTIL_H
+#include "gui_config.h"
+
#include <qglobal.h>
+#include <QString>
-class QString;
+#ifdef QWT_FOUND
+#include <QWidget>
+#endif
namespace Util {
QString formatTime(qint64 ms);
+QString formatByteSize(double size, int precision = 1);
+
+QString wrapLabel(QString label, int maxLineLength, int lastLineExtra = 0,
+ const QString &delimiter = QString("<br>"));
+
+#ifdef QWT_FOUND
+bool isUnresolvedFunction(const QString &functionName);
+
+bool exportChart(QWidget *parent, QWidget &chartWidget, const QString &chartName);
+#endif
}
#endif // UTIL_H
diff --git a/src/heaptrack_gui.pro b/src/heaptrack_gui.pro
new file mode 100644
index 0000000..a6fea99
--- /dev/null
+++ b/src/heaptrack_gui.pro
@@ -0,0 +1,254 @@
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = heaptrack_gui
+TEMPLATE = app
+
+DEFINES += QT_DEPRECATED_WARNINGS
+
+INCLUDEPATH += $$PWD/analyze/gui
+
+# build heaptrack for Samsung Tizen OS
+CONFIG += SAMSUNG_TIZEN_BRANCH
+
+SAMSUNG_TIZEN_BRANCH {
+ DEFINES += SAMSUNG_TIZEN_BRANCH
+ TARGET = TizenMemoryProfiler
+}
+
+# uncomment the next line to disable using KDE libraries unconditionally (i.e. on Linux too)
+#CONFIG *= NO_K_LIB NO_K_CHART
+
+# Third-party libraries which may be used:
+
+# QWT: http://qwt.sourceforge.net (SVN: https://svn.code.sf.net/p/qwt/code/trunk).
+# Used on Windows but not on Linux by default.
+
+# ThreadWeaver: https://cgit.kde.org/threadweaver.git (git://anongit.kde.org/threadweaver.git).
+# Used on both Windows and Linux by default.
+
+# comment the next line to not use ThreadWeaver library (used to parse data files faster)
+!NO_K_LIB:CONFIG += THREAD_WEAVER
+
+win32 {
+ # comment the next line to not use QWT library (charts will not be displayed in this case)
+ CONFIG += QWT_CHART
+
+ CONFIG *= NO_K_LIB NO_K_CHART
+
+ DEFINES *= WINDOWS
+ INCLUDEPATH += $$(BOOST_LIB)
+ LIBS += -L$$(BOOST_LIB)/stage/lib
+
+ RC_ICONS += analyze/gui/icons/if_diagram_v2-14_37134.ico
+
+ CONFIG(debug, debug|release) {
+ TARGET = $${TARGET}Dbg
+ DESTDIR = ../bin/debug
+ }
+ else {
+ DESTDIR = ../bin/release
+ }
+}
+
+unix {
+ # uncomment the next line to use QWT instead of KChart on Linux
+# CONFIG += QWT_CHART
+
+ QWT_CHART {
+ CONFIG *= NO_K_CHART USE_CHART QWT_CHART
+ INCLUDEPATH += /usr/include/qwt
+ LIBS += -lqwt-qt5 # correct the library name if needed (e.g. to 'qwt')
+ }
+ else {
+ !NO_K_LIB {
+ CONFIG *= USE_CHART
+ }
+ }
+
+ LIBS += -lboost_program_options -lboost_iostreams -lpthread
+}
+
+QWT_CHART {
+ # QMAKEFEATURES and QWT_ROOT environment variables must be set (e.g. to d:\Qwt\Qwt-6.2).
+ # This is the directory where qwt.prf and qwt*.pri files reside.
+ # Windows: file qwt.dll must exist in $${DESTDIR}\release and qwtd.dll in $${DESTDIR}\debug
+ # to be able to run the application.
+ CONFIG *= USE_CHART QWT
+}
+
+# add defines if have the following values in CONFIG
+NO_K_LIB: DEFINES *= NO_K_LIB
+NO_K_CHART: DEFINES *= NO_K_CHART
+USE_CHART: DEFINES *= USE_CHART
+QWT: DEFINES *= QWT
+
+THREAD_WEAVER {
+ DEFINES += THREAD_WEAVER
+ win32 {
+ # ThreadWeaver shall be built beforehand - use ThreadWeaver.pro file (edit it if necessary)
+
+ # change the variable if ThreadWeaver headers are located in another directory
+ THREAD_WEAVER_PATH = ../../kf5/threadweaver
+
+ THREAD_WEAVER_HEADER_PATH = $${THREAD_WEAVER_PATH}/src/
+
+ INCLUDEPATH += $$THREAD_WEAVER_HEADER_PATH ThreadWeaver
+ CONFIG(debug, debug|release) {
+ win32-msvc:LIBS += $${DESTDIR}/threadweaverd.lib
+ }
+ else {
+ win32-msvc:LIBS += $${DESTDIR}/threadweaver.lib
+ }
+ }
+ unix {
+ QT += ThreadWeaver
+ }
+}
+
+SOURCES += \
+ analyze/accumulatedtracedata.cpp \
+ analyze/gui/aboutdata.cpp \
+ analyze/gui/aboutdialog.cpp \
+ analyze/gui/gui.cpp \
+ analyze/gui/callercalleemodel.cpp \
+ analyze/gui/costdelegate.cpp \
+ analyze/gui/flamegraph.cpp \
+ analyze/gui/mainwindow.cpp \
+ analyze/gui/objecttreemodel.cpp \
+ analyze/gui/objecttreeproxy.cpp \
+ analyze/gui/parser.cpp \
+ analyze/gui/stacksmodel.cpp \
+ analyze/gui/topproxy.cpp \
+ analyze/gui/treemodel.cpp \
+ analyze/gui/treeproxy.cpp \
+ analyze/gui/util.cpp
+
+HEADERS += \
+ analyze/accumulatedtracedata.h \
+ analyze/gui/aboutdata.h \
+ analyze/gui/aboutdialog.h \
+ analyze/gui/callercalleemodel.h \
+ analyze/gui/costdelegate.h \
+ analyze/gui/flamegraph.h \
+ analyze/gui/gui_config.h \
+ analyze/gui/locationdata.h \
+ analyze/gui/mainwindow.h \
+ analyze/gui/objecttreemodel.h \
+ analyze/gui/objecttreeproxy.h \
+ analyze/gui/parser.h \
+ analyze/gui/stacksmodel.h \
+ analyze/gui/summarydata.h \
+ analyze/gui/topproxy.h \
+ analyze/gui/treemodel.h \
+ analyze/gui/treeproxy.h \
+ analyze/gui/util.h \
+ util/config.h
+
+QWT_CHART {
+ SOURCES += \
+ analyze/gui/charthelpwindow.cpp \
+ analyze/gui/chartmodel2qwtseriesdata.cpp \
+ analyze/gui/chartwidgetqwtplot.cpp \
+ analyze/gui/contextmenuqwt.cpp \
+ analyze/gui/histogramwidgetqwtplot.cpp
+
+ HEADERS += \
+ analyze/gui/charthelpwindow.h \
+ analyze/gui/chartmodel2qwtseriesdata.h \
+ analyze/gui/chartwidgetqwtplot.h \
+ analyze/gui/contextmenuqwt.h \
+ analyze/gui/histogramwidgetqwtplot.h
+}
+
+USE_CHART {
+ SOURCES += \
+ analyze/gui/chartmodel.cpp \
+ analyze/gui/chartproxy.cpp \
+ analyze/gui/chartwidget.cpp \
+ analyze/gui/histogrammodel.cpp \
+ analyze/gui/histogramwidget.cpp
+
+ HEADERS += \
+ analyze/gui/chartmodel.h \
+ analyze/gui/chartproxy.h \
+ analyze/gui/chartwidget.h \
+ analyze/gui/histogrammodel.h \
+ analyze/gui/histogramwidget.h
+}
+
+!NO_K_LIB {
+ QT += KCoreAddons # for KFormat
+ QT += KI18n
+
+ QT += KIOCore
+ QT += KIOFileWidgets
+
+ QT += KConfigWidgets
+ QT += KIOWidgets
+
+ QT += KItemModels
+
+ QT *= ThreadWeaver
+ DEFINES *= THREAD_WEAVER
+}
+
+!NO_K_CHART {
+ QT += KChart
+
+ FORMS += \
+ analyze/gui/mainwindow.ui
+}
+
+NO_K_LIB {
+ HEADERS += \
+ analyze/gui/noklib.h
+
+ FORMS += \
+ analyze/gui/mainwindow_noklib.ui \
+
+ RESOURCES += \
+ analyze/gui/gui.qrc
+}
+
+FORMS += \
+ analyze/gui/aboutdialog.ui
+
+# copy extra files (Win32, release)
+win32 {
+ CONFIG(release, debug|release) {
+ # copy Qt dlls
+ EXTRA_BINFILES += \
+ $$(QTDIR)/bin/Qt5Core.dll \
+ $$(QTDIR)/bin/Qt5Gui.dll \
+ $$(QTDIR)/bin/Qt5OpenGL.dll \
+ $$(QTDIR)/bin/Qt5Svg.dll \
+ $$(QTDIR)/bin/Qt5Widgets.dll
+ QWT_CHART {
+ # ... and qwt.dll
+ EXTRA_BINFILES += $$(QWT_ROOT)/lib/qwt.dll
+ }
+ EXTRA_BINFILES_WIN = $${EXTRA_BINFILES}
+ EXTRA_BINFILES_WIN ~= s,/,\\,g
+ DESTDIR_WIN = $${DESTDIR}
+ DESTDIR_WIN ~= s,/,\\,g
+ for (FILE, EXTRA_BINFILES_WIN) {
+ QMAKE_POST_LINK += $$quote(cmd /c copy /y $${FILE} $${DESTDIR_WIN}$$escape_expand(\n\t))
+ }
+ QT_PLUGIN_DIR_WIN = $$(QTDIR)/plugins
+ QT_PLUGIN_DIR_WIN ~= s,/,\\,g
+ # copy imageformats\qjpeg.dll
+ DEST_PLUGIN_DIR = $${DESTDIR_WIN}\\imageformats
+ QMAKE_POST_LINK += $$quote(cmd /c if not exist $${DEST_PLUGIN_DIR} mkdir $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ QMAKE_POST_LINK += $$quote(cmd /c copy /y $${QT_PLUGIN_DIR_WIN}\\imageformats\\qjpeg.dll $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ # copy platforms\qwindows.dll
+ DEST_PLUGIN_DIR = $${DESTDIR_WIN}\\platforms
+ QMAKE_POST_LINK += $$quote(cmd /c if not exist $${DEST_PLUGIN_DIR} mkdir $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ QMAKE_POST_LINK += $$quote(cmd /c copy /y $${QT_PLUGIN_DIR_WIN}\\platforms\\qwindows.dll $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ # copy styles\qwindowsvistastyle.dll
+ DEST_PLUGIN_DIR = $${DESTDIR_WIN}\\styles
+ QMAKE_POST_LINK += $$quote(cmd /c if not exist $${DEST_PLUGIN_DIR} mkdir $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ QMAKE_POST_LINK += $$quote(cmd /c copy /y $${QT_PLUGIN_DIR_WIN}\\styles\\qwindowsvistastyle.dll $${DEST_PLUGIN_DIR}$$escape_expand(\n\t))
+ }
+}
diff --git a/src/heaptrack_print.pro b/src/heaptrack_print.pro
new file mode 100644
index 0000000..b4ead91
--- /dev/null
+++ b/src/heaptrack_print.pro
@@ -0,0 +1,16 @@
+TEMPLATE = app
+CONFIG += console c++11
+CONFIG -= app_bundle
+CONFIG -= qt
+
+#INCLUDEPATH += $$PWD/src
+
+LIBS += -lboost_program_options -lboost_iostreams -lpthread
+
+SOURCES += \
+ analyze/print/heaptrack_print.cpp \
+ analyze/accumulatedtracedata.cpp
+
+HEADERS += \
+ analyze/accumulatedtracedata.h
+ util/config.h
diff --git a/src/track/heaptrack.sh.cmake b/src/track/heaptrack.sh.cmake
index 4cca4c0..f9f8d97 100755
--- a/src/track/heaptrack.sh.cmake
+++ b/src/track/heaptrack.sh.cmake
@@ -89,7 +89,7 @@ while true; do
break
;;
"-v" | "--version")
- echo "heaptrack @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@"
+ echo "heaptrack @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@-@HEAPTRACK_VERSION_SUFFIX@"
exit 0
;;
*)
diff --git a/src/util/config.h b/src/util/config.h
new file mode 100644
index 0000000..ea6c3fa
--- /dev/null
+++ b/src/util/config.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef HEAPTRACK_CONFIG_H
+#define HEAPTRACK_CONFIG_H
+
+#define HEAPTRACK_VERSION_STRING "1.1.0-0.1"
+#define HEAPTRACK_VERSION_MAJOR 1
+#define HEAPTRACK_VERSION_MINOR 1
+#define HEAPTRACK_VERSION_PATCH 0
+#define HEAPTRACK_VERSION ((HEAPTRACK_VERSION_MAJOR<<16)|(HEAPTRACK_VERSION_MINOR<<8)|(HEAPTRACK_VERSION_PATCH))
+
+#define HEAPTRACK_FILE_FORMAT_VERSION 2
+
+#define HEAPTRACK_DEBUG_BUILD 1
+
+// cfree() does not exist in glibc 2.26+.
+// See: https://bugs.kde.org/show_bug.cgi?id=383889
+#define HAVE_CFREE 0
+
+#endif // HEAPTRACK_CONFIG_H
diff --git a/src/util/config.h.cmake b/src/util/config.h.cmake
index 861c1f7..d86aa35 100644
--- a/src/util/config.h.cmake
+++ b/src/util/config.h.cmake
@@ -19,7 +19,7 @@
#ifndef HEAPTRACK_CONFIG_H
#define HEAPTRACK_CONFIG_H
-#define HEAPTRACK_VERSION_STRING "@HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@"
+#define HEAPTRACK_VERSION_STRING "@HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@-@HEAPTRACK_VERSION_SUFFIX@"
#define HEAPTRACK_VERSION_MAJOR @HEAPTRACK_VERSION_MAJOR@
#define HEAPTRACK_VERSION_MINOR @HEAPTRACK_VERSION_MINOR@
#define HEAPTRACK_VERSION_PATCH @HEAPTRACK_VERSION_PATCH@
diff --git a/src/util/indices.h b/src/util/indices.h
index a79a007..9826c5f 100644
--- a/src/util/indices.h
+++ b/src/util/indices.h
@@ -64,7 +64,7 @@ struct Index
};
template <typename Base>
-uint qHash(const Index<Base> index, uint seed = 0) noexcept
+unsigned int qHash(const Index<Base> index, unsigned int seed = 0) noexcept
{
return qHash(index.index, seed);
}
diff --git a/src/util/libunwind_config.h b/src/util/libunwind_config.h
new file mode 100644
index 0000000..bcc01c1
--- /dev/null
+++ b/src/util/libunwind_config.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef LIBUNWIND_CONFIG_H
+#define LIBUNWIND_CONFIG_H
+
+#define LIBUNWIND_HAS_UNW_BACKTRACE 1
+
+#define LIBUNWIND_HAS_UNW_BACKTRACE_SKIP 0
+
+#endif // LIBUNWIND_CONFIG_H
+