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"