void MemoryUsageModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
    if (type.message() != MemoryAllocation) {
        if (type.rangeType() != MaximumRangeType) {
            if (event.rangeStage() == RangeStart)
                m_rangeStack.push(RangeStackFrame(event.typeIndex(), event.timestamp()));
            else if (event.rangeStage() == RangeEnd)
                m_rangeStack.pop();

            m_continuation = ContinueNothing;
        }
        return;
    }

    auto canContinue = [&](EventContinuation continuation) {
        QTC_ASSERT(continuation != ContinueNothing, return false);
        if ((m_continuation & continuation) == 0)
            return false;

        int currentIndex = (continuation == ContinueAllocation ? m_currentJSHeapIndex :
                                                                 m_currentUsageIndex);

        if (m_rangeStack.isEmpty()) {
            qint64 amount = event.number<qint64>(0);
            // outside of ranges show monotonous allocation or deallocation
            return (amount >= 0 && m_data[currentIndex].allocated >= 0)
                    || (amount < 0 && m_data[currentIndex].deallocated > 0);
        } else {
            return m_data[currentIndex].typeId == m_rangeStack.top().originTypeIndex
                    && m_rangeStack.top().startTime < startTime(currentIndex);
        }
    };
void DebugMessagesModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
    m_data.insert(insert(event.timestamp(), 0, type.detailType()),
                  MessageData(event.string(), event.typeIndex()));
    if (type.detailType() > m_maximumMsgType)
        m_maximumMsgType = event.typeIndex();
}
void QmlProfilerStatisticsModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
    if (!d->acceptedTypes.contains(type.rangeType()))
        return;

    switch (event.rangeStage()) {
    case RangeStart:
        // binding loop detection: check whether event is already in stack
        for (int ii = 1; ii < d->callStack.size(); ++ii) {
            if (d->callStack.at(ii).typeIndex() == event.typeIndex()
                    && type.rangeType() != Javascript) {
                d->eventsInBindingLoop.insert(event.typeIndex());
                break;
            }
        }
        d->callStack.push(event);
        break;
    case RangeEnd: {
        // update stats
        QmlEventStats *stats = &d->data[event.typeIndex()];
        qint64 duration = event.timestamp() - d->callStack.top().timestamp();
        stats->duration += duration;
        stats->durationSelf += duration;
        if (duration < stats->minTime)
            stats->minTime = duration;
        if (duration > stats->maxTime)
            stats->maxTime = duration;
        stats->calls++;
        // for median computing
        d->durations[event.typeIndex()].append(duration);
        // qml time computation
        if (event.timestamp() > d->lastEndTime) { // assume parent event if starts before last end
            d->qmlTime += duration;
            d->lastEndTime = event.timestamp();
        }

        d->callStack.pop();

        if (d->callStack.count() > 1)
            d->data[d->callStack.top().typeIndex()].durationSelf -= duration;

        break;
    }
    default:
        break;
    }

    if (!d->childrenModel.isNull())
        d->childrenModel->loadEvent(event);
    if (!d->parentsModel.isNull())
        d->parentsModel->loadEvent(event);
}
void FlameGraphModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
    Q_UNUSED(type);

    if (m_stackBottom.children.isEmpty())
        beginResetModel();

    const bool isCompiling = (type.rangeType() == Compiling);
    QStack<QmlEvent> &stack =  isCompiling ? m_compileStack : m_callStack;
    FlameGraphData *&stackTop = isCompiling ? m_compileStackTop : m_callStackTop;

    const QmlEvent *potentialParent = &(stack.top());
    if (type.message() == MemoryAllocation) {
        if (type.detailType() == HeapPage)
            return; // We're only interested in actual allocations, not heap pages being mmap'd

        qint64 amount = event.number<qint64>(0);
        if (amount < 0)
            return; // We're not interested in GC runs here

        for (FlameGraphData *data = stackTop; data; data = data->parent) {
            ++data->allocations;
            data->memory += amount;
        }

    } else if (event.rangeStage() == RangeEnd) {
        stackTop->duration += event.timestamp() - potentialParent->timestamp();
        stack.pop();
        stackTop = stackTop->parent;
        potentialParent = &(stack.top());
    } else {
        QTC_ASSERT(event.rangeStage() == RangeStart, return);
        stack.push(event);
        stackTop = pushChild(stackTop, event);
    }
}
/* 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::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
    PixmapCacheItem newEvent;
    const PixmapEventType pixmapType = static_cast<PixmapEventType>(type.detailType());
    newEvent.pixmapEventType = pixmapType;
    qint64 pixmapStartTime = event.timestamp();

    newEvent.urlIndex = -1;
    for (auto i = m_pixmaps.cend(), begin = m_pixmaps.cbegin(); i != begin;) {
        if ((--i)->url == type.location().filename()) {
            newEvent.urlIndex = i - m_pixmaps.cbegin();
            break;
        }
    }

    newEvent.sizeIndex = -1;
    if (newEvent.urlIndex == -1) {
        newEvent.urlIndex = m_pixmaps.count();
        m_pixmaps << Pixmap(type.location().filename());
    }

    Pixmap &pixmap = m_pixmaps[newEvent.urlIndex];
    switch (pixmapType) {
    case PixmapSizeKnown: {// pixmap size
        // Look for pixmaps for which we don't know the size, yet and which have actually been
        // loaded.
        for (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != 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.number<qint32>(0));
            i->size.setHeight(event.number<qint32>(1));
            newEvent.sizeIndex = i - pixmap.sizes.begin();
            break;
        }

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

        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
        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.
        for (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != 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 (auto i = pixmap.sizes.begin(), end = pixmap.sizes.end(); i != end; ++i) {
                if (uncache && (i->cacheState == Cached || i->cacheState == ToBeCached)) {
                    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) {
                    // A pixmap can repeatedly be cached, become corrupt, and the be uncached again.
                    newEvent.sizeIndex = i - pixmap.sizes.begin();
                    i->cacheState = Corrupt;
                    break;
                }
            }
        }

        // If that does't work, create a new entry.
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState(uncache ? Uncached : ToBeCached);
            // 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());
        }
        break;
    }
    case PixmapLoadingStarted: { // Load
        // Look for a pixmap that hasn't been started, yet. There may have been a refcount
        // event, which we ignore.
        for (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
            if (i->loadState == Initial) {
                newEvent.sizeIndex = i - pixmap.sizes.cbegin();
                break;
            }
        }
        if (newEvent.sizeIndex == -1) {
            newEvent.sizeIndex = pixmap.sizes.length();
            pixmap.sizes << PixmapState();
        }

        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
        for (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
            if (i->loadState != Loading)
                continue;
            // Pixmaps with known size cannot be errors and vice versa
            if (pixmapType == 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 (auto i = pixmap.sizes.cbegin(), end = pixmap.sizes.cend(); i != end; ++i) {
                if (i->loadState != Initial)
                    continue;
                // Pixmaps with known size cannot be errors and vice versa
                if (pixmapType == 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.typeId = event.typeIndex();
            qint64 traceStart = modelManager()->traceTime()->startTime();
            state.started = insert(traceStart, pixmapStartTime - traceStart,
                                   newEvent.urlIndex + 1);
            m_data.insert(state.started, newEvent);

            // 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;
                    }
                }
            }
        } else {
            insertEnd(state.started, pixmapStartTime - startTime(state.started));
        }

        if (pixmapType == 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_UNREACHABLE();
            }
        } else {
            state.loadState = Finished;
        }
        break;
    }
    default:
        break;
    }
}
bool MemoryUsageModel::accepted(const QmlEventType &type) const
{
    return QmlProfilerTimelineModel::accepted(type) || type.rangeType() != MaximumRangeType;
}
bool QmlProfilerTimelineModel::accepted(const QmlEventType &type) const
{
    return (type.rangeType() == m_rangeType && type.message() == m_message);
}