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 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 QmlProfilerRangeModel::loadEvent(const QmlEvent &event, const QmlEventType &type) { Q_UNUSED(type); // store starttime-based instance if (event.rangeStage() == RangeStart) { int index = insertStart(event.timestamp(), event.typeIndex()); m_stack.append(index); m_data.insert(index, QmlRangeEventStartInstance()); } else if (event.rangeStage() == RangeEnd) { if (!m_stack.isEmpty()) { int index = m_stack.pop(); insertEnd(index, event.timestamp() - startTime(index)); } else { qWarning() << "Received inconsistent trace data from application."; } } }
void QmlProfilerStatisticsRelativesModel::loadEvent(const QmlEvent &event) { // level computation switch (event.rangeStage()) { case RangeStart: // now lastparent is the new type ++m_level; m_typesPerLevel[m_level] = event.typeIndex(); m_startTimesPerLevel[m_level] = event.timestamp(); break; case RangeEnd: { int parentTypeIndex = -1; if (m_level > Constants::QML_MIN_LEVEL && m_typesPerLevel.contains(m_level-1)) parentTypeIndex = m_typesPerLevel[m_level-1]; int relativeTypeIndex = (m_relation == QmlProfilerStatisticsParents) ? parentTypeIndex : event.typeIndex(); int selfTypeIndex = (m_relation == QmlProfilerStatisticsParents) ? event.typeIndex() : parentTypeIndex; QmlStatisticsRelativesMap &relativesMap = m_data[selfTypeIndex]; QmlStatisticsRelativesMap::Iterator it = relativesMap.find(relativeTypeIndex); if (it != relativesMap.end()) { it.value().calls++; it.value().duration += event.timestamp() - m_startTimesPerLevel[m_level]; } else { QmlStatisticsRelativesData relative = { event.timestamp() - m_startTimesPerLevel[m_level], 1, false }; relativesMap.insert(relativeTypeIndex, relative); } --m_level; break; } default: break; } }
void FlameGraphModel::loadEvent(const QmlEvent &event, const QmlEventType &type) { if (!m_acceptedTypes.contains(type.rangeType)) return; if (m_stackBottom.children.isEmpty()) beginResetModel(); const QmlEvent *potentialParent = &(m_callStack.top()); if (event.rangeStage() == RangeEnd) { m_stackTop->duration += event.timestamp() - potentialParent->timestamp(); m_callStack.pop(); m_stackTop = m_stackTop->parent; potentialParent = &(m_callStack.top()); } else { QTC_ASSERT(event.rangeStage() == RangeStart, return); m_callStack.push(event); m_stackTop = pushChild(m_stackTop, 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; } }