diff options
Diffstat (limited to 'plugins/qmlprofilerextension/pixmapcachemodel.cpp')
-rw-r--r-- | plugins/qmlprofilerextension/pixmapcachemodel.cpp | 318 |
1 files changed, 262 insertions, 56 deletions
diff --git a/plugins/qmlprofilerextension/pixmapcachemodel.cpp b/plugins/qmlprofilerextension/pixmapcachemodel.cpp index 1ff6e6ab99..f5d08083e8 100644 --- a/plugins/qmlprofilerextension/pixmapcachemodel.cpp +++ b/plugins/qmlprofilerextension/pixmapcachemodel.cpp @@ -23,12 +23,45 @@ #include "qmlprofiler/singlecategorytimelinemodel_p.h" #include <QDebug> +#include <QSize> namespace QmlProfilerExtension { namespace Internal { using namespace QmlProfiler; +enum CacheState { + Uncached, // After loading started (or some other proof of existence) or after uncaching + ToBeCached, // After determining the pixmap is to be cached but before knowing its size + Cached, // After caching a pixmap or determining the size of a ToBeCached pixmap + Uncacheable, // If loading failed without ToBeCached or after a corrupt pixmap has been uncached + Corrupt // If after ToBeCached we learn that loading failed +}; + +enum LoadState { + Initial, + Loading, + Finished, + Error +}; + +struct PixmapState { + PixmapState(int width, int height, CacheState cache = Uncached) : + size(width, height), started(-1), loadState(Initial), cacheState(cache) {} + PixmapState(CacheState cache = Uncached) : started(-1), loadState(Initial), cacheState(cache) {} + QSize size; + int started; + LoadState loadState; + CacheState cacheState; +}; + +struct Pixmap { + Pixmap() {} + Pixmap(const QString &url) : url(url), sizes(1) {} + QString url; + QVector<PixmapState> sizes; +}; + class PixmapCacheModel::PixmapCacheModelPrivate : public SortedTimelineModel<PixmapCacheEvent, SingleCategoryTimelineModel::SingleCategoryTimelineModelPrivate> @@ -38,9 +71,10 @@ public: void resizeUnfinishedLoads(); void flattenLoads(); void computeRowCounts(); + int updateCacheCount(int lastCacheSizeEvent, qint64 startTime, qint64 pixSize, + PixmapCacheEvent &newEvent); - QVector < QString > pixmapUrls; - QVector < QPair<int, int> > pixmapSizes; + QVector<Pixmap> pixmaps; int expandedRowCount; int collapsedRowCount; void addVP(QVariantList &l, QString label, qint64 time) const; @@ -129,11 +163,13 @@ const QVariantList PixmapCacheModel::getLabelsForCategory(int category) const result << element; } - for (int i=0; i < d->pixmapUrls.count(); i++) { + for (int i=0; i < d->pixmaps.count(); i++) { // Loading QVariantMap element; - element.insert(QLatin1String("displayName"), QVariant(getFilenameOnly(d->pixmapUrls[i]))); - element.insert(QLatin1String("description"), QVariant(getFilenameOnly(d->pixmapUrls[i]))); + element.insert(QLatin1String("displayName"), + QVariant(getFilenameOnly(d->pixmaps[i].url))); + element.insert(QLatin1String("description"), + QVariant(getFilenameOnly(d->pixmaps[i].url))); element.insert(QLatin1String("id"), QVariant(i+1)); result << element; @@ -173,20 +209,23 @@ const QVariantList PixmapCacheModel::getEventDetails(int index) const { QVariantMap res; - res.insert(tr("File"), QVariant(getFilenameOnly(d->pixmapUrls[ev->urlIndex]))); + res.insert(tr("File"), QVariant(getFilenameOnly(d->pixmaps[ev->urlIndex].url))); result << res; } { QVariantMap res; - res.insert(tr("Width"), QVariant(QString::fromLatin1("%1 px").arg(d->pixmapSizes[ev->urlIndex].first))); + res.insert(tr("Width"), QVariant(QString::fromLatin1("%1 px") + .arg(d->pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.width()))); result << res; res.clear(); - res.insert(tr("Height"), QVariant(QString::fromLatin1("%1 px").arg(d->pixmapSizes[ev->urlIndex].second))); + res.insert(tr("Height"), QVariant(QString::fromLatin1("%1 px") + .arg(d->pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.height()))); result << res; } - if (ev->pixmapEventType == PixmapLoadingStarted && ev->cacheSize == -1) { + if (ev->pixmapEventType == PixmapLoadingStarted && + d->pixmaps[ev->urlIndex].sizes[ev->sizeIndex].loadState != Finished) { QVariantMap res; res.insert(tr("Result"), QVariant(QLatin1String("Load Error"))); result << res; @@ -195,6 +234,36 @@ const QVariantList PixmapCacheModel::getEventDetails(int index) const return result; } +/* Ultimately there is no way to know which cache entry a given event refers to as long as we only + * receive the pixmap URL from the application. Multiple copies of different sizes may be cached + * for each URL. However, we can apply some heuristics to make the result somewhat plausible by + * using the following assumptions: + * + * - PixmapSizeKnown will happen at most once for every cache entry. + * - PixmapSizeKnown cannot happen for entries with PixmapLoadingError and vice versa. + * - PixmapCacheCountChanged can happen for entries with PixmapLoadingError but doesn't have to. + * - Decreasing PixmapCacheCountChanged events can only happen for entries that have seen an + * increasing PixmapCacheCountChanged (but that may have happened before the trace). + * - PixmapCacheCountChanged can happen before or after PixmapSizeKnown. + * - For every PixmapLoadingFinished or PixmapLoadingError there is exactly one + * PixmapLoadingStarted event, but it may be before the trace. + * - For every PixmapLoadingStarted there is exactly one PixmapLoadingFinished or + * PixmapLoadingError, but it may be after the trace. + * - Decreasing PixmapCacheCountChanged events in the presence of corrupt cache entries are more + * likely to clear those entries than other, correctly loaded ones. + * - Increasing PixmapCacheCountChanged events are more likely to refer to correctly loaded entries + * than to ones with PixmapLoadingError. + * - PixmapLoadingFinished and PixmapLoadingError are more likely to refer to cache entries that + * have seen a PixmapLoadingStarted than to ones that haven't. + * + * For each URL we keep an ordered list of pixmaps possibly being loaded and assign new events to + * the first entry that "fits". If multiple sizes of the same pixmap are being loaded concurrently + * we generally assume that the PixmapLoadingFinished and PixmapLoadingError events occur in the + * order we learn about the existence of these sizes, subject to the above constraints. This is not + * necessarily the order the pixmaps are really loaded but it's the best we can do with the given + * information. If they're loaded sequentially the representation is correct. + */ + void PixmapCacheModel::loadData() { Q_D(PixmapCacheModel); @@ -205,8 +274,6 @@ void PixmapCacheModel::loadData() int lastCacheSizeEvent = -1; int cumulatedCount = 0; - QVector < int > pixmapStartPoints; - QVector < int > pixmapCachePoints; foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) { if (!eventAccepted(event)) @@ -216,69 +283,192 @@ void PixmapCacheModel::loadData() newEvent.pixmapEventType = event.bindingType; qint64 startTime = event.startTime; - bool isNewEntry = false; - newEvent.urlIndex = d->pixmapUrls.indexOf(event.location.filename); + newEvent.urlIndex = -1; + for (QVector<Pixmap>::const_iterator it(d->pixmaps.cend()); it != d->pixmaps.cbegin();) { + if ((--it)->url == event.location.filename) { + newEvent.urlIndex = it - d->pixmaps.cbegin(); + break; + } + } + + newEvent.sizeIndex = -1; if (newEvent.urlIndex == -1) { - isNewEntry = true; - newEvent.urlIndex = d->pixmapUrls.count(); - d->pixmapUrls << event.location.filename; - d->pixmapSizes << QPair<int, int>(0,0); // default value - pixmapStartPoints << -1; // dummy value to be filled by load event - pixmapCachePoints << -1; // dummy value to be filled by cache event + newEvent.urlIndex = d->pixmaps.count(); + d->pixmaps << Pixmap(event.location.filename); } newEvent.eventId = newEvent.urlIndex + 1; newEvent.rowNumberExpanded = newEvent.urlIndex + 2; + Pixmap &pixmap = d->pixmaps[newEvent.urlIndex]; switch (newEvent.pixmapEventType) { - case PixmapSizeKnown: // pixmap size - d->pixmapSizes[newEvent.urlIndex] = QPair<int,int>((int)event.numericData1, (int)event.numericData2); - if (pixmapCachePoints[newEvent.urlIndex] == -1) + case PixmapSizeKnown: {// pixmap size + // Look for pixmaps for which we don't know the size, yet and which have actually been + // loaded. + for (QVector<PixmapState>::iterator i(pixmap.sizes.begin()); + i != pixmap.sizes.end(); ++i) { + if (i->size.isValid() || i->cacheState == Uncacheable || i->cacheState == Corrupt) + continue; + + // We can't have cached it before we knew the size + Q_ASSERT(i->cacheState != Cached); + + i->size.setWidth(event.numericData1); + i->size.setHeight(event.numericData2); + newEvent.sizeIndex = i - pixmap.sizes.begin(); break; - // else fall through and update cache size - newEvent.pixmapEventType = PixmapCacheCountChanged; + } + + if (newEvent.sizeIndex == -1) { + newEvent.sizeIndex = pixmap.sizes.length(); + pixmap.sizes << PixmapState(event.numericData1, event.numericData2); + } + + PixmapState &state = pixmap.sizes[newEvent.sizeIndex]; + if (state.cacheState == ToBeCached) { + lastCacheSizeEvent = d->updateCacheCount(lastCacheSizeEvent, startTime, + state.size.width() * state.size.height(), newEvent); + state.cacheState = Cached; + } + break; + } case PixmapCacheCountChanged: {// Cache Size Changed Event startTime = event.startTime + 1; // delay 1 ns for proper sorting - newEvent.eventId = 0; - newEvent.rowNumberExpanded = 1; - newEvent.rowNumberCollapsed = 1; - - qint64 pixSize = d->pixmapSizes[newEvent.urlIndex].first * d->pixmapSizes[newEvent.urlIndex].second; - qint64 prevSize = 0; - if (lastCacheSizeEvent != -1) { - prevSize = d->range(lastCacheSizeEvent).cacheSize; - if (pixmapCachePoints[newEvent.urlIndex] == -1) { - // else it's a synthesized update and doesn't have a valid cache count - if (event.numericData3 < cumulatedCount) - pixSize = -pixSize; - cumulatedCount = event.numericData3; + + bool uncache = cumulatedCount > event.numericData3; + cumulatedCount = event.numericData3; + qint64 pixSize = 0; + + // First try to find a preferred pixmap, which either is Corrupt and will be uncached + // or is uncached and will be cached. + for (QVector<PixmapState>::iterator i(pixmap.sizes.begin()); + i != pixmap.sizes.end(); ++i) { + if (uncache && i->cacheState == Corrupt) { + newEvent.sizeIndex = i - pixmap.sizes.begin(); + i->cacheState = Uncacheable; + break; + } else if (!uncache && i->cacheState == Uncached) { + newEvent.sizeIndex = i - pixmap.sizes.begin(); + if (i->size.isValid()) { + pixSize = i->size.width() * i->size.height(); + i->cacheState = Cached; + } else { + i->cacheState = ToBeCached; + } + break; + } + } + + // If none found, check for cached or ToBeCached pixmaps that shall be uncached or + // Error pixmaps that become corrupt cache entries. We also accept Initial to be + // uncached as we may have missed the matching PixmapCacheCountChanged that cached it. + if (newEvent.sizeIndex == -1) { + for (QVector<PixmapState>::iterator i(pixmap.sizes.begin()); + i != pixmap.sizes.end(); ++i) { + if (uncache && (i->cacheState == Cached || i->cacheState == ToBeCached || + i->cacheState == Uncached)) { + newEvent.sizeIndex = i - pixmap.sizes.begin(); + if (i->size.isValid()) + pixSize = -i->size.width() * i->size.height(); + i->cacheState = Uncached; + break; + } else if (!uncache && i->cacheState == Uncacheable) { + newEvent.sizeIndex = i - pixmap.sizes.begin(); + i->cacheState = Corrupt; + break; + } } - d->insertEnd(lastCacheSizeEvent, startTime - d->range(lastCacheSizeEvent).start); } - newEvent.cacheSize = prevSize + pixSize; - lastCacheSizeEvent = d->insertStart(startTime, newEvent); - pixmapCachePoints[newEvent.urlIndex] = lastCacheSizeEvent; + + // If that does't work, create a new entry. + if (newEvent.sizeIndex == -1) { + newEvent.sizeIndex = pixmap.sizes.length(); + pixmap.sizes << PixmapState(uncache ? Uncached : ToBeCached); + } + + lastCacheSizeEvent = d->updateCacheCount(lastCacheSizeEvent, startTime, pixSize, + newEvent); break; } case PixmapLoadingStarted: // Load - pixmapStartPoints[newEvent.urlIndex] = d->insertStart(startTime, newEvent); + // Look for a pixmap that hasn't been started, yet. There may have been a refcount + // event, which we ignore. + for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin()); + i != pixmap.sizes.cend(); ++i) { + if (i->loadState == Initial) { + newEvent.sizeIndex = i - pixmap.sizes.cbegin(); + break; + } + } + if (newEvent.sizeIndex == -1) { + newEvent.sizeIndex = pixmap.sizes.length(); + pixmap.sizes << PixmapState(); + } + pixmap.sizes[newEvent.sizeIndex].started = d->insertStart(startTime, newEvent); + pixmap.sizes[newEvent.sizeIndex].loadState = Loading; break; case PixmapLoadingFinished: case PixmapLoadingError: { - int loadIndex = pixmapStartPoints[newEvent.urlIndex]; - if (!isNewEntry && loadIndex != -1) { - d->insertEnd(loadIndex, startTime - d->range(loadIndex).start); - } else { - // if it's a new entry it means that we don't have a corresponding start + // First try to find one that has already started + for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin()); + i != pixmap.sizes.cend(); ++i) { + if (i->loadState != Loading) + continue; + // Pixmaps with known size cannot be errors and vice versa + if (newEvent.pixmapEventType == PixmapLoadingError && i->size.isValid()) + continue; + + newEvent.sizeIndex = i - pixmap.sizes.cbegin(); + break; + } + + // If none was found use any other compatible one + if (newEvent.sizeIndex == -1) { + for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin()); + i != pixmap.sizes.cend(); ++i) { + if (i->loadState != Initial) + continue; + // Pixmaps with known size cannot be errors and vice versa + if (newEvent.pixmapEventType == PixmapLoadingError && i->size.isValid()) + continue; + + newEvent.sizeIndex = i - pixmap.sizes.cbegin(); + break; + } + } + + // If again none was found, create one. + if (newEvent.sizeIndex == -1) { + newEvent.sizeIndex = pixmap.sizes.length(); + pixmap.sizes << PixmapState(); + } + + PixmapState &state = pixmap.sizes[newEvent.sizeIndex]; + // If the pixmap loading wasn't started, start it at traceStartTime() + if (state.loadState == Initial) { newEvent.pixmapEventType = PixmapLoadingStarted; - newEvent.rowNumberExpanded = newEvent.urlIndex + 2; - loadIndex = d->insert(traceStartTime(), startTime - traceStartTime(), newEvent); - pixmapStartPoints[newEvent.urlIndex] = loadIndex; + state.started = d->insert(traceStartTime(), startTime - traceStartTime(), newEvent); + } + + d->insertEnd(state.started, startTime - d->range(state.started).start); + if (newEvent.pixmapEventType == PixmapLoadingError) { + state.loadState = Error; + switch (state.cacheState) { + case Uncached: + state.cacheState = Uncacheable; + break; + case ToBeCached: + state.cacheState = Corrupt; + break; + default: + // Cached cannot happen as size would have to be known and Corrupt or + // Uncacheable cannot happen as we only accept one finish or error event per + // pixmap. + Q_ASSERT(false); + } + } else { + state.loadState = Finished; } - if (event.bindingType == PixmapLoadingFinished) - d->data(loadIndex).cacheSize = 1; // use count to mark success - else - d->data(loadIndex).cacheSize = -1; // ... or failure break; } default: @@ -306,8 +496,7 @@ void PixmapCacheModel::clear() { Q_D(PixmapCacheModel); d->SortedTimelineModel::clear(); - d->pixmapUrls.clear(); - d->pixmapSizes.clear(); + d->pixmaps.clear(); d->collapsedRowCount = 1; d->expandedRowCount = 1; d->expanded = false; @@ -380,6 +569,23 @@ void PixmapCacheModel::PixmapCacheModelPrivate::computeRowCounts() collapsedRowCount++; } +int PixmapCacheModel::PixmapCacheModelPrivate::updateCacheCount(int lastCacheSizeEvent, + qint64 startTime, qint64 pixSize, PixmapCacheEvent &newEvent) +{ + newEvent.pixmapEventType = PixmapCacheCountChanged; + newEvent.eventId = 0; + newEvent.rowNumberExpanded = 1; + newEvent.rowNumberCollapsed = 1; + + qint64 prevSize = 0; + if (lastCacheSizeEvent != -1) { + prevSize = range(lastCacheSizeEvent).cacheSize; + insertEnd(lastCacheSizeEvent, startTime - range(lastCacheSizeEvent).start); + } + + newEvent.cacheSize = prevSize + pixSize; + return insertStart(startTime, newEvent); +} } // namespace Internal |