File indexing completed on 2024-05-19 05:44:22
0001 /* 0002 SPDX-FileCopyrightText: 2015-2017 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "chartmodel.h" 0008 0009 #include <KChartGlobal> 0010 #include <KChartLineAttributes> 0011 #include <KLocalizedString> 0012 0013 #include <QBrush> 0014 #include <QDebug> 0015 #include <QPen> 0016 0017 #include "util.h" 0018 0019 #include <algorithm> 0020 0021 namespace { 0022 QColor colorForColumn(int column, int columnCount) 0023 { 0024 // The total cost graph (column 0) is always red. 0025 if (column == 0) { 0026 return Qt::red; 0027 } else { 0028 return QColor::fromHsv((double(column + 1) / columnCount) * 255, 255, 255); 0029 } 0030 } 0031 } 0032 0033 ChartModel::ChartModel(Type type, QObject* parent) 0034 : QAbstractTableModel(parent) 0035 , m_type(type) 0036 , m_maxDatasetCount(11) 0037 { 0038 qRegisterMetaType<ChartData>(); 0039 } 0040 0041 ChartModel::~ChartModel() = default; 0042 0043 ChartModel::Type ChartModel::type() const 0044 { 0045 return m_type; 0046 } 0047 0048 QString ChartModel::typeString() const 0049 { 0050 switch (m_type) { 0051 case Allocations: 0052 return i18n("Memory Allocations"); 0053 case Consumed: 0054 return i18n("Memory Consumed"); 0055 case Temporary: 0056 return i18n("Temporary Allocations"); 0057 default: 0058 return QString(); 0059 } 0060 } 0061 0062 QVariant ChartModel::headerData(int section, Qt::Orientation orientation, int role) const 0063 { 0064 if (section < 0 || section >= columnCount() || orientation != Qt::Horizontal) { 0065 return {}; 0066 } 0067 0068 if (role == KChart::DatasetPenRole) { 0069 return QVariant::fromValue(m_columnDataSetPens.at(section)); 0070 } else if (role == KChart::DatasetBrushRole) { 0071 return QVariant::fromValue(m_columnDataSetBrushes.at(section)); 0072 } 0073 0074 if (role == Qt::ToolTipRole) { 0075 if (section == 0) { 0076 return i18n("Elapsed Time"); 0077 } 0078 return typeString(); 0079 } else if (role == Qt::DisplayRole) { 0080 if (section == 0) { 0081 switch (m_type) { 0082 case Allocations: 0083 return i18n("Total Memory Allocations"); 0084 case Consumed: 0085 return i18n("Total Memory Consumption"); 0086 case Temporary: 0087 return i18n("Total Temporary Allocations"); 0088 } 0089 } else { 0090 auto id = m_data.labels.value(section / 2).functionId; 0091 QString label = m_data.resultData->string(id); 0092 0093 // Experimental symbol name shortening, currently only truncating 0094 // and left-justified labels. The length is also fixed and should 0095 // be adjusted later on. 0096 // 0097 // The justified text is also a workaround to setTextAlignment as 0098 // this does not seem to work on KChartLegend objects. This might 0099 // be because it is not supported for these instances, as the re- 0100 // ference suggests: https://doc.qt.io/qt-6/stylesheet-reference.html 0101 // (see "text-align" entry). 0102 int symbolNameLength = 60; 0103 0104 if (label.size() < symbolNameLength) { 0105 label = label.leftJustified(symbolNameLength); 0106 } else if (label.size() > symbolNameLength) { 0107 label.truncate(symbolNameLength - 3); 0108 label = label.leftJustified(symbolNameLength, QLatin1Char('.')); 0109 } 0110 0111 label = label.rightJustified(symbolNameLength + 1); 0112 0113 return i18n("%1", label); 0114 } 0115 } 0116 0117 return {}; 0118 } 0119 0120 QVariant ChartModel::data(const QModelIndex& index, int role) const 0121 { 0122 if (!index.isValid()) { 0123 return {}; 0124 } 0125 Q_ASSERT(index.row() >= 0 && index.row() < rowCount(index.parent())); 0126 Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent())); 0127 Q_ASSERT(!index.parent().isValid()); 0128 0129 if (role == KChart::LineAttributesRole) { 0130 KChart::LineAttributes attributes; 0131 attributes.setDisplayArea(true); 0132 if (index.column() > 1) { 0133 attributes.setTransparency(127); 0134 } else { 0135 attributes.setTransparency(50); 0136 } 0137 return QVariant::fromValue(attributes); 0138 } 0139 0140 if (role == KChart::DatasetPenRole) { 0141 return QVariant::fromValue(m_columnDataSetPens.at(index.column())); 0142 } else if (role == KChart::DatasetBrushRole) { 0143 return QVariant::fromValue(m_columnDataSetBrushes.at(index.column())); 0144 } 0145 0146 if (role != Qt::DisplayRole && role != Qt::ToolTipRole) { 0147 return {}; 0148 } 0149 0150 const auto& data = m_data.rows.at(index.row()); 0151 0152 int column = index.column(); 0153 if (role != Qt::ToolTipRole && column % 2 == 0) { 0154 return data.timeStamp; 0155 } 0156 column = column / 2; 0157 Q_ASSERT(column < ChartRows::MAX_NUM_COST); 0158 0159 const auto cost = data.cost[column]; 0160 if (role == Qt::ToolTipRole) { 0161 const QString time = Util::formatTime(data.timeStamp); 0162 auto byteCost = [cost]() -> QString { 0163 const auto formatted = Util::formatBytes(cost); 0164 if (cost > 1024) { 0165 return i18nc("%1: the formatted byte size, e.g. \"1.2KB\", %2: the raw byte size, e.g. \"1300\"", 0166 "%1 (%2 bytes)", formatted, cost); 0167 } else { 0168 return formatted; 0169 } 0170 }; 0171 if (column == 0) { 0172 switch (m_type) { 0173 case Allocations: 0174 return i18n("<qt>%1 allocations in total after %2</qt>", cost, time); 0175 case Temporary: 0176 return i18n("<qt>%1 temporary allocations in total after %2</qt>", cost, time); 0177 case Consumed: 0178 return i18n("<qt>%1 consumed in total after %2</qt>", byteCost(), time); 0179 } 0180 } else { 0181 auto label = Util::toString(m_data.labels.value(column), *m_data.resultData, Util::Long); 0182 switch (m_type) { 0183 case Allocations: 0184 return i18n("<qt>%2 allocations after %3 from:<p " 0185 "style='margin-left:10px;'>%1</p></qt>", 0186 label, cost, time); 0187 case Temporary: 0188 return i18n("<qt>%2 temporary allocations after %3 from:<p " 0189 "style='margin-left:10px'>%1</p></qt>", 0190 label, cost, time); 0191 case Consumed: 0192 return i18n("<qt>%2 consumed after %3 from:<p " 0193 "style='margin-left:10px'>%1</p></qt>", 0194 label, byteCost(), time); 0195 } 0196 } 0197 return {}; 0198 } 0199 0200 return cost; 0201 } 0202 0203 int ChartModel::columnCount(const QModelIndex& /*parent*/) const 0204 { 0205 return qMin(m_maxDatasetCount, m_data.labels.size()) * 2; 0206 } 0207 0208 int ChartModel::rowCount(const QModelIndex& parent) const 0209 { 0210 if (parent.isValid()) { 0211 return 0; 0212 } else { 0213 return m_data.rows.size(); 0214 } 0215 } 0216 0217 void ChartModel::setMaximumDatasetCount(int count) 0218 { 0219 Q_ASSERT(count >= 0); 0220 0221 int currentColumns = qMin(m_data.labels.size(), m_maxDatasetCount); 0222 int newColumnCount = qMin(m_data.labels.size(), count); 0223 0224 if (newColumnCount == currentColumns) { 0225 return; 0226 } else if (newColumnCount < currentColumns) { 0227 beginRemoveColumns(QModelIndex(), newColumnCount * 2, currentColumns * 2 - 1); 0228 } else { 0229 beginInsertColumns(QModelIndex(), currentColumns * 2, newColumnCount * 2 - 1); 0230 } 0231 0232 m_maxDatasetCount = count; 0233 resetColors(); 0234 0235 if (newColumnCount < currentColumns) { 0236 endRemoveColumns(); 0237 } else { 0238 endInsertColumns(); 0239 } 0240 Q_ASSERT(columnCount() == newColumnCount * 2); 0241 } 0242 0243 void ChartModel::resetColors() 0244 { 0245 m_columnDataSetBrushes.clear(); 0246 m_columnDataSetPens.clear(); 0247 const auto columns = columnCount(); 0248 for (int i = 0; i < columns; ++i) { 0249 auto color = colorForColumn(i, columns); 0250 m_columnDataSetBrushes << QBrush(color); 0251 m_columnDataSetPens << QPen(color); 0252 } 0253 } 0254 0255 void ChartModel::resetData(const ChartData& data) 0256 { 0257 Q_ASSERT(data.resultData); 0258 Q_ASSERT(data.labels.size() < ChartRows::MAX_NUM_COST); 0259 beginResetModel(); 0260 m_data = data; 0261 resetColors(); 0262 endResetModel(); 0263 } 0264 0265 void ChartModel::clearData() 0266 { 0267 beginResetModel(); 0268 m_data = {}; 0269 m_columnDataSetBrushes = {}; 0270 m_columnDataSetPens = {}; 0271 endResetModel(); 0272 } 0273 0274 struct CompareClosestToTime 0275 { 0276 bool operator()(const qint64& lhs, const ChartRows& rhs) const 0277 { 0278 return lhs > rhs.timeStamp; 0279 } 0280 bool operator()(const ChartRows& lhs, const qint64& rhs) const 0281 { 0282 return lhs.timeStamp > rhs; 0283 } 0284 }; 0285 0286 qint64 ChartModel::totalCostAt(qint64 timeStamp) const 0287 { 0288 auto it = std::lower_bound(m_data.rows.rbegin(), m_data.rows.rend(), timeStamp, CompareClosestToTime()); 0289 if (it == m_data.rows.rend()) 0290 return 0; 0291 return it->cost[0]; 0292 } 0293 0294 #include "moc_chartmodel.cpp"