You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1324 lines
32 KiB
1324 lines
32 KiB
/* This file is part of KCachegrind.
|
|
Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
|
|
|
|
KCachegrind is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation, version 2.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <tqfile.h>
|
|
#include <tqcstring.h>
|
|
|
|
#include <tdelocale.h>
|
|
#include <kdebug.h>
|
|
|
|
#include "loader.h"
|
|
#include "tracedata.h"
|
|
#include "utils.h"
|
|
#include "fixcost.h"
|
|
|
|
|
|
#define TRACE_LOADER 0
|
|
|
|
/*
|
|
* Loader for Callgrind Profile data (format based on Cachegrind format).
|
|
* See Callgrind documentation for the file format.
|
|
*/
|
|
|
|
class CachegrindLoader: public Loader
|
|
{
|
|
public:
|
|
CachegrindLoader();
|
|
|
|
bool canLoadTrace(TQFile* file);
|
|
bool loadTrace(TracePart*);
|
|
bool isPartOfTrace(TQString file, TraceData*);
|
|
|
|
private:
|
|
bool loadTraceInternal(TracePart*);
|
|
|
|
enum lineType { SelfCost, CallCost, BoringJump, CondJump };
|
|
|
|
bool parsePosition(FixString& s, PositionSpec& newPos);
|
|
|
|
// position setters
|
|
void clearPosition();
|
|
void ensureObject();
|
|
void ensureFile();
|
|
void ensureFunction();
|
|
void setObject(const TQString&);
|
|
void setCalledObject(const TQString&);
|
|
void setFile(const TQString&);
|
|
void setCalledFile(const TQString&);
|
|
void setFunction(const TQString&);
|
|
void setCalledFunction(const TQString&);
|
|
|
|
TQString _emptyString;
|
|
|
|
// current line in file to read in
|
|
TQString _filename;
|
|
int _lineNo;
|
|
|
|
TraceSubMapping* subMapping;
|
|
TraceData* _data;
|
|
TracePart* _part;
|
|
|
|
// current position
|
|
lineType nextLineType;
|
|
bool hasLineInfo, hasAddrInfo;
|
|
PositionSpec currentPos;
|
|
|
|
// current function/line
|
|
TraceObject* currentObject;
|
|
TracePartObject* currentPartObject;
|
|
TraceFile* currentFile;
|
|
TracePartFile* currentPartFile;
|
|
TraceFunction* currentFunction;
|
|
TracePartFunction* currentPartFunction;
|
|
TraceFunctionSource* currentFunctionSource;
|
|
TraceInstr* currentInstr;
|
|
TracePartInstr* currentPartInstr;
|
|
TraceLine* currentLine;
|
|
TracePartLine* currentPartLine;
|
|
|
|
// current call
|
|
TraceObject* currentCalledObject;
|
|
TracePartObject* currentCalledPartObject;
|
|
TraceFile* currentCalledFile;
|
|
TracePartFile* currentCalledPartFile;
|
|
TraceFunction* currentCalledFunction;
|
|
TracePartFunction* currentCalledPartFunction;
|
|
SubCost currentCallCount;
|
|
|
|
// current jump
|
|
TraceFile* currentJumpToFile;
|
|
TraceFunction* currentJumpToFunction;
|
|
PositionSpec targetPos;
|
|
SubCost jumpsFollowed, jumpsExecuted;
|
|
|
|
/** Support for compressed string format
|
|
* This uses the following string compression model
|
|
* for objects, files, functions:
|
|
* If the name matches
|
|
* "(<Integer>) Name": this is a compression specification,
|
|
* mapping the integer number to Name and using Name.
|
|
* "(<Integer>)" : this is a compression reference.
|
|
* Assumes previous compression specification of the
|
|
* integer number to a name, uses this name.
|
|
* "Name" : Regular name
|
|
*/
|
|
void clearCompression();
|
|
const TQString& checkUnknown(const TQString& n);
|
|
TraceObject* compressedObject(const TQString& name);
|
|
TraceFile* compressedFile(const TQString& name);
|
|
TraceFunction* compressedFunction(const TQString& name,
|
|
TraceFile*, TraceObject*);
|
|
|
|
TQPtrVector<TraceCostItem> _objectVector, _fileVector, _functionVector;
|
|
};
|
|
|
|
|
|
|
|
/**********************************************************
|
|
* Loader
|
|
*/
|
|
|
|
|
|
CachegrindLoader::CachegrindLoader()
|
|
: Loader("Callgrind",
|
|
i18n( "Import filter for Cachegrind/Callgrind generated profile data files") )
|
|
{
|
|
_emptyString = TQString("");
|
|
}
|
|
|
|
bool CachegrindLoader::canLoadTrace(TQFile* file)
|
|
{
|
|
if (!file) return false;
|
|
|
|
if (!file->isOpen()) {
|
|
if (!file->open( IO_ReadOnly ) ) {
|
|
kdDebug() << TQFile::encodeName(_filename).data() << ": "
|
|
<< strerror( errno ) << endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We recognize this as cachegrind/callgrind format if in the first
|
|
* 2047 bytes we see the string "\nevents:"
|
|
*/
|
|
char buf[2048];
|
|
int read = file->readBlock(buf,2047);
|
|
if (read < 0)
|
|
return false;
|
|
buf[read] = 0;
|
|
|
|
TQCString s;
|
|
s.setRawData(buf, read+1);
|
|
int pos = s.find("events:");
|
|
if (pos>0 && buf[pos-1] != '\n') pos = -1;
|
|
s.resetRawData(buf, read+1);
|
|
return (pos>=0);
|
|
}
|
|
|
|
bool CachegrindLoader::loadTrace(TracePart* p)
|
|
{
|
|
/* do the loading in a new object so parallel load
|
|
* operations do not interfere each other.
|
|
*/
|
|
CachegrindLoader l;
|
|
|
|
/* emit progress signals via the singleton loader */
|
|
connect(&l, TQT_SIGNAL(updateStatus(TQString, int)),
|
|
this, TQT_SIGNAL(updateStatus(TQString, int)));
|
|
|
|
return l.loadTraceInternal(p);
|
|
}
|
|
|
|
Loader* createCachegrindLoader()
|
|
{
|
|
return new CachegrindLoader();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Return false if this is no position specification
|
|
*/
|
|
bool CachegrindLoader::parsePosition(FixString& line,
|
|
PositionSpec& newPos)
|
|
{
|
|
char c;
|
|
uint diff;
|
|
|
|
if (hasAddrInfo) {
|
|
|
|
if (!line.first(c)) return false;
|
|
|
|
if (c == '*') {
|
|
// nothing changed
|
|
line.stripFirst(c);
|
|
newPos.fromAddr = currentPos.fromAddr;
|
|
newPos.toAddr = currentPos.toAddr;
|
|
}
|
|
else if (c == '+') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff, false);
|
|
newPos.fromAddr = currentPos.fromAddr + diff;
|
|
newPos.toAddr = newPos.fromAddr;
|
|
}
|
|
else if (c == '-') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff, false);
|
|
newPos.fromAddr = currentPos.fromAddr - diff;
|
|
newPos.toAddr = newPos.fromAddr;
|
|
}
|
|
else if (c >= '0') {
|
|
uint64 v;
|
|
line.stripUInt64(v, false);
|
|
newPos.fromAddr = Addr(v);
|
|
newPos.toAddr = newPos.fromAddr;
|
|
}
|
|
else return false;
|
|
|
|
// Range specification
|
|
if (line.first(c)) {
|
|
if (c == '+') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff);
|
|
newPos.toAddr = newPos.fromAddr + diff;
|
|
}
|
|
else if ((c == '-') || (c == ':')) {
|
|
line.stripFirst(c);
|
|
uint64 v;
|
|
line.stripUInt64(v);
|
|
newPos.toAddr = Addr(v);
|
|
}
|
|
}
|
|
line.stripSpaces();
|
|
|
|
#if TRACE_LOADER
|
|
if (newPos.fromAddr == newPos.toAddr)
|
|
kdDebug() << " Got Addr " << newPos.fromAddr.toString() << endl;
|
|
else
|
|
kdDebug() << " Got AddrRange " << newPos.fromAddr.toString()
|
|
<< ":" << newPos.toAddr.toString() << endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
if (hasLineInfo) {
|
|
|
|
if (!line.first(c)) return false;
|
|
|
|
if (c > '9') return false;
|
|
else if (c == '*') {
|
|
// nothing changed
|
|
line.stripFirst(c);
|
|
newPos.fromLine = currentPos.fromLine;
|
|
newPos.toLine = currentPos.toLine;
|
|
}
|
|
else if (c == '+') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff, false);
|
|
newPos.fromLine = currentPos.fromLine + diff;
|
|
newPos.toLine = newPos.fromLine;
|
|
}
|
|
else if (c == '-') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff, false);
|
|
if (currentPos.fromLine < diff) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Negative line number "
|
|
<< (int)currentPos.fromLine - (int)diff << endl;
|
|
diff = currentPos.fromLine;
|
|
}
|
|
newPos.fromLine = currentPos.fromLine - diff;
|
|
newPos.toLine = newPos.fromLine;
|
|
}
|
|
else if (c >= '0') {
|
|
line.stripUInt(newPos.fromLine, false);
|
|
newPos.toLine = newPos.fromLine;
|
|
}
|
|
else return false;
|
|
|
|
// Range specification
|
|
if (line.first(c)) {
|
|
if (c == '+') {
|
|
line.stripFirst(c);
|
|
line.stripUInt(diff);
|
|
newPos.toLine = newPos.fromLine + diff;
|
|
}
|
|
else if ((c == '-') || (c == ':')) {
|
|
line.stripFirst(c);
|
|
line.stripUInt(newPos.toLine);
|
|
}
|
|
}
|
|
line.stripSpaces();
|
|
|
|
#if TRACE_LOADER
|
|
if (newPos.fromLine == newPos.toLine)
|
|
kdDebug() << " Got Line " << newPos.fromLine << endl;
|
|
else
|
|
kdDebug() << " Got LineRange " << newPos.fromLine
|
|
<< ":" << newPos.toLine << endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Support for compressed strings
|
|
void CachegrindLoader::clearCompression()
|
|
{
|
|
// this doesn't delete previous contained objects
|
|
_objectVector.clear();
|
|
_fileVector.clear();
|
|
_functionVector.clear();
|
|
|
|
// reset to reasonable init size. We double lengths if needed.
|
|
_objectVector.resize(100);
|
|
_fileVector.resize(1000);
|
|
_functionVector.resize(10000);
|
|
}
|
|
|
|
const TQString& CachegrindLoader::checkUnknown(const TQString& n)
|
|
{
|
|
if (n == "???") return _emptyString;
|
|
return n;
|
|
}
|
|
|
|
TraceObject* CachegrindLoader::compressedObject(const TQString& name)
|
|
{
|
|
if ((name[0] != '(') || !name[1].isDigit()) return _data->object(checkUnknown(name));
|
|
|
|
// compressed format using _objectVector
|
|
int p = name.find(')');
|
|
if (p<2) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid compressed ELF object ('"
|
|
<< name << "')" << endl;
|
|
return 0;
|
|
}
|
|
unsigned index = name.mid(1, p-1).toInt();
|
|
TraceObject* o = 0;
|
|
p++;
|
|
if ((int)name.length()>p) {
|
|
while(name.at(p).isSpace()) p++;
|
|
|
|
if (_objectVector.size() <= index) {
|
|
int newSize = index * 2;
|
|
#if TRACE_LOADER
|
|
kdDebug() << " CachegrindLoader: objectVector enlarged to "
|
|
<< newSize << endl;
|
|
#endif
|
|
_objectVector.resize(newSize);
|
|
}
|
|
|
|
TQString realName = checkUnknown(name.mid(p));
|
|
o = (TraceObject*) _objectVector.at(index);
|
|
if (o && (o->name() != realName)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Redefinition of compressed ELF object index " << index
|
|
<< " (was '" << o->name()
|
|
<< "') to '" << realName << "'" << endl;
|
|
}
|
|
|
|
o = _data->object(realName);
|
|
_objectVector.insert(index, o);
|
|
}
|
|
else {
|
|
if ((_objectVector.size() <= index) ||
|
|
( (o=(TraceObject*)_objectVector.at(index)) == 0)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Undefined compressed ELF object index " << index << endl;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
|
|
// Note: Callgrind sometimes gives different IDs for same file
|
|
// (when references to same source file come from different ELF objects)
|
|
TraceFile* CachegrindLoader::compressedFile(const TQString& name)
|
|
{
|
|
if ((name[0] != '(') || !name[1].isDigit()) return _data->file(checkUnknown(name));
|
|
|
|
// compressed format using _fileVector
|
|
int p = name.find(')');
|
|
if (p<2) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid compressed file ('"
|
|
<< name << "')" << endl;
|
|
return 0;
|
|
}
|
|
unsigned int index = name.mid(1, p-1).toUInt();
|
|
TraceFile* f = 0;
|
|
p++;
|
|
if ((int)name.length()>p) {
|
|
while(name.at(p).isSpace()) p++;
|
|
|
|
if (_fileVector.size() <= index) {
|
|
int newSize = index * 2;
|
|
#if TRACE_LOADER
|
|
kdDebug() << " CachegrindLoader::fileVector enlarged to "
|
|
<< newSize << endl;
|
|
#endif
|
|
_fileVector.resize(newSize);
|
|
}
|
|
|
|
TQString realName = checkUnknown(name.mid(p));
|
|
f = (TraceFile*) _fileVector.at(index);
|
|
if (f && (f->name() != realName)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Redefinition of compressed file index " << index
|
|
<< " (was '" << f->name()
|
|
<< "') to '" << realName << "'" << endl;
|
|
}
|
|
|
|
f = _data->file(realName);
|
|
_fileVector.insert(index, f);
|
|
}
|
|
else {
|
|
if ((_fileVector.size() <= index) ||
|
|
( (f=(TraceFile*)_fileVector.at(index)) == 0)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Undefined compressed file index " << index << endl;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
// Note: Callgrind gives different IDs even for same function
|
|
// when parts of the function are from different source files.
|
|
// Thus, it is no error when multiple indexes map to same function.
|
|
TraceFunction* CachegrindLoader::compressedFunction(const TQString& name,
|
|
TraceFile* file,
|
|
TraceObject* object)
|
|
{
|
|
if ((name[0] != '(') || !name[1].isDigit())
|
|
return _data->function(checkUnknown(name), file, object);
|
|
|
|
// compressed format using _functionVector
|
|
int p = name.find(')');
|
|
if (p<2) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid compressed function ('"
|
|
<< name << "')" << endl;
|
|
return 0;
|
|
}
|
|
|
|
|
|
unsigned int index = name.mid(1, p-1).toUInt();
|
|
TraceFunction* f = 0;
|
|
p++;
|
|
if ((int)name.length()>p) {
|
|
while(name.at(p).isSpace()) p++;
|
|
|
|
if (_functionVector.size() <= index) {
|
|
int newSize = index * 2;
|
|
#if TRACE_LOADER
|
|
kdDebug() << " CachegrindLoader::functionVector enlarged to "
|
|
<< newSize << endl;
|
|
#endif
|
|
_functionVector.resize(newSize);
|
|
}
|
|
|
|
TQString realName = checkUnknown(name.mid(p));
|
|
f = (TraceFunction*) _functionVector.at(index);
|
|
if (f && (f->name() != realName)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Redefinition of compressed function index " << index
|
|
<< " (was '" << f->name()
|
|
<< "') to '" << realName << "'" << endl;
|
|
}
|
|
|
|
f = _data->function(realName, file, object);
|
|
_functionVector.insert(index, f);
|
|
|
|
#if TRACE_LOADER
|
|
kdDebug() << "compressedFunction: Inserted at Index " << index
|
|
<< "\n " << f->fullName()
|
|
<< "\n in " << f->cls()->fullName()
|
|
<< "\n in " << f->file()->fullName()
|
|
<< "\n in " << f->object()->fullName() << endl;
|
|
#endif
|
|
}
|
|
else {
|
|
if ((_functionVector.size() <= index) ||
|
|
( (f=(TraceFunction*)_functionVector.at(index)) == 0)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Undefined compressed function index "
|
|
<< index << endl;
|
|
return 0;
|
|
}
|
|
|
|
// there was a check if the used function (returned from KCachegrinds
|
|
// model) has the same object and file as here given to us, but that was wrong:
|
|
// that holds only if we make this assumption on the model...
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
|
|
// make sure that a valid object is set, at least dummy with empty name
|
|
void CachegrindLoader::ensureObject()
|
|
{
|
|
if (currentObject) return;
|
|
|
|
currentObject = _data->object(_emptyString);
|
|
currentPartObject = currentObject->partObject(_part);
|
|
}
|
|
|
|
void CachegrindLoader::setObject(const TQString& name)
|
|
{
|
|
currentObject = compressedObject(name);
|
|
if (!currentObject) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid object specification, setting to unknown" << endl;
|
|
|
|
currentObject = _data->object(_emptyString);
|
|
}
|
|
|
|
currentPartObject = currentObject->partObject(_part);
|
|
currentFunction = 0;
|
|
currentPartFunction = 0;
|
|
}
|
|
|
|
void CachegrindLoader::setCalledObject(const TQString& name)
|
|
{
|
|
currentCalledObject = compressedObject(name);
|
|
|
|
if (!currentCalledObject) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid called specification, setting to unknown" << endl;
|
|
|
|
currentCalledObject = _data->object(_emptyString);
|
|
}
|
|
|
|
currentCalledPartObject = currentCalledObject->partObject(_part);
|
|
}
|
|
|
|
|
|
// make sure that a valid file is set, at least dummy with empty name
|
|
void CachegrindLoader::ensureFile()
|
|
{
|
|
if (currentFile) return;
|
|
|
|
currentFile = _data->file(_emptyString);
|
|
currentPartFile = currentFile->partFile(_part);
|
|
}
|
|
|
|
void CachegrindLoader::setFile(const TQString& name)
|
|
{
|
|
currentFile = compressedFile(name);
|
|
|
|
if (!currentFile) {
|
|
kdWarning() << _filename << ":" << _lineNo
|
|
<< " - Invalid file specification, setting to unknown" << endl;
|
|
|
|
currentFile = _data->file(_emptyString);
|
|
}
|
|
|
|
currentPartFile = currentFile->partFile(_part);
|
|
currentLine = 0;
|
|
currentPartLine = 0;
|
|
}
|
|
|
|
void CachegrindLoader::setCalledFile(const TQString& name)
|
|
{
|
|
currentCalledFile = compressedFile(name);
|
|
|
|
if (!currentCalledFile) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid called file specification, setting to unknown" << endl;
|
|
|
|
currentCalledFile = _data->file(_emptyString);
|
|
}
|
|
|
|
currentCalledPartFile = currentCalledFile->partFile(_part);
|
|
}
|
|
|
|
// make sure that a valid function is set, at least dummy with empty name
|
|
void CachegrindLoader::ensureFunction()
|
|
{
|
|
if (currentFunction) return;
|
|
|
|
kdWarning() << _filename << ":" << _lineNo
|
|
<< " - Function name not set" << endl;
|
|
|
|
ensureFile();
|
|
ensureObject();
|
|
|
|
currentFunction = _data->function(_emptyString,
|
|
currentFile,
|
|
currentObject);
|
|
currentPartFunction = currentFunction->partFunction(_part,
|
|
currentPartFile,
|
|
currentPartObject);
|
|
}
|
|
|
|
void CachegrindLoader::setFunction(const TQString& name)
|
|
{
|
|
ensureFile();
|
|
ensureObject();
|
|
|
|
currentFunction = compressedFunction( name,
|
|
currentFile,
|
|
currentObject);
|
|
|
|
if (!currentFunction) {
|
|
kdWarning() << _filename << ":" << _lineNo
|
|
<< " - Invalid function, setting to unknown" << endl;
|
|
|
|
currentFunction = _data->function(_emptyString,
|
|
currentFile,
|
|
currentObject);
|
|
}
|
|
|
|
currentPartFunction = currentFunction->partFunction(_part,
|
|
currentPartFile,
|
|
currentPartObject);
|
|
|
|
currentFunctionSource = 0;
|
|
currentLine = 0;
|
|
currentPartLine = 0;
|
|
}
|
|
|
|
void CachegrindLoader::setCalledFunction(const TQString& name)
|
|
{
|
|
// if called object/file not set, use current object/file
|
|
if (!currentCalledObject) {
|
|
currentCalledObject = currentObject;
|
|
currentCalledPartObject = currentPartObject;
|
|
}
|
|
|
|
if (!currentCalledFile) {
|
|
// !=0 as functions needs file
|
|
currentCalledFile = currentFile;
|
|
currentCalledPartFile = currentPartFile;
|
|
}
|
|
|
|
currentCalledFunction = compressedFunction(name,
|
|
currentCalledFile,
|
|
currentCalledObject);
|
|
if (!currentCalledFunction) {
|
|
kdWarning() << _filename << ":" << _lineNo
|
|
<< " - Invalid called function, setting to unknown" << endl;
|
|
|
|
currentCalledFunction = _data->function(_emptyString,
|
|
currentCalledFile,
|
|
currentCalledObject);
|
|
}
|
|
|
|
currentCalledPartFunction =
|
|
currentCalledFunction->partFunction(_part,
|
|
currentCalledPartFile,
|
|
currentCalledPartObject);
|
|
}
|
|
|
|
|
|
void CachegrindLoader::clearPosition()
|
|
{
|
|
currentPos = PositionSpec();
|
|
|
|
// current function/line
|
|
currentFunction = 0;
|
|
currentPartFunction = 0;
|
|
currentFunctionSource = 0;
|
|
currentFile = 0;
|
|
currentPartFile = 0;
|
|
currentObject = 0;
|
|
currentPartObject = 0;
|
|
currentLine = 0;
|
|
currentPartLine = 0;
|
|
currentInstr = 0;
|
|
currentPartInstr = 0;
|
|
|
|
// current call
|
|
currentCalledObject = 0;
|
|
currentCalledPartObject = 0;
|
|
currentCalledFile = 0;
|
|
currentCalledPartFile = 0;
|
|
currentCalledFunction = 0;
|
|
currentCalledPartFunction = 0;
|
|
currentCallCount = 0;
|
|
|
|
// current jump
|
|
currentJumpToFile = 0;
|
|
currentJumpToFunction = 0;
|
|
targetPos = PositionSpec();
|
|
jumpsFollowed = 0;
|
|
jumpsExecuted = 0;
|
|
|
|
subMapping = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* The main import function...
|
|
*/
|
|
bool CachegrindLoader::loadTraceInternal(TracePart* part)
|
|
{
|
|
clearCompression();
|
|
clearPosition();
|
|
|
|
_part = part;
|
|
_data = part->data();
|
|
TQFile* pFile = part->file();
|
|
|
|
if (!pFile) return false;
|
|
|
|
_filename = pFile->name();
|
|
|
|
FixFile file(pFile);
|
|
if (!file.exists()) {
|
|
kdError() << "File doesn't exist\n" << endl;
|
|
return false;
|
|
}
|
|
kdDebug() << "Loading " << _filename << " ..." << endl;
|
|
TQString statusMsg = i18n("Loading %1").arg(_filename);
|
|
int statusProgress = 0;
|
|
emit updateStatus(statusMsg,statusProgress);
|
|
|
|
|
|
#if USE_FIXCOST
|
|
// FixCost Memory Pool
|
|
FixPool* pool = _data->fixPool();
|
|
#endif
|
|
|
|
_lineNo = 0;
|
|
FixString line;
|
|
char c;
|
|
bool totalsSet = false;
|
|
|
|
// current position
|
|
nextLineType = SelfCost;
|
|
// default if there's no "positions:" line
|
|
hasLineInfo = true;
|
|
hasAddrInfo = false;
|
|
|
|
while (file.nextLine(line)) {
|
|
|
|
_lineNo++;
|
|
|
|
#if TRACE_LOADER
|
|
kdDebug() << "[CachegrindLoader] " << _filename << ":" << _lineNo
|
|
<< " - '" << TQString(line) << "'" << endl;
|
|
#endif
|
|
|
|
// if we cannot strip a character, this was an empty line
|
|
if (!line.first(c)) continue;
|
|
|
|
if (c <= '9') {
|
|
|
|
if (c == '#') continue;
|
|
|
|
// parse position(s)
|
|
if (!parsePosition(line, currentPos)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid position specification ('"
|
|
<< TQString(line) << "')" << endl;
|
|
continue;
|
|
}
|
|
|
|
// go through after big switch
|
|
}
|
|
else { // if (c > '9')
|
|
|
|
line.stripFirst(c);
|
|
|
|
/* in order of probability */
|
|
switch(c) {
|
|
|
|
case 'f':
|
|
|
|
// fl=, fi=, fe=
|
|
if (line.stripPrefix("l=") ||
|
|
line.stripPrefix("i=") ||
|
|
line.stripPrefix("e=")) {
|
|
|
|
setFile(line);
|
|
continue;
|
|
}
|
|
|
|
// fn=
|
|
if (line.stripPrefix("n=")) {
|
|
|
|
setFunction(line);
|
|
|
|
// on a new function, update status
|
|
int progress = (int)(100.0 * file.current() / file.len() +.5);
|
|
if (progress != statusProgress) {
|
|
statusProgress = progress;
|
|
|
|
/* When this signal is connected, it most probably
|
|
* should lead to GUI update. Thus, when multiple
|
|
* "long operations" (like file loading) are in progress,
|
|
* this can temporarly switch to another operation.
|
|
*/
|
|
emit updateStatus(statusMsg,statusProgress);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
// cob=
|
|
if (line.stripPrefix("ob=")) {
|
|
setCalledObject(line);
|
|
continue;
|
|
}
|
|
|
|
// cfi= / cfl=
|
|
if (line.stripPrefix("fl=") ||
|
|
line.stripPrefix("fi=")) {
|
|
setCalledFile(line);
|
|
continue;
|
|
}
|
|
|
|
// cfn=
|
|
if (line.stripPrefix("fn=")) {
|
|
|
|
setCalledFunction(line);
|
|
continue;
|
|
}
|
|
|
|
// calls=
|
|
if (line.stripPrefix("alls=")) {
|
|
// ignore long lines...
|
|
line.stripUInt64(currentCallCount);
|
|
nextLineType = CallCost;
|
|
continue;
|
|
}
|
|
|
|
// cmd:
|
|
if (line.stripPrefix("md:")) {
|
|
TQString command = TQString(line).stripWhiteSpace();
|
|
if (!_data->command().isEmpty() &&
|
|
_data->command() != command) {
|
|
|
|
kdWarning() << _filename << ":" << _lineNo
|
|
<< " - Redefined command, was '"
|
|
<< _data->command()
|
|
<< "'" << endl;
|
|
}
|
|
_data->setCommand(command);
|
|
continue;
|
|
}
|
|
|
|
// creator:
|
|
if (line.stripPrefix("reator:")) {
|
|
// ignore ...
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'j':
|
|
|
|
// jcnd=
|
|
if (line.stripPrefix("cnd=")) {
|
|
bool valid;
|
|
|
|
valid = line.stripUInt64(jumpsFollowed) &&
|
|
line.stripPrefix("/") &&
|
|
line.stripUInt64(jumpsExecuted) &&
|
|
parsePosition(line, targetPos);
|
|
|
|
if (!valid) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid jcnd line" << endl;
|
|
}
|
|
else
|
|
nextLineType = CondJump;
|
|
continue;
|
|
}
|
|
|
|
if (line.stripPrefix("ump=")) {
|
|
bool valid;
|
|
|
|
valid = line.stripUInt64(jumpsExecuted) &&
|
|
parsePosition(line, targetPos);
|
|
|
|
if (!valid) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid jump line" << endl;
|
|
}
|
|
else
|
|
nextLineType = BoringJump;
|
|
continue;
|
|
}
|
|
|
|
// jfi=
|
|
if (line.stripPrefix("fi=")) {
|
|
currentJumpToFile = compressedFile(line);
|
|
continue;
|
|
}
|
|
|
|
// jfn=
|
|
if (line.stripPrefix("fn=")) {
|
|
|
|
if (!currentJumpToFile) {
|
|
// !=0 as functions needs file
|
|
currentJumpToFile = currentFile;
|
|
}
|
|
|
|
currentJumpToFunction =
|
|
compressedFunction(line,
|
|
currentJumpToFile,
|
|
currentObject);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
// ob=
|
|
if (line.stripPrefix("b=")) {
|
|
setObject(line);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case '#':
|
|
continue;
|
|
|
|
case 't':
|
|
|
|
// totals:
|
|
if (line.stripPrefix("otals:")) continue;
|
|
|
|
// thread:
|
|
if (line.stripPrefix("hread:")) {
|
|
part->setThreadID(TQString(line).toInt());
|
|
continue;
|
|
}
|
|
|
|
// timeframe (BB):
|
|
if (line.stripPrefix("imeframe (BB):")) {
|
|
part->setTimeframe(line);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
// desc:
|
|
if (line.stripPrefix("esc:")) {
|
|
|
|
line.stripSurroundingSpaces();
|
|
|
|
// desc: Trigger:
|
|
if (line.stripPrefix("Trigger:")) {
|
|
part->setTrigger(line);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'e':
|
|
|
|
// events:
|
|
if (line.stripPrefix("vents:")) {
|
|
subMapping = _data->mapping()->subMapping(line);
|
|
part->setFixSubMapping(subMapping);
|
|
continue;
|
|
}
|
|
|
|
// event:<name>[=<formula>][:<long name>]
|
|
if (line.stripPrefix("vent:")) {
|
|
line.stripSurroundingSpaces();
|
|
|
|
FixString e, f, l;
|
|
if (!line.stripName(e)) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid event" << endl;
|
|
continue;
|
|
}
|
|
line.stripSpaces();
|
|
if (!line.stripFirst(c)) continue;
|
|
|
|
if (c=='=') f = line.stripUntil(':');
|
|
line.stripSpaces();
|
|
|
|
// add to known cost types
|
|
if (line.isEmpty()) line = e;
|
|
TraceCostType::add(new TraceCostType(e,line,f));
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
// part:
|
|
if (line.stripPrefix("art:")) {
|
|
part->setPartNumber(TQString(line).toInt());
|
|
continue;
|
|
}
|
|
|
|
// pid:
|
|
if (line.stripPrefix("id:")) {
|
|
part->setProcessID(TQString(line).toInt());
|
|
continue;
|
|
}
|
|
|
|
// positions:
|
|
if (line.stripPrefix("ositions:")) {
|
|
TQString positions(line);
|
|
hasLineInfo = (positions.find("line")>=0);
|
|
hasAddrInfo = (positions.find("instr")>=0);
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
// version:
|
|
if (line.stripPrefix("ersion:")) {
|
|
part->setVersion(line);
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
|
|
// summary:
|
|
if (line.stripPrefix("ummary:")) {
|
|
if (!subMapping) {
|
|
kdError() << "No event line found. Skipping '" << _filename << endl;
|
|
return false;
|
|
}
|
|
|
|
part->totals()->set(subMapping, line);
|
|
continue;
|
|
}
|
|
|
|
case 'r':
|
|
|
|
// rcalls= (deprecated)
|
|
if (line.stripPrefix("calls=")) {
|
|
// handle like normal calls: we need the sum of call count
|
|
// recursive cost is discarded in cycle detection
|
|
line.stripUInt64(currentCallCount);
|
|
nextLineType = CallCost;
|
|
|
|
kdDebug() << "WARNING: This trace dump was generated by an old "
|
|
"version\n of the call-tree skin. Use a new one!" << endl;
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid line '" << c << TQString(line) << "'" << endl;
|
|
continue;
|
|
}
|
|
|
|
if (!subMapping) {
|
|
kdError() << "No event line found. Skipping '" << _filename << "'" << endl;
|
|
return false;
|
|
}
|
|
|
|
// for a cost line, we always need a current function
|
|
ensureFunction();
|
|
|
|
|
|
#if USE_FIXCOST
|
|
if (!currentFunctionSource ||
|
|
(currentFunctionSource->file() != currentFile))
|
|
currentFunctionSource = currentFunction->sourceFile(currentFile,
|
|
true);
|
|
#else
|
|
if (hasAddrInfo) {
|
|
if (!currentInstr ||
|
|
(currentInstr->addr() != currentPos.fromAddr)) {
|
|
currentInstr = currentFunction->instr(currentPos.fromAddr,
|
|
true);
|
|
|
|
if (!currentInstr) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Invalid address "
|
|
<< currentPos.fromAddr.toString() << endl;
|
|
|
|
continue;
|
|
}
|
|
|
|
currentPartInstr = currentInstr->partInstr(part,
|
|
currentPartFunction);
|
|
}
|
|
}
|
|
|
|
if (hasLineInfo) {
|
|
if (!currentLine ||
|
|
(currentLine->lineno() != currentPos.fromLine)) {
|
|
|
|
currentLine = currentFunction->line(currentFile,
|
|
currentPos.fromLine,
|
|
true);
|
|
currentPartLine = currentLine->partLine(part,
|
|
currentPartFunction);
|
|
}
|
|
if (hasAddrInfo && currentInstr)
|
|
currentInstr->setLine(currentLine);
|
|
}
|
|
#endif
|
|
|
|
#if TRACE_LOADER
|
|
kdDebug() << _filename << ":" << _lineNo
|
|
<< endl << " currentInstr "
|
|
<< (currentInstr ? currentInstr->toString().ascii() : ".")
|
|
<< endl << " currentLine "
|
|
<< (currentLine ? currentLine->toString().ascii() : ".")
|
|
<< "( file " << currentFile->name() << ")"
|
|
<< endl << " currentFunction "
|
|
<< currentFunction->prettyName().ascii()
|
|
<< endl << " currentCalled "
|
|
<< (currentCalledFunction ? currentCalledFunction->prettyName().ascii() : ".")
|
|
<< endl;
|
|
#endif
|
|
|
|
// create cost item
|
|
|
|
if (nextLineType == SelfCost) {
|
|
|
|
#if USE_FIXCOST
|
|
new (pool) FixCost(part, pool,
|
|
currentFunctionSource,
|
|
currentPos,
|
|
currentPartFunction,
|
|
line);
|
|
#else
|
|
if (hasAddrInfo) {
|
|
TracePartInstr* partInstr;
|
|
partInstr = currentInstr->partInstr(part, currentPartFunction);
|
|
|
|
if (hasLineInfo) {
|
|
// we need to set <line> back after reading for the line
|
|
int l = line.len();
|
|
const char* s = line.ascii();
|
|
|
|
partInstr->addCost(subMapping, line);
|
|
line.set(s,l);
|
|
}
|
|
else
|
|
partInstr->addCost(subMapping, line);
|
|
}
|
|
|
|
if (hasLineInfo) {
|
|
TracePartLine* partLine;
|
|
partLine = currentLine->partLine(part, currentPartFunction);
|
|
partLine->addCost(subMapping, line);
|
|
}
|
|
#endif
|
|
|
|
if (!line.isEmpty()) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Garbage at end of cost line ('"
|
|
<< TQString(line) << "')" << endl;
|
|
}
|
|
}
|
|
else if (nextLineType == CallCost) {
|
|
nextLineType = SelfCost;
|
|
|
|
TraceCall* calling = currentFunction->calling(currentCalledFunction);
|
|
TracePartCall* partCalling =
|
|
calling->partCall(part, currentPartFunction,
|
|
currentCalledPartFunction);
|
|
|
|
#if USE_FIXCOST
|
|
FixCallCost* fcc;
|
|
fcc = new (pool) FixCallCost(part, pool,
|
|
currentFunctionSource,
|
|
hasLineInfo ? currentPos.fromLine : 0,
|
|
hasAddrInfo ? currentPos.fromAddr : Addr(0),
|
|
partCalling,
|
|
currentCallCount, line);
|
|
fcc->setMax(_data->callMax());
|
|
#else
|
|
if (hasAddrInfo) {
|
|
TraceInstrCall* instrCall;
|
|
TracePartInstrCall* partInstrCall;
|
|
|
|
instrCall = calling->instrCall(currentInstr);
|
|
partInstrCall = instrCall->partInstrCall(part, partCalling);
|
|
partInstrCall->addCallCount(currentCallCount);
|
|
|
|
if (hasLineInfo) {
|
|
// we need to set <line> back after reading for the line
|
|
int l = line.len();
|
|
const char* s = line.ascii();
|
|
|
|
partInstrCall->addCost(subMapping, line);
|
|
line.set(s,l);
|
|
}
|
|
else
|
|
partInstrCall->addCost(subMapping, line);
|
|
|
|
// update maximum of call cost
|
|
_data->callMax()->maxCost(partInstrCall);
|
|
}
|
|
|
|
if (hasLineInfo) {
|
|
TraceLineCall* lineCall;
|
|
TracePartLineCall* partLineCall;
|
|
|
|
lineCall = calling->lineCall(currentLine);
|
|
partLineCall = lineCall->partLineCall(part, partCalling);
|
|
|
|
partLineCall->addCallCount(currentCallCount);
|
|
partLineCall->addCost(subMapping, line);
|
|
|
|
// update maximum of call cost
|
|
_data->callMax()->maxCost(partLineCall);
|
|
}
|
|
#endif
|
|
currentCalledFile = 0;
|
|
currentCalledPartFile = 0;
|
|
currentCalledObject = 0;
|
|
currentCalledPartObject = 0;
|
|
currentCallCount = 0;
|
|
|
|
if (!line.isEmpty()) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Garbage at end of call cost line ('"
|
|
<< TQString(line) << "')" << endl;
|
|
}
|
|
}
|
|
else { // (nextLineType == BoringJump || nextLineType == CondJump)
|
|
|
|
TraceFunctionSource* targetSource;
|
|
|
|
if (!currentJumpToFunction)
|
|
currentJumpToFunction = currentFunction;
|
|
|
|
targetSource = (currentJumpToFile) ?
|
|
currentJumpToFunction->sourceFile(currentJumpToFile, true) :
|
|
currentFunctionSource;
|
|
|
|
#if USE_FIXCOST
|
|
new (pool) FixJump(part, pool,
|
|
/* source */
|
|
hasLineInfo ? currentPos.fromLine : 0,
|
|
hasAddrInfo ? currentPos.fromAddr : 0,
|
|
currentPartFunction,
|
|
currentFunctionSource,
|
|
/* target */
|
|
hasLineInfo ? targetPos.fromLine : 0,
|
|
hasAddrInfo ? targetPos.fromAddr : Addr(0),
|
|
currentJumpToFunction,
|
|
targetSource,
|
|
(nextLineType == CondJump),
|
|
jumpsExecuted, jumpsFollowed);
|
|
#endif
|
|
|
|
if (0) {
|
|
kdDebug() << _filename << ":" << _lineNo
|
|
<< " - jump from 0x" << currentPos.fromAddr.toString()
|
|
<< " (line " << currentPos.fromLine
|
|
<< ") to 0x" << targetPos.fromAddr.toString()
|
|
<< " (line " << targetPos.fromLine << ")" << endl;
|
|
|
|
if (nextLineType == BoringJump)
|
|
kdDebug() << " Boring Jump, count " << jumpsExecuted.pretty() << endl;
|
|
else
|
|
kdDebug() << " Cond. Jump, followed " << jumpsFollowed.pretty()
|
|
<< ", executed " << jumpsExecuted.pretty() << endl;
|
|
}
|
|
|
|
nextLineType = SelfCost;
|
|
currentJumpToFunction = 0;
|
|
currentJumpToFile = 0;
|
|
|
|
if (!line.isEmpty()) {
|
|
kdError() << _filename << ":" << _lineNo
|
|
<< " - Garbage at end of jump cost line ('"
|
|
<< TQString(line) << "')" << endl;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
emit updateStatus(statusMsg,100);
|
|
|
|
_part->invalidate();
|
|
if (!totalsSet) {
|
|
_part->totals()->clear();
|
|
_part->totals()->addCost(_part);
|
|
}
|
|
|
|
pFile->close();
|
|
|
|
return true;
|
|
}
|
|
|