pixmapcachemodel.cpp 18.3 KB
Newer Older
1 2
/****************************************************************************
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
6
** This file is part of Qt Creator.
7
**
8 9 10
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
11
** Software or, alternatively, in accordance with the terms contained in
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 24 25 26
**
****************************************************************************/

#include "pixmapcachemodel.h"
27
#include "qmlprofilermodelmanager.h"
28
#include "qmlprofilereventtypes.h"
29

30
namespace QmlProfiler {
31 32
namespace Internal {

33
PixmapCacheModel::PixmapCacheModel(QmlProfilerModelManager *manager, QObject *parent) :
34 35
    QmlProfilerTimelineModel(manager, PixmapCacheEvent, MaximumRangeType, ProfilePixmapCache,
                             parent)
36
{
37 38
}

39 40 41
int PixmapCacheModel::rowMaxValue(int rowNumber) const
{
    if (rowNumber == 1) {
42
        return m_maxCacheSize;
43
    } else {
44
        return QmlProfilerTimelineModel::rowMaxValue(rowNumber);
45 46 47
    }
}

48 49 50 51 52 53
int PixmapCacheModel::expandedRow(int index) const
{
    return selectionId(index) + 1;
}

int PixmapCacheModel::collapsedRow(int index) const
54
{
55
    return m_data[index].rowNumberCollapsed;
56 57
}

58
int PixmapCacheModel::typeId(int index) const
59
{
60
    return m_data[index].typeId;
61 62
}

63
QColor PixmapCacheModel::color(int index) const
64
{
65 66
    if (m_data[index].pixmapEventType == PixmapCacheCountChanged)
        return colorByHue(s_pixmapCacheCountHue);
67

68
    return colorBySelectionId(index);
69 70
}

71
float PixmapCacheModel::relativeHeight(int index) const
72
{
73 74
    if (m_data[index].pixmapEventType == PixmapCacheCountChanged)
        return (float)m_data[index].cacheSize / (float)m_maxCacheSize;
75 76
    else
        return 1.0f;
77 78 79 80 81 82 83 84 85 86
}

QString getFilenameOnly(QString absUrl)
{
    int characterPos = absUrl.lastIndexOf(QLatin1Char('/'))+1;
    if (characterPos < absUrl.length())
        absUrl = absUrl.mid(characterPos);
    return absUrl;
}

87
QVariantList PixmapCacheModel::labels() const
88 89 90
{
    QVariantList result;

91 92
    // Cache Size
    QVariantMap element;
93
    element.insert(QLatin1String("description"), tr("Cache Size"));
94

95
    element.insert(QLatin1String("id"), 0);
96 97
    result << element;

98
    for (int i = 0; i < m_pixmaps.count(); ++i) {
99 100 101
        // Loading
        QVariantMap element;
        element.insert(QLatin1String("displayName"), m_pixmaps[i].url);
102
        element.insert(QLatin1String("description"), getFilenameOnly(m_pixmaps[i].url));
103

104
        element.insert(QLatin1String("id"), QVariant(i + 1));
105
        result << element;
106 107 108 109 110
    }

    return result;
}

Ulf Hermann's avatar
Ulf Hermann committed
111
QVariantMap PixmapCacheModel::details(int index) const
112
{
Ulf Hermann's avatar
Ulf Hermann committed
113
    QVariantMap result;
114
    const PixmapCacheItem *ev = &m_data[index];
115

Ulf Hermann's avatar
Ulf Hermann committed
116 117
    if (ev->pixmapEventType == PixmapCacheCountChanged) {
        result.insert(QLatin1String("displayName"), tr("Image Cached"));
118
    } else {
119 120 121
        result.insert(QLatin1String("displayName"), tr("Image Loaded"));
        if (m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].loadState != Finished)
            result.insert(tr("Result"), tr("Load Error"));
122
        result.insert(tr("Duration"), QmlProfilerDataModel::formatTime(duration(index)));
123 124
    }

Ulf Hermann's avatar
Ulf Hermann committed
125
    result.insert(tr("Cache Size"), QString::fromLatin1("%1 px").arg(ev->cacheSize));
126
    result.insert(tr("File"), getFilenameOnly(m_pixmaps[ev->urlIndex].url));
Ulf Hermann's avatar
Ulf Hermann committed
127
    result.insert(tr("Width"), QString::fromLatin1("%1 px")
128
                  .arg(m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.width()));
Ulf Hermann's avatar
Ulf Hermann committed
129
    result.insert(tr("Height"), QString::fromLatin1("%1 px")
130
                  .arg(m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.height()));
131 132 133
    return result;
}

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
/* 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.
 */
163
void PixmapCacheModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
164
{
165 166 167 168 169
    PixmapCacheItem newEvent;
    newEvent.pixmapEventType = static_cast<PixmapEventType>(type.detailType);
    qint64 pixmapStartTime = event.timestamp();

    newEvent.urlIndex = -1;
170 171 172
    for (auto i = m_pixmaps.cend(), begin = m_pixmaps.cbegin(); i != begin;) {
        if ((--i)->url == type.location.filename) {
            newEvent.urlIndex = i - m_pixmaps.cbegin();
173
            break;
174
        }
175
    }
176

177 178 179 180 181
    newEvent.sizeIndex = -1;
    if (newEvent.urlIndex == -1) {
        newEvent.urlIndex = m_pixmaps.count();
        m_pixmaps << Pixmap(type.location.filename);
    }
182

183 184 185 186 187
    Pixmap &pixmap = m_pixmaps[newEvent.urlIndex];
    switch (newEvent.pixmapEventType) {
    case PixmapSizeKnown: {// pixmap size
        // Look for pixmaps for which we don't know the size, yet and which have actually been
        // loaded.
188
        for (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != end; ++i) {
189 190 191 192 193 194 195 196 197 198 199
            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.number<qint32>(0));
            i->size.setHeight(event.number<qint32>(1));
            newEvent.sizeIndex = i - pixmap.sizes.begin();
            break;
        }
200

201 202 203 204
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState(event.number<qint32>(0), event.number<qint32>(1));
        }
205

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
        PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
        if (state.cacheState == ToBeCached) {
            m_lastCacheSizeEvent = updateCacheCount(m_lastCacheSizeEvent, pixmapStartTime,
                                          state.size.width() * state.size.height(), newEvent,
                                          event.typeIndex());
            state.cacheState = Cached;
        }
        break;
    }
    case PixmapCacheCountChanged: {// Cache Size Changed Event
        pixmapStartTime = event.timestamp() + 1; // delay 1 ns for proper sorting

        bool uncache = m_cumulatedCount > event.number<qint32>(2);
        m_cumulatedCount = event.number<qint32>(2);
        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.
224
        for (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != end; ++i) {
225
            if (uncache && i->cacheState == Corrupt) {
226
                newEvent.sizeIndex = i - pixmap.sizes.begin();
227 228 229 230 231 232 233 234 235 236
                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;
                }
237
                break;
238 239 240
            }
        }

241 242 243 244
        // 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) {
245 246
            for (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != end; ++i) {
                if (uncache && (i->cacheState == Cached || i->cacheState == ToBeCached)) {
247
                    newEvent.sizeIndex = i - pixmap.sizes.begin();
248 249 250
                    if (i->size.isValid())
                        pixSize = -i->size.width() * i->size.height();
                    i->cacheState = Uncached;
251
                    break;
252
                } else if (!uncache && i->cacheState == Uncacheable) {
253
                    // A pixmap can repeatedly be cached, become corrupt, and the be uncached again.
254
                    newEvent.sizeIndex = i - pixmap.sizes.begin();
255
                    i->cacheState = Corrupt;
256 257 258
                    break;
                }
            }
259
        }
