| #include "apitrace.h" |
| |
| #include "traceloader.h" |
| #include "saverthread.h" |
| |
| #include <QDebug> |
| #include <QFileInfo> |
| #include <QDir> |
| #include <QThread> |
| |
| ApiTrace::ApiTrace() |
| : m_needsSaving(false) |
| { |
| m_loader = new TraceLoader(); |
| |
| connect(this, SIGNAL(loadTrace(QString)), |
| m_loader, SLOT(loadTrace(QString))); |
| connect(this, SIGNAL(requestFrame(ApiTraceFrame*)), |
| m_loader, SLOT(loadFrame(ApiTraceFrame*))); |
| connect(m_loader, SIGNAL(framesLoaded(const QList<ApiTraceFrame*>)), |
| this, SLOT(addFrames(const QList<ApiTraceFrame*>))); |
| connect(m_loader, |
| SIGNAL(frameContentsLoaded(ApiTraceFrame*,QVector<ApiTraceCall*>, QVector<ApiTraceCall*>,quint64)), |
| this, |
| SLOT(loaderFrameLoaded(ApiTraceFrame*,QVector<ApiTraceCall*>,QVector<ApiTraceCall*>,quint64))); |
| connect(m_loader, SIGNAL(guessedApi(int)), |
| this, SLOT(guessedApi(int))); |
| connect(this, SIGNAL(loaderSearch(ApiTrace::SearchRequest)), |
| m_loader, SLOT(search(ApiTrace::SearchRequest))); |
| connect(m_loader, |
| SIGNAL(searchResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*)), |
| this, |
| SLOT(loaderSearchResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*))); |
| connect(this, SIGNAL(loaderFindFrameStart(ApiTraceFrame*)), |
| m_loader, SLOT(findFrameStart(ApiTraceFrame*))); |
| connect(this, SIGNAL(loaderFindFrameEnd(ApiTraceFrame*)), |
| m_loader, SLOT(findFrameEnd(ApiTraceFrame*))); |
| connect(m_loader, SIGNAL(foundFrameStart(ApiTraceFrame*)), |
| this, SIGNAL(foundFrameStart(ApiTraceFrame*))); |
| connect(m_loader, SIGNAL(foundFrameEnd(ApiTraceFrame*)), |
| this, SIGNAL(foundFrameEnd(ApiTraceFrame*))); |
| connect(this, SIGNAL(loaderFindCallIndex(int)), |
| m_loader, SLOT(findCallIndex(int))); |
| connect(m_loader, SIGNAL(foundCallIndex(ApiTraceCall*)), |
| this, SIGNAL(foundCallIndex(ApiTraceCall*))); |
| |
| |
| connect(m_loader, SIGNAL(parseProblem(const QString&)), |
| this, SIGNAL(problemLoadingTrace(const QString&))); |
| connect(m_loader, SIGNAL(startedParsing()), |
| this, SIGNAL(startedLoadingTrace())); |
| connect(m_loader, SIGNAL(parsed(int)), |
| this, SIGNAL(loaded(int))); |
| connect(m_loader, SIGNAL(finishedParsing()), |
| this, SIGNAL(finishedLoadingTrace())); |
| |
| |
| m_saver = new SaverThread(this); |
| connect(m_saver, SIGNAL(traceSaved()), |
| this, SLOT(slotSaved())); |
| connect(m_saver, SIGNAL(traceSaved()), |
| this, SIGNAL(saved())); |
| |
| m_loaderThread = new QThread(); |
| m_loader->moveToThread(m_loaderThread); |
| m_loaderThread->start(); |
| } |
| |
| ApiTrace::~ApiTrace() |
| { |
| m_loaderThread->quit(); |
| m_loaderThread->deleteLater(); |
| qDeleteAll(m_frames); |
| delete m_loader; |
| delete m_saver; |
| } |
| |
| bool ApiTrace::isEmpty() const |
| { |
| return m_frames.isEmpty(); |
| } |
| |
| QString ApiTrace::fileName() const |
| { |
| if (edited()) { |
| return m_tempFileName; |
| } |
| |
| return m_fileName; |
| } |
| |
| const QList<ApiTraceFrame*> & ApiTrace::frames() const |
| { |
| return m_frames; |
| } |
| |
| ApiTraceFrame * ApiTrace::frameAt(int idx) const |
| { |
| return m_frames.value(idx); |
| } |
| |
| int ApiTrace::numFrames() const |
| { |
| return m_frames.count(); |
| } |
| |
| int ApiTrace::numCallsInFrame(int idx) const |
| { |
| const ApiTraceFrame *frame = frameAt(idx); |
| if (frame) { |
| return frame->numTotalCalls(); |
| } else { |
| return 0; |
| } |
| } |
| |
| void ApiTrace::setFileName(const QString &name) |
| { |
| if (m_fileName != name) { |
| m_fileName = name; |
| m_tempFileName = QString(); |
| |
| m_frames.clear(); |
| m_errors.clear(); |
| m_editedCalls.clear(); |
| m_queuedErrors.clear(); |
| m_needsSaving = false; |
| emit invalidated(); |
| |
| emit loadTrace(m_fileName); |
| } |
| } |
| |
| void ApiTrace::addFrames(const QList<ApiTraceFrame*> &frames) |
| { |
| int currentFrames = m_frames.count(); |
| int numNewFrames = frames.count(); |
| |
| emit beginAddingFrames(currentFrames, numNewFrames); |
| |
| m_frames += frames; |
| |
| foreach(ApiTraceFrame *frame, frames) { |
| frame->setParentTrace(this); |
| } |
| |
| emit endAddingFrames(); |
| } |
| |
| ApiTraceCall * ApiTrace::callWithIndex(int idx) const |
| { |
| for (int i = 0; i < m_frames.count(); ++i) { |
| ApiTraceCall *call = m_frames[i]->callWithIndex(idx); |
| if (call) { |
| return call; |
| } |
| } |
| return NULL; |
| } |
| |
| ApiTraceState ApiTrace::defaultState() const |
| { |
| ApiTraceFrame *frame = frameAt(0); |
| if (!frame || !frame->isLoaded() || frame->isEmpty()) { |
| return ApiTraceState(); |
| } |
| |
| ApiTraceCall *firstCall = frame->calls().first(); |
| if (!firstCall->hasState()) { |
| return ApiTraceState(); |
| } |
| |
| return *firstCall->state(); |
| } |
| |
| void ApiTrace::callEdited(ApiTraceCall *call) |
| { |
| if (!m_editedCalls.contains(call)) { |
| // Lets generate a temp filename |
| QFileInfo fileInfo(m_fileName); |
| m_tempFileName = QDir::temp().filePath(fileInfo.fileName() + |
| QString::fromLatin1(".edited")); |
| } |
| m_editedCalls.insert(call); |
| m_needsSaving = true; |
| |
| emit changed(call); |
| } |
| |
| void ApiTrace::callReverted(ApiTraceCall *call) |
| { |
| m_editedCalls.remove(call); |
| |
| if (m_editedCalls.isEmpty()) { |
| m_needsSaving = false; |
| } |
| emit changed(call); |
| } |
| |
| bool ApiTrace::edited() const |
| { |
| return !m_editedCalls.isEmpty(); |
| } |
| |
| bool ApiTrace::needsSaving() const |
| { |
| return m_needsSaving; |
| } |
| |
| void ApiTrace::save() |
| { |
| QFileInfo fi(m_tempFileName); |
| QDir dir; |
| emit startedSaving(); |
| dir.mkpath(fi.absolutePath()); |
| m_saver->saveFile(m_tempFileName, |
| m_fileName, |
| m_editedCalls); |
| } |
| |
| void ApiTrace::slotSaved() |
| { |
| m_needsSaving = false; |
| } |
| |
| bool ApiTrace::isSaving() const |
| { |
| return m_saver->isRunning(); |
| } |
| |
| bool ApiTrace::hasErrors() const |
| { |
| return !m_errors.isEmpty() || !m_queuedErrors.isEmpty(); |
| } |
| |
| void ApiTrace::loadFrame(ApiTraceFrame *frame) |
| { |
| if (!isFrameLoading(frame)) { |
| Q_ASSERT(!frame->isLoaded()); |
| m_loadingFrames.insert(frame); |
| emit requestFrame(frame); |
| } |
| } |
| |
| void ApiTrace::guessedApi(int api) |
| { |
| m_api = static_cast<trace::API>(api); |
| } |
| |
| trace::API ApiTrace::api() const |
| { |
| return m_api; |
| } |
| |
| void ApiTrace::finishedParsing() |
| { |
| if (!m_frames.isEmpty()) { |
| ApiTraceFrame *firstFrame = m_frames[0]; |
| if (firstFrame && !firstFrame->isLoaded()) { |
| loadFrame(firstFrame); |
| } |
| } |
| } |
| |
| void ApiTrace::loaderFrameLoaded(ApiTraceFrame *frame, |
| const QVector<ApiTraceCall*> &topLevelItems, |
| const QVector<ApiTraceCall*> &calls, |
| quint64 binaryDataSize) |
| { |
| Q_ASSERT(frame->numChildrenToLoad() >= calls.size()); |
| |
| if (!frame->isLoaded()) { |
| emit beginLoadingFrame(frame, calls.size()); |
| frame->setCalls(topLevelItems, calls, binaryDataSize); |
| emit endLoadingFrame(frame); |
| m_loadingFrames.remove(frame); |
| } |
| |
| if (!m_queuedErrors.isEmpty()) { |
| QList< QPair<ApiTraceFrame*, ApiTraceError> >::iterator itr; |
| itr = m_queuedErrors.begin(); |
| while (itr != m_queuedErrors.end()) { |
| const ApiTraceError &error = (*itr).second; |
| if ((*itr).first == frame) { |
| ApiTraceCall *call = frame->callWithIndex(error.callIndex); |
| |
| if (!call) { |
| ++itr; |
| continue; |
| } |
| |
| call->setError(error.message); |
| itr = m_queuedErrors.erase(itr); |
| |
| if (call->hasError()) { |
| m_errors.insert(call); |
| } else { |
| m_errors.remove(call); |
| } |
| emit changed(call); |
| } else { |
| ++itr; |
| } |
| } |
| } |
| } |
| |
| void ApiTrace::findNext(ApiTraceFrame *frame, |
| ApiTraceCall *from, |
| const QString &str, |
| Qt::CaseSensitivity sensitivity, |
| bool useRegex) |
| { |
| ApiTraceCall *foundCall = 0; |
| int frameIdx = m_frames.indexOf(frame); |
| SearchRequest request(SearchRequest::Next, |
| frame, from, str, sensitivity, useRegex); |
| |
| if (frame->isLoaded()) { |
| foundCall = frame->findNextCall(from, str, sensitivity, useRegex); |
| if (foundCall) { |
| emit findResult(request, SearchResult_Found, foundCall); |
| return; |
| } |
| |
| //if the frame is loaded we already searched it above |
| // so skip it |
| frameIdx += 1; |
| } |
| |
| //for the rest of the frames we search from beginning |
| request.from = 0; |
| for (int i = frameIdx; i < m_frames.count(); ++i) { |
| ApiTraceFrame *frame = m_frames[i]; |
| request.frame = frame; |
| if (!frame->isLoaded()) { |
| emit loaderSearch(request); |
| return; |
| } else { |
| ApiTraceCall *call = frame->findNextCall(0, str, sensitivity, useRegex); |
| if (call) { |
| emit findResult(request, SearchResult_Found, call); |
| return; |
| } |
| } |
| } |
| emit findResult(request, SearchResult_Wrapped, 0); |
| } |
| |
| void ApiTrace::findPrev(ApiTraceFrame *frame, |
| ApiTraceCall *from, |
| const QString &str, |
| Qt::CaseSensitivity sensitivity, |
| bool useRegex) |
| { |
| ApiTraceCall *foundCall = 0; |
| int frameIdx = m_frames.indexOf(frame); |
| SearchRequest request(SearchRequest::Prev, |
| frame, from, str, sensitivity, useRegex); |
| |
| if (frame->isLoaded()) { |
| foundCall = frame->findPrevCall(from, str, sensitivity, useRegex); |
| if (foundCall) { |
| emit findResult(request, SearchResult_Found, foundCall); |
| return; |
| } |
| |
| //if the frame is loaded we already searched it above |
| // so skip it |
| frameIdx -= 1; |
| } |
| |
| request.from = 0; |
| for (int i = frameIdx; i >= 0; --i) { |
| ApiTraceFrame *frame = m_frames[i]; |
| request.frame = frame; |
| if (!frame->isLoaded()) { |
| emit loaderSearch(request); |
| return; |
| } else { |
| ApiTraceCall *call = frame->findPrevCall(0, str, sensitivity, useRegex); |
| if (call) { |
| emit findResult(request, SearchResult_Found, call); |
| return; |
| } |
| } |
| } |
| emit findResult(request, SearchResult_Wrapped, 0); |
| } |
| |
| void ApiTrace::loaderSearchResult(const ApiTrace::SearchRequest &request, |
| ApiTrace::SearchResult result, |
| ApiTraceCall *call) |
| { |
| //qDebug()<<"Search result = "<<result |
| // <<", call is = "<<call; |
| emit findResult(request, result, call); |
| } |
| |
| void ApiTrace::findFrameStart(ApiTraceFrame *frame) |
| { |
| if (!frame) |
| return; |
| |
| if (frame->isLoaded()) { |
| emit foundFrameStart(frame); |
| } else { |
| emit loaderFindFrameStart(frame); |
| } |
| } |
| |
| void ApiTrace::findFrameEnd(ApiTraceFrame *frame) |
| { |
| if (!frame) |
| return; |
| |
| if (frame->isLoaded()) { |
| emit foundFrameEnd(frame); |
| } else { |
| emit loaderFindFrameEnd(frame); |
| } |
| } |
| |
| void ApiTrace::findCallIndex(int index) |
| { |
| int frameIdx = callInFrame(index); |
| ApiTraceFrame *frame = 0; |
| |
| if (frameIdx < 0) { |
| emit foundCallIndex(0); |
| return; |
| } |
| |
| frame = m_frames[frameIdx]; |
| |
| if (frame) { |
| if (frame->isLoaded()) { |
| ApiTraceCall *call = frame->callWithIndex(index); |
| emit foundCallIndex(call); |
| } else { |
| emit loaderFindCallIndex(index); |
| } |
| } |
| } |
| |
| int ApiTrace::callInFrame(int callIdx) const |
| { |
| unsigned numCalls = 0; |
| |
| for (int frameIdx = 0; frameIdx < m_frames.size(); ++frameIdx) { |
| const ApiTraceFrame *frame = m_frames[frameIdx]; |
| unsigned numCallsInFrame = frame->isLoaded() |
| ? frame->numTotalCalls() |
| : frame->numChildrenToLoad(); |
| unsigned firstCall = numCalls; |
| unsigned endCall = numCalls + numCallsInFrame; |
| if (firstCall <= callIdx && endCall > callIdx) { |
| return frameIdx; |
| } |
| numCalls = endCall; |
| } |
| |
| return -1; |
| } |
| |
| void ApiTrace::setCallError(const ApiTraceError &error) |
| { |
| int frameIdx = callInFrame(error.callIndex); |
| if (frameIdx < 0) { |
| return; |
| } |
| |
| ApiTraceFrame *frame = 0; |
| frame = m_frames[frameIdx]; |
| if (frame->isLoaded()) { |
| ApiTraceCall *call = frame->callWithIndex(error.callIndex); |
| // call might be null if the error is in a filtered call |
| if (call) { |
| call->setError(error.message); |
| if (call->hasError()) { |
| m_errors.insert(call); |
| } else { |
| m_errors.remove(call); |
| } |
| emit changed(call); |
| } |
| } else { |
| loadFrame(frame); |
| m_queuedErrors.append(qMakePair(frame, error)); |
| } |
| } |
| |
| bool ApiTrace::isFrameLoading(ApiTraceFrame *frame) const |
| { |
| return m_loadingFrames.contains(frame); |
| } |
| |
| void ApiTrace::bindThumbnails(const ImageHash &thumbnails) |
| { |
| QHashIterator<int, QImage> i(thumbnails); |
| |
| while (i.hasNext()) { |
| i.next(); |
| |
| if (!m_thumbnails.contains(i.key())) { |
| int callIndex = i.key(); |
| const QImage &thumbnail = i.value(); |
| |
| m_thumbnails.insert(callIndex, thumbnail); |
| |
| // find the frame associated with the call index |
| int frameIndex = 0; |
| while (frameAt(frameIndex)->lastCallIndex() < callIndex) { |
| ++frameIndex; |
| } |
| |
| ApiTraceFrame *frame = frameAt(frameIndex); |
| |
| // if the call was actually for a frame, ... |
| if (callIndex == frame->lastCallIndex()) { |
| frame->setThumbnail(thumbnail); |
| |
| emit changed(frame); |
| } else { |
| ApiTraceCall *call = frame->callWithIndex(callIndex); |
| if (call) { |
| call->setThumbnail(thumbnail); |
| |
| emit changed(call); |
| } |
| } |
| } |
| } |
| } |
| |
| void ApiTrace::missingThumbnail(ApiTraceFrame *frame) |
| { |
| missingThumbnail(frame->lastCallIndex()); |
| } |
| |
| void ApiTrace::missingThumbnail(ApiTraceCall *call) |
| { |
| missingThumbnail(call->index()); |
| } |
| |
| void ApiTrace::missingThumbnail(int callIdx) |
| { |
| // technically, the contain() test is redundant, since this is a set; |
| // however, it enables debugging techniques to confirm correct behavior |
| if (!m_missingThumbnails.contains(callIdx)) { |
| //qDebug() << QLatin1String("debug: new missing thumbnail: ") << callIdx; |
| m_missingThumbnails.insert(callIdx); |
| } |
| } |
| |
| bool ApiTrace::isMissingThumbnails() const |
| { |
| return !m_missingThumbnails.isEmpty(); |
| } |
| void ApiTrace::resetMissingThumbnails() |
| { |
| m_missingThumbnails.clear(); |
| } |
| |
| void ApiTrace::iterateMissingThumbnails(void *object, ThumbnailCallback cb) |
| { |
| //qDebug() << QLatin1String("debug: count of missing thumbnail list") << m_missingThumbnails.count(); |
| foreach (int thumbnailIndex, m_missingThumbnails) { |
| //qDebug() << QLatin1String("debug: iterate missing thumbnail list") << thumbnailIndex; |
| (*cb)(object, thumbnailIndex); |
| } |
| } |
| |
| #include "apitrace.moc" |