| #include "traceloader.h" |
| |
| #include "apitrace.h" |
| #include <QDebug> |
| #include <QFile> |
| |
| #define FRAMES_TO_CACHE 100 |
| |
| static ApiTraceCall * |
| apiCallFromTraceCall(const trace::Call *call, |
| const QHash<QString, QUrl> &helpHash, |
| ApiTraceFrame *frame, |
| ApiTraceCall *parentCall, |
| TraceLoader *loader) |
| { |
| ApiTraceCall *apiCall; |
| |
| if (parentCall) |
| apiCall = new ApiTraceCall(parentCall, loader, call); |
| else |
| apiCall = new ApiTraceCall(frame, loader, call); |
| |
| apiCall->setHelpUrl(helpHash.value(apiCall->name())); |
| |
| return apiCall; |
| } |
| |
| TraceLoader::TraceLoader(QObject *parent) |
| : QObject(parent) |
| { |
| } |
| |
| TraceLoader::~TraceLoader() |
| { |
| m_parser.close(); |
| qDeleteAll(m_signatures); |
| } |
| |
| void TraceLoader::loadTrace(const QString &filename) |
| { |
| if (m_helpHash.isEmpty()) { |
| loadHelpFile(); |
| } |
| |
| if (!m_frameBookmarks.isEmpty()) { |
| qDeleteAll(m_signatures); |
| m_signatures.clear(); |
| m_frameBookmarks.clear(); |
| m_createdFrames.clear(); |
| m_parser.close(); |
| } |
| |
| if (!m_parser.open(filename.toLatin1())) { |
| qDebug() << "error: failed to open " << filename; |
| return; |
| } |
| |
| if (!m_parser.supportsOffsets()) { |
| emit parseProblem( |
| "This trace in compressed in a format that does not allow random seeking.\n" |
| "Please repack the trace with `apitrace repack`." |
| ); |
| m_parser.close(); |
| return; |
| } |
| |
| emit startedParsing(); |
| |
| scanTrace(); |
| |
| emit guessedApi(static_cast<int>(m_parser.api)); |
| emit finishedParsing(); |
| } |
| |
| void TraceLoader::loadFrame(ApiTraceFrame *currentFrame) |
| { |
| fetchFrameContents(currentFrame); |
| } |
| |
| int TraceLoader::numberOfFrames() const |
| { |
| return m_frameBookmarks.size(); |
| } |
| |
| int TraceLoader::numberOfCallsInFrame(int frameIdx) const |
| { |
| if (frameIdx >= m_frameBookmarks.size()) { |
| return 0; |
| } |
| FrameBookmarks::const_iterator itr = |
| m_frameBookmarks.find(frameIdx); |
| return itr->numberOfCalls; |
| } |
| |
| void TraceLoader::loadHelpFile() |
| { |
| QFile file(":/resources/glreference.tsv"); |
| if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| QString line; |
| while (!file.atEnd()) { |
| line = file.readLine(); |
| QString function = line.section('\t', 0, 0).trimmed(); |
| QUrl url = QUrl(line.section('\t', 1, 1).trimmed()); |
| //qDebug()<<"function = "<<function<<", url = "<<url.toString(); |
| m_helpHash.insert(function, url); |
| } |
| } else { |
| qWarning() << "Couldn't open reference file " |
| << file.fileName(); |
| } |
| file.close(); |
| } |
| |
| void TraceLoader::scanTrace() |
| { |
| QList<ApiTraceFrame*> frames; |
| ApiTraceFrame *currentFrame = 0; |
| |
| trace::Call *call; |
| trace::ParseBookmark startBookmark; |
| int numOfFrames = 0; |
| int numOfCalls = 0; |
| int lastPercentReport = 0; |
| |
| m_parser.getBookmark(startBookmark); |
| |
| while ((call = m_parser.scan_call())) { |
| ++numOfCalls; |
| |
| if (call->flags & trace::CALL_FLAG_END_FRAME) { |
| FrameBookmark frameBookmark(startBookmark); |
| frameBookmark.numberOfCalls = numOfCalls; |
| |
| currentFrame = new ApiTraceFrame(); |
| currentFrame->number = numOfFrames; |
| currentFrame->setNumChildren(numOfCalls); |
| currentFrame->setLastCallIndex(call->no); |
| frames.append(currentFrame); |
| |
| m_createdFrames.append(currentFrame); |
| m_frameBookmarks[numOfFrames] = frameBookmark; |
| ++numOfFrames; |
| |
| if (m_parser.percentRead() - lastPercentReport >= 5) { |
| emit parsed(m_parser.percentRead()); |
| lastPercentReport = m_parser.percentRead(); |
| } |
| m_parser.getBookmark(startBookmark); |
| numOfCalls = 0; |
| } |
| delete call; |
| } |
| |
| if (numOfCalls) { |
| //trace::File::Bookmark endBookmark = m_parser.currentBookmark(); |
| FrameBookmark frameBookmark(startBookmark); |
| frameBookmark.numberOfCalls = numOfCalls; |
| |
| currentFrame = new ApiTraceFrame(); |
| currentFrame->number = numOfFrames; |
| currentFrame->setNumChildren(numOfCalls); |
| frames.append(currentFrame); |
| |
| m_createdFrames.append(currentFrame); |
| m_frameBookmarks[numOfFrames] = frameBookmark; |
| ++numOfFrames; |
| } |
| |
| emit parsed(100); |
| |
| emit framesLoaded(frames); |
| } |
| |
| |
| ApiTraceCallSignature * TraceLoader::signature(unsigned id) |
| { |
| if (id >= m_signatures.count()) { |
| m_signatures.resize(id + 1); |
| return NULL; |
| } else { |
| return m_signatures[id]; |
| } |
| } |
| |
| void TraceLoader::addSignature(unsigned id, ApiTraceCallSignature *signature) |
| { |
| m_signatures[id] = signature; |
| } |
| |
| void TraceLoader::searchNext(const ApiTrace::SearchRequest &request) |
| { |
| Q_ASSERT(m_parser.supportsOffsets()); |
| int startFrame = m_createdFrames.indexOf(request.frame); |
| const FrameBookmark &frameBookmark = m_frameBookmarks[startFrame]; |
| m_parser.setBookmark(frameBookmark.start); |
| trace::Call *call = 0; |
| while ((call = m_parser.parse_call())) { |
| |
| if (callContains(call, request.text, request.cs, request.useRegex)) { |
| unsigned frameIdx = callInFrame(call->no); |
| ApiTraceFrame *frame = m_createdFrames[frameIdx]; |
| const QVector<ApiTraceCall*> calls = |
| fetchFrameContents(frame); |
| for (int i = 0; i < calls.count(); ++i) { |
| if (calls[i]->index() == call->no) { |
| emit searchResult(request, ApiTrace::SearchResult_Found, |
| calls[i]); |
| break; |
| } |
| } |
| delete call; |
| return; |
| } |
| |
| delete call; |
| } |
| emit searchResult(request, ApiTrace::SearchResult_NotFound, 0); |
| } |
| |
| void TraceLoader::searchPrev(const ApiTrace::SearchRequest &request) |
| { |
| Q_ASSERT(m_parser.supportsOffsets()); |
| int startFrame = m_createdFrames.indexOf(request.frame); |
| trace::Call *call = 0; |
| QList<trace::Call*> frameCalls; |
| int frameIdx = startFrame; |
| |
| const FrameBookmark &frameBookmark = m_frameBookmarks[frameIdx]; |
| int numCallsToParse = frameBookmark.numberOfCalls; |
| m_parser.setBookmark(frameBookmark.start); |
| |
| while ((call = m_parser.parse_call())) { |
| |
| frameCalls.append(call); |
| --numCallsToParse; |
| |
| if (numCallsToParse == 0) { |
| bool foundCall = searchCallsBackwards(frameCalls, |
| frameIdx, |
| request); |
| |
| qDeleteAll(frameCalls); |
| frameCalls.clear(); |
| if (foundCall) { |
| return; |
| } |
| |
| --frameIdx; |
| |
| if (frameIdx >= 0) { |
| const FrameBookmark &frameBookmark = |
| m_frameBookmarks[frameIdx]; |
| m_parser.setBookmark(frameBookmark.start); |
| numCallsToParse = frameBookmark.numberOfCalls; |
| } |
| } |
| } |
| emit searchResult(request, ApiTrace::SearchResult_NotFound, 0); |
| } |
| |
| bool TraceLoader::searchCallsBackwards(const QList<trace::Call*> &calls, |
| int frameIdx, |
| const ApiTrace::SearchRequest &request) |
| { |
| for (int i = calls.count() - 1; i >= 0; --i) { |
| trace::Call *call = calls[i]; |
| if (callContains(call, request.text, request.cs, request.useRegex)) { |
| ApiTraceFrame *frame = m_createdFrames[frameIdx]; |
| const QVector<ApiTraceCall*> apiCalls = |
| fetchFrameContents(frame); |
| for (int i = 0; i < apiCalls.count(); ++i) { |
| if (apiCalls[i]->index() == call->no) { |
| emit searchResult(request, |
| ApiTrace::SearchResult_Found, |
| apiCalls[i]); |
| break; |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| int TraceLoader::callInFrame(int callIdx) const |
| { |
| unsigned numCalls = 0; |
| |
| for (int frameIdx = 0; frameIdx < m_frameBookmarks.size(); ++frameIdx) { |
| const FrameBookmark &frameBookmark = m_frameBookmarks[frameIdx]; |
| unsigned firstCall = numCalls; |
| unsigned endCall = numCalls + frameBookmark.numberOfCalls; |
| if (firstCall <= callIdx && endCall > callIdx) { |
| return frameIdx; |
| } |
| numCalls = endCall; |
| } |
| Q_ASSERT(!"call not in the trace"); |
| return 0; |
| } |
| |
| bool TraceLoader::callContains(trace::Call *call, |
| const QString &str, |
| Qt::CaseSensitivity sensitivity, |
| bool useRegex) |
| { |
| /* |
| * FIXME: do string comparison directly on trace::Call |
| */ |
| ApiTraceCall *apiCall = apiCallFromTraceCall(call, m_helpHash, |
| 0, 0, this); |
| bool result = apiCall->contains(str, sensitivity, useRegex); |
| delete apiCall; |
| return result; |
| } |
| |
| QVector<ApiTraceCall*> |
| TraceLoader::fetchFrameContents(ApiTraceFrame *currentFrame) |
| { |
| Q_ASSERT(currentFrame); |
| |
| if (currentFrame->isLoaded()) { |
| return currentFrame->calls(); |
| } |
| |
| unsigned frameIdx = currentFrame->number; |
| int numOfCalls = numberOfCallsInFrame(frameIdx); |
| |
| if (numOfCalls) { |
| const FrameBookmark &frameBookmark = m_frameBookmarks[frameIdx]; |
| |
| m_parser.setBookmark(frameBookmark.start); |
| |
| FrameContents frameCalls(numOfCalls); |
| frameCalls.load(this, currentFrame, m_helpHash, m_parser); |
| if (frameCalls.topLevelCount() == frameCalls.allCallsCount()) { |
| emit frameContentsLoaded(currentFrame, |
| frameCalls.allCalls(), |
| frameCalls.allCalls(), |
| frameCalls.binaryDataSize()); |
| } else { |
| emit frameContentsLoaded(currentFrame, |
| frameCalls.topLevelCalls(), |
| frameCalls.allCalls(), |
| frameCalls.binaryDataSize()); |
| } |
| return frameCalls.allCalls(); |
| } |
| return QVector<ApiTraceCall*>(); |
| } |
| |
| void TraceLoader::findFrameStart(ApiTraceFrame *frame) |
| { |
| if (!frame->isLoaded()) { |
| loadFrame(frame); |
| } |
| emit foundFrameStart(frame); |
| } |
| |
| void TraceLoader::findFrameEnd(ApiTraceFrame *frame) |
| { |
| if (!frame->isLoaded()) { |
| loadFrame(frame); |
| } |
| emit foundFrameEnd(frame); |
| } |
| |
| void TraceLoader::findCallIndex(int index) |
| { |
| int frameIdx = callInFrame(index); |
| ApiTraceFrame *frame = m_createdFrames[frameIdx]; |
| QVector<ApiTraceCall*> calls = fetchFrameContents(frame); |
| QVector<ApiTraceCall*>::const_iterator itr; |
| ApiTraceCall *call = 0; |
| for (itr = calls.constBegin(); itr != calls.constEnd(); ++itr) { |
| if ((*itr)->index() == index) { |
| call = *itr; |
| break; |
| } |
| } |
| if (call) { |
| emit foundCallIndex(call); |
| } |
| } |
| |
| void TraceLoader::search(const ApiTrace::SearchRequest &request) |
| { |
| if (request.direction == ApiTrace::SearchRequest::Next) { |
| searchNext(request); |
| } else { |
| searchPrev(request); |
| } |
| } |
| |
| TraceLoader::FrameContents::FrameContents(int numOfCalls) |
| : m_allCalls(numOfCalls), |
| m_binaryDataSize(0), |
| m_parsedCalls(0) |
| {} |
| |
| |
| void |
| TraceLoader::FrameContents::reset() |
| { |
| m_groups.clear(); |
| m_allCalls.clear(); |
| m_topLevelItems.clear(); |
| m_binaryDataSize = 0; |
| } |
| |
| int |
| TraceLoader::FrameContents::topLevelCount() const |
| { |
| return m_topLevelItems.count(); |
| } |
| |
| int |
| TraceLoader::FrameContents::allCallsCount() const |
| { |
| return m_allCalls.count(); |
| } |
| |
| quint64 |
| TraceLoader::FrameContents::binaryDataSize() const |
| { |
| return m_binaryDataSize; |
| } |
| QVector<ApiTraceCall*> |
| TraceLoader::FrameContents::topLevelCalls() const |
| { |
| return m_topLevelItems; |
| } |
| |
| QVector<ApiTraceCall*> |
| TraceLoader::FrameContents::allCalls() const |
| { |
| return m_allCalls; |
| } |
| |
| bool |
| TraceLoader::FrameContents::isEmpty() |
| { |
| return (m_allCalls.count() == 0); |
| } |
| |
| bool |
| TraceLoader::FrameContents::load(TraceLoader *loader, |
| ApiTraceFrame *currentFrame, |
| QHash<QString, QUrl> helpHash, |
| trace::Parser &parser) |
| { |
| bool bEndFrameReached = false; |
| int initNumOfCalls = m_allCalls.count(); |
| trace::Call *call; |
| ApiTraceCall *apiCall = NULL; |
| |
| while ((call = parser.parse_call())) { |
| |
| apiCall = apiCallFromTraceCall(call, helpHash, currentFrame, |
| m_groups.isEmpty() ? 0 : m_groups.top(), |
| loader); |
| Q_ASSERT(apiCall); |
| if (initNumOfCalls) { |
| Q_ASSERT(m_parsedCalls < m_allCalls.size()); |
| m_allCalls[m_parsedCalls++] = apiCall; |
| } else { |
| m_allCalls.append(apiCall); |
| } |
| if (m_groups.count() == 0) { |
| m_topLevelItems.append(apiCall); |
| } else { |
| m_groups.top()->addChild(apiCall); |
| } |
| if (call->flags & trace::CALL_FLAG_MARKER_PUSH) { |
| m_groups.push(apiCall); |
| } else if (call->flags & trace::CALL_FLAG_MARKER_POP) { |
| if (m_groups.count()) { |
| m_groups.top()->finishedAddingChildren(); |
| m_groups.pop(); |
| } |
| } |
| if (apiCall->hasBinaryData()) { |
| QByteArray data = |
| apiCall->arguments()[apiCall->binaryDataIndex()]. |
| toByteArray(); |
| m_binaryDataSize += data.size(); |
| } |
| |
| delete call; |
| |
| if (apiCall->flags() & trace::CALL_FLAG_END_FRAME) { |
| bEndFrameReached = true; |
| break; |
| } |
| } |
| if (initNumOfCalls) { |
| // There can be fewer parsed calls when call in different |
| // threads cross the frame boundary |
| Q_ASSERT(m_parsedCalls <= initNumOfCalls); |
| Q_ASSERT(m_parsedCalls <= m_allCalls.size()); |
| m_allCalls.resize(m_parsedCalls); |
| Q_ASSERT(m_parsedCalls <= currentFrame->numChildrenToLoad()); |
| } |
| m_allCalls.squeeze(); |
| m_topLevelItems.squeeze(); |
| |
| return bEndFrameReached; |
| } |
| |
| #include "traceloader.moc" |