260

261 262 263 264
        // If that does't work, create a new entry.
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState(uncache ? Uncached : ToBeCached);
265 266 267 268
            // now the size is 0. Thus, there is no point in updating the size row.
        } else if (pixSize != 0) {
            m_lastCacheSizeEvent = updateCacheCount(m_lastCacheSizeEvent, pixmapStartTime, pixSize,
                                                    newEvent, event.typeIndex());
269 270 271 272 273 274
        }
        break;
    }
    case PixmapLoadingStarted: { // Load
        // Look for a pixmap that hasn't been started, yet. There may have been a refcount
        // event, which we ignore.
275
        for (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
276 277 278
            if (i->loadState == Initial) {
                newEvent.sizeIndex = i - pixmap.sizes.cbegin();
                break;
279
            }
280
        }
281 282 283 284
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState();
        }
285

286 287 288 289 290 291 292 293 294 295
        PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
        state.loadState = Loading;
        newEvent.typeId = event.typeIndex();
        state.started = insertStart(pixmapStartTime, newEvent.urlIndex + 1);
        m_data.insert(state.started, newEvent);
        break;
    }
    case PixmapLoadingFinished:
    case PixmapLoadingError: {
        // First try to find one that has already started
296
        for (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
297 298 299 300 301 302 303
            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();
304
            break;
305
        }
306 307 308

        // If none was found use any other compatible one
        if (newEvent.sizeIndex == -1) {
309
            for (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
310
                if (i->loadState != Initial)
311 312 313 314 315 316 317 318
                    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;
            }
319
        }
320

321 322 323 324 325
        // If again none was found, create one.
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState();
        }
326

327 328 329 330 331 332 333 334 335
        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.typeId = event.typeIndex();
            qint64 traceStart = modelManager()->traceTime()->startTime();
            state.started = insert(traceStart, pixmapStartTime - traceStart,
                                   newEvent.urlIndex + 1);
            m_data.insert(state.started, newEvent);
336

