ee175659b5aa62f00aa2faf1b2004f6c9b8497a2
[sdk/tools/heaptrack.git] / src / analyze / gui / callercalleemodel.cpp
1 /*
2  * Copyright 2016-2017 Milian Wolff <mail@milianw.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "callercalleemodel.h"
20
21 #ifdef NO_K_LIB
22 #include "noklib.h"
23 #else
24 #include <KLocalizedString>
25 #endif
26
27 #include "util.h"
28
29 #include <QTextStream>
30
31 #include <cmath>
32
33 namespace {
34 /// TODO: share code
35 QString basename(const QString& path)
36 {
37     int idx = path.lastIndexOf(QLatin1Char('/'));
38     return path.mid(idx + 1);
39 }
40 }
41
42 CallerCalleeModel::CallerCalleeModel(QObject* parent)
43     : QAbstractTableModel(parent)
44 {
45     qRegisterMetaType<CallerCalleeRows>();
46 }
47
48 CallerCalleeModel::~CallerCalleeModel() = default;
49
50 QVariant CallerCalleeModel::headerData(int section, Qt::Orientation orientation, int role) const
51 {
52     if (orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) {
53         return {};
54     }
55     if (role == Qt::InitialSortOrderRole) {
56         if (section == SelfAllocatedColumn || section == SelfAllocationsColumn || section == SelfPeakColumn
57             || section == SelfPeakInstancesColumn || section == SelfLeakedColumn || section == SelfTemporaryColumn || section == InclusiveAllocatedColumn
58             || section == InclusivePeakInstancesColumn || section == InclusiveAllocationsColumn || section == InclusivePeakColumn
59             || section == InclusiveLeakedColumn || section == InclusiveTemporaryColumn) {
60             return Qt::DescendingOrder;
61         }
62     }
63     if (role == Qt::DisplayRole) {
64         switch (static_cast<Columns>(section)) {
65         case FileColumn:
66             return i18n("File");
67         case LineColumn:
68             return i18n("Line");
69         case FunctionColumn:
70             return i18n("Function");
71         case ModuleColumn:
72             return i18n("Module");
73         case SelfAllocationsColumn:
74             return i18n("Allocations (Self)");
75         case SelfTemporaryColumn:
76             return i18n("Temporary (Self)");
77         case SelfPeakColumn:
78             return i18n("Peak (Self)");
79         case SelfPeakInstancesColumn:
80             return i18n("Peak instances (Self.)");
81         case SelfLeakedColumn:
82             return i18n("Leaked (Self)");
83         case SelfAllocatedColumn:
84             return i18n("Allocated (Self)");
85         case InclusiveAllocationsColumn:
86             return i18n("Allocations (Incl.)");
87         case InclusiveTemporaryColumn:
88             return i18n("Temporary (Incl.)");
89         case InclusivePeakColumn:
90             return i18n("Peak (Incl.)");
91         case InclusivePeakInstancesColumn:
92             return i18n("Peak instances (Incl.)");
93         case InclusiveLeakedColumn:
94             return i18n("Leaked (Incl.)");
95         case InclusiveAllocatedColumn:
96             return i18n("Allocated (Incl.)");
97         case LocationColumn:
98             return i18n("Location");
99         case NUM_COLUMNS:
100             break;
101         }
102     } else if (role == Qt::ToolTipRole) {
103         switch (static_cast<Columns>(section)) {
104         case FileColumn:
105             return i18n("<qt>The file where the allocation function was called from. "
106                         "May be empty when debug information is missing.</qt>");
107         case LineColumn:
108             return i18n("<qt>The line number where the allocation function was called from. "
109                         "May be empty when debug information is missing.</qt>");
110         case FunctionColumn:
111             return i18n("<qt>The parent function that called an allocation function. "
112                         "May be unknown when debug information is missing.</qt>");
113         case ModuleColumn:
114             return i18n("<qt>The module, i.e. executable or shared library, from "
115                         "which an allocation function was "
116                         "called.</qt>");
117         case SelfAllocationsColumn:
118             return i18n("<qt>The number of times an allocation function was directly "
119                         "called from this location.</qt>");
120         case SelfTemporaryColumn:
121             return i18n("<qt>The number of direct temporary allocations. These "
122                         "allocations are directly followed by a "
123                         "free without any other allocations in-between.</qt>");
124         case SelfPeakColumn:
125             return i18n("<qt>The maximum heap memory in bytes consumed from "
126                         "allocations originating directly at "
127                         "this location. "
128                         "This takes deallocations into account.</qt>");
129         case SelfPeakInstancesColumn:
130             return i18n("<qt>The maximum number of instances in created"
131                         "from allocations originating at this "
132                         "location or from functions called from here. "
133                         "This takes deallocations into account.</qt>");
134         case SelfLeakedColumn:
135             return i18n("<qt>The bytes allocated directly at this location that have "
136                         "not been deallocated.</qt>");
137         case SelfAllocatedColumn:
138             return i18n("<qt>The sum of all bytes directly allocated from this "
139                         "location, ignoring deallocations.</qt>");
140         case InclusiveAllocationsColumn:
141             return i18n("<qt>The inclusive number of times an allocation function "
142                         "was called from this location or any "
143                         "functions called from here.</qt>");
144         case InclusiveTemporaryColumn:
145             return i18n("<qt>The number of inclusive temporary allocations. These "
146                         "allocations are directly followed by "
147                         "a free without any other allocations in-between.</qt>");
148         case InclusivePeakColumn:
149             return i18n("<qt>The inclusive maximum heap memory in bytes consumed "
150                         "from allocations originating at this "
151                         "location or from functions called from here. "
152                         "This takes deallocations into account.</qt>");
153         case InclusivePeakInstancesColumn:
154             return i18n("<qt>The inclusive maximum number of instances in created"
155                         "from allocations originating at this "
156                         "location or from functions called from here. "
157                         "This takes deallocations into account.</qt>");
158         case InclusiveLeakedColumn:
159             return i18n("<qt>The bytes allocated at this location that have not been "
160                         "deallocated.</qt>");
161         case InclusiveAllocatedColumn:
162             return i18n("<qt>The inclusive sum of all bytes allocated from this "
163                         "location or functions called from "
164                         "here, ignoring deallocations.</qt>");
165         case LocationColumn:
166             return i18n("<qt>The location from which an allocation function was "
167                         "called. Function symbol and file "
168                         "information "
169                         "may be unknown when debug information was missing when "
170                         "heaptrack was run.</qt>");
171         case NUM_COLUMNS:
172             break;
173         }
174     }
175     return {};
176 }
177
178 QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
179 {
180     if (!hasIndex(index.row(), index.column(), index.parent())) {
181         return {};
182     }
183
184     const auto& row = (role == MaxCostRole) ? m_maxCost : m_rows.at(index.row());
185
186     if (role == Qt::DisplayRole || role == SortRole || role == MaxCostRole) {
187         switch (static_cast<Columns>(index.column())) {
188         case SelfAllocatedColumn:
189             if (role == SortRole || role == MaxCostRole) {
190                 return static_cast<qint64>(row.selfCost.allocated);
191             } else {
192                 return Util::formatByteSize(row.selfCost.allocated, 1);
193             }
194         case SelfAllocationsColumn:
195             return static_cast<qint64>(row.selfCost.allocations);
196         case SelfTemporaryColumn:
197             return static_cast<qint64>(row.selfCost.temporary);
198         case SelfPeakColumn:
199             if (role == SortRole || role == MaxCostRole) {
200                 return static_cast<qint64>(row.selfCost.peak);
201             } else {
202                 return Util::formatByteSize(row.selfCost.peak, 1);
203             }
204         case SelfPeakInstancesColumn:
205             return static_cast<qint64>(row.selfCost.peak_instances);
206         case SelfLeakedColumn:
207             if (role == SortRole || role == MaxCostRole) {
208                 return static_cast<qint64>(row.selfCost.leaked);
209             } else {
210                 return Util::formatByteSize(row.selfCost.leaked, 1);
211             }
212         case InclusiveAllocatedColumn:
213             if (role == SortRole || role == MaxCostRole) {
214                 return static_cast<qint64>(row.inclusiveCost.allocated);
215             } else {
216                 return Util::formatByteSize(row.inclusiveCost.allocated, 1);
217             }
218         case InclusiveAllocationsColumn:
219             return static_cast<qint64>(row.inclusiveCost.allocations);
220         case InclusiveTemporaryColumn:
221             return static_cast<qint64>(row.inclusiveCost.temporary);
222         case InclusivePeakColumn:
223             if (role == SortRole || role == MaxCostRole) {
224                 return static_cast<qint64>(row.inclusiveCost.peak);
225             } else {
226                 return Util::formatByteSize(row.inclusiveCost.peak, 1);
227             }
228         case InclusivePeakInstancesColumn:
229             return static_cast<qint64>(row.inclusiveCost.peak_instances);
230         case InclusiveLeakedColumn:
231             if (role == SortRole || role == MaxCostRole) {
232                 return static_cast<qint64>(row.inclusiveCost.leaked);
233             } else {
234                 return Util::formatByteSize(row.inclusiveCost.leaked, 1);
235             }
236         case FunctionColumn:
237             return row.location->function;
238         case ModuleColumn:
239             return row.location->module;
240         case FileColumn:
241             return row.location->file;
242         case LineColumn:
243             return row.location->line;
244         case LocationColumn:
245             if (row.location->file.isEmpty()) {
246                 return i18n("%1 in ?? (%2)", basename(row.location->function), basename(row.location->module));
247             } else {
248                 return i18n("%1 in %2:%3 (%4)", row.location->function, basename(row.location->file),
249                             row.location->line, basename(row.location->module));
250             }
251         case NUM_COLUMNS:
252             break;
253         }
254     } else if (role == Qt::ToolTipRole) {
255         QString tooltip;
256         QTextStream stream(&tooltip);
257         stream << "<qt><pre style='font-family:monospace;'>";
258         if (row.location->line > 0) {
259             stream << i18nc("1: function, 2: file, 3: line, 4: module", "%1\n  at %2:%3\n  in %4",
260                             row.location->function.toHtmlEscaped(), row.location->file.toHtmlEscaped(),
261                             row.location->line, row.location->module.toHtmlEscaped());
262         } else {
263             stream << i18nc("1: function, 2: module", "%1\n  in %2", row.location->function.toHtmlEscaped(),
264                             row.location->module.toHtmlEscaped());
265         }
266         stream << '\n';
267         stream << i18n("inclusive: allocated %1 over %2 calls (%3 temporary, i.e. "
268                        "%4%), peak at %5, leaked %6",
269                        Util::formatByteSize(row.inclusiveCost.allocated, 1),
270                        row.inclusiveCost.allocations,
271                        row.inclusiveCost.temporary, round(float(row.inclusiveCost.temporary) * 100.f * 100.f
272                                                           / std::max(int64_t(1), row.inclusiveCost.allocations))
273                            / 100.f,
274                        Util::formatByteSize(row.inclusiveCost.peak, 1),
275                        Util::formatByteSize(row.inclusiveCost.leaked, 1));
276         stream << '\n';
277         stream << i18n(
278             "self: allocated %1 over %2 calls (%3 temporary, i.e. %4%), "
279             "peak at %5, leaked %6",
280             Util::formatByteSize(row.selfCost.allocated, 1),
281             row.selfCost.allocations, row.selfCost.temporary,
282             round(float(row.selfCost.temporary) * 100.f * 100.f / std::max(int64_t(1), row.selfCost.allocations))
283                 / 100.f,
284             Util::formatByteSize(row.selfCost.peak, 1),
285             Util::formatByteSize(row.selfCost.leaked, 1));
286         stream << '\n';
287         stream << "</pre></qt>";
288         return tooltip;
289     } else if (role == LocationRole) {
290         return QVariant::fromValue(row.location);
291     }
292     return {};
293 }
294
295 int CallerCalleeModel::columnCount(const QModelIndex& parent) const
296 {
297     return parent.isValid() ? 0 : NUM_COLUMNS;
298 }
299
300 int CallerCalleeModel::rowCount(const QModelIndex& parent) const
301 {
302     return parent.isValid() ? 0 : m_rows.size();
303 }
304
305 void CallerCalleeModel::resetData(const QVector<CallerCalleeData>& rows)
306 {
307     beginResetModel();
308     m_rows = rows;
309     endResetModel();
310 }
311
312 void CallerCalleeModel::setSummary(const SummaryData& data)
313 {
314     beginResetModel();
315     m_maxCost.inclusiveCost = data.cost;
316     m_maxCost.selfCost = data.cost;
317     endResetModel();
318 }
319
320 void CallerCalleeModel::clearData()
321 {
322     beginResetModel();
323     m_rows = {};
324     m_maxCost = {};
325     endResetModel();
326 }