337 338 339 340 341 342 343 344 345 346 347
            // All other indices are wrong now as we've prepended. Fix them ...
            if (m_lastCacheSizeEvent >= state.started)
                ++m_lastCacheSizeEvent;

            for (int pixmapIndex = 0; pixmapIndex < m_pixmaps.count(); ++pixmapIndex) {
                Pixmap &brokenPixmap = m_pixmaps[pixmapIndex];
                for (int sizeIndex = 0; sizeIndex < brokenPixmap.sizes.count(); ++sizeIndex) {
                    PixmapState &brokenSize = brokenPixmap.sizes[sizeIndex];
                    if ((pixmapIndex != newEvent.urlIndex || sizeIndex != newEvent.sizeIndex) &&
                            brokenSize.started >= state.started) {
                        ++brokenSize.started;
348 349
                    }
                }
350
            }
351
        }
352

353 354 355 356 357 358 359 360 361 362 363 364 365 366
        insertEnd(state.started, pixmapStartTime - startTime(state.started));
        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.
367
                Q_UNREACHABLE();
368
            }
369 370
        } else {
            state.loadState = Finished;
371
        }
372 373 374 375
        break;
    }
    default:
        break;
376
    }
377
}
378

379 380 381 382 383 384
void PixmapCacheModel::finalize()
{
    if (m_lastCacheSizeEvent != -1) {
        insertEnd(m_lastCacheSizeEvent, modelManager()->traceTime()->endTime() -
                  startTime(m_lastCacheSizeEvent));
    }
385

386 387 388
    resizeUnfinishedLoads();
    computeMaxCacheSize();
    flattenLoads();
389
    computeNesting();
390 391 392 393
}

void PixmapCacheModel::clear()
{
394 395
    m_pixmaps.clear();
    m_data.clear();
396 397 398
    m_maxCacheSize = 1;
    m_lastCacheSizeEvent = -1;
    m_cumulatedCount = 0;
399
    QmlProfilerTimelineModel::clear();
400 401
}

402
void PixmapCacheModel::computeMaxCacheSize()
403
{
404
    foreach (const PixmapCacheModel::PixmapCacheItem &event, m_data) {
405
        if (event.pixmapEventType == PixmapCacheModel::PixmapCacheCountChanged) {
406 407
            if (event.cacheSize > m_maxCacheSize)
                m_maxCacheSize = event.cacheSize;
408 409 410 411
        }
    }
}

412
void PixmapCacheModel::resizeUnfinishedLoads()
413 414
{
    // all the "load start" events with duration 0 continue till the end of the trace
415 416 417 418
    for (int i = 0; i < count(); i++) {
        if (m_data[i].pixmapEventType == PixmapCacheModel::PixmapLoadingStarted &&
                duration(i) == 0) {
            insertEnd(i, modelManager()->traceTime()->endTime() - startTime(i));
419 420 421 422
        }
    }
}

423
void PixmapCacheModel::flattenLoads()
424
{
425
    int collapsedRowCount = 0;
Ulf Hermann's avatar
Ulf Hermann committed
426

427 428
    // computes "compressed row"
    QVector <qint64> eventEndTimes;
429
    for (int i = 0; i < count(); i++) {
430
        PixmapCacheModel::PixmapCacheItem &event = m_data[i];
431 432 433
        if (event.pixmapEventType == PixmapCacheModel::PixmapLoadingStarted) {
            event.rowNumberCollapsed = 0;
            while (eventEndTimes.count() > event.rowNumberCollapsed &&
434
                   eventEndTimes[event.rowNumberCollapsed] > startTime(i))
435 436 437
                event.rowNumberCollapsed++;

            if (eventEndTimes.count() == event.rowNumberCollapsed)
438
                eventEndTimes << 0; // increase stack length, proper value added below
439
            eventEndTimes[event.rowNumberCollapsed] = endTime(i);
440 441

            // readjust to account for category empty row and bargraph
442
            event.rowNumberCollapsed += 2;
443 444 445 446 447 448
        }
        if (event.rowNumberCollapsed > collapsedRowCount)
            collapsedRowCount = event.rowNumberCollapsed;
    }

    // Starting from 0, count is maxIndex+1
449 450
    setCollapsedRowCount(collapsedRowCount + 1);
    setExpandedRowCount(m_pixmaps.count() + 2);
451 452
}

453
int PixmapCacheModel::updateCacheCount(int lastCacheSizeEvent,
454
        qint64 pixmapStartTime, qint64 pixSize, PixmapCacheItem &newEvent, int typeId)
455 456 457 458 459 460
{
    newEvent.pixmapEventType = PixmapCacheCountChanged;
    newEvent.rowNumberCollapsed = 1;

    qint64 prevSize = 0;
    if (lastCacheSizeEvent != -1) {
461 462
        prevSize = m_data[lastCacheSizeEvent].cacheSize;
        insertEnd(lastCacheSizeEvent, pixmapStartTime - startTime(lastCacheSizeEvent));
463 464 465
    }

    newEvent.cacheSize = prevSize + pixSize;
466 467
    newEvent.typeId = typeId;
    int index = insertStart(pixmapStartTime, 0);
468
    m_data.insert(index, newEvent);
469
    return index;
470
}
471 472 473


} // namespace Internal
474
} // namespace QmlProfiler