From 9aabd0ab3aa05f19544617bd185564e6c30050aa Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Tue, 30 Sep 2008 22:17:22 +0000 Subject: [PATCH] Added option "-v ". The tool will generate output files in valgrind's callgrind format in the given directory. Those can be analyzed with graphical tools like KCachegrind. Recursive functions are probably not handled correctly yet. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@27804 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../profile/CallgrindThreadProfileResult.cpp | 277 ++++++++++++++++++ .../profile/CallgrindThreadProfileResult.h | 89 ++++++ src/bin/debug/profile/Jamfile | 1 + src/bin/debug/profile/Options.h | 2 + src/bin/debug/profile/profile.cpp | 14 +- 5 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 src/bin/debug/profile/CallgrindThreadProfileResult.cpp create mode 100644 src/bin/debug/profile/CallgrindThreadProfileResult.h diff --git a/src/bin/debug/profile/CallgrindThreadProfileResult.cpp b/src/bin/debug/profile/CallgrindThreadProfileResult.cpp new file mode 100644 index 0000000000..0b4e72feec --- /dev/null +++ b/src/bin/debug/profile/CallgrindThreadProfileResult.cpp @@ -0,0 +1,277 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "CallgrindThreadProfileResult.h" + +#include +#include + +#include +#include + +#include "Options.h" + + +// #pragma mark - CallgrindThreadImage + + +CallgrindThreadImage::CallgrindThreadImage(Image* image) + : + ThreadImage(image), + fFunctions(NULL), + fOutputIndex(0) +{ +} + + +CallgrindThreadImage::~CallgrindThreadImage() +{ + int32 symbolCount = fImage->SymbolCount(); + for (int32 i = 0; i < symbolCount; i++) { + while (CallgrindCalledFunction* calledFunction + = fFunctions[i].calledFunctions) { + fFunctions[i].calledFunctions = calledFunction->next; + delete calledFunction; + } + } + + delete[] fFunctions; +} + + +status_t +CallgrindThreadImage::Init() +{ + int32 symbolCount = fImage->SymbolCount(); + fFunctions = new(std::nothrow) CallgrindFunction[symbolCount]; + if (fFunctions == NULL) + return B_NO_MEMORY; + + memset(fFunctions, 0, sizeof(CallgrindFunction) * symbolCount); + + return B_OK; +} + + +void +CallgrindThreadImage::AddSymbolHit(int32 symbolIndex, + CallgrindThreadImage* calledImage, int32 calledSymbol) + +{ + fTotalHits++; + + CallgrindFunction& function = fFunctions[symbolIndex]; + if (calledImage != NULL) { + // check whether the called function is known already + CallgrindCalledFunction* calledFunction = function.calledFunctions; + while (calledFunction != NULL) { + if (calledFunction->image == calledImage + && calledFunction->function == calledSymbol) { + break; + } + calledFunction = calledFunction->next; + } + + // create a new CallgrindCalledFunction object, if not known + if (calledFunction == NULL) { + calledFunction = new(std::nothrow) CallgrindCalledFunction( + calledImage, calledSymbol); + if (calledFunction == NULL) + return; + + calledFunction->next = function.calledFunctions; + function.calledFunctions = calledFunction; + } + + calledFunction->hits++; + } else + function.hits++; +} + + +CallgrindFunction* +CallgrindThreadImage::Functions() const +{ + return fFunctions; +} + + +int32 +CallgrindThreadImage::OutputIndex() const +{ + return fOutputIndex; +} + + +void +CallgrindThreadImage::SetOutputIndex(int32 index) +{ + fOutputIndex = index; +} + + +// #pragma mark - CallgrindThreadProfileResult + + +CallgrindThreadProfileResult::CallgrindThreadProfileResult() + : + fTotalTicks(0), + fUnkownTicks(0), + fDroppedTicks(0), + fNextImageOutputIndex(1), + fNextFunctionOutputIndex(1) +{ +} + + +void +CallgrindThreadProfileResult::AddSamples(addr_t* samples, int32 sampleCount) +{ + int32 unknownSamples = 0; + CallgrindThreadImage* previousImage = NULL; + int32 previousSymbol = -1; + + // TODO: That probably doesn't work with recursive functions. + for (int32 i = 0; i < sampleCount; i++) { + addr_t address = samples[i]; + CallgrindThreadImage* image = FindImage(address); + int32 symbol = -1; + if (image != NULL) { + symbol = image->GetImage()->FindSymbol(address); + if (symbol >= 0) { + image->AddSymbolHit(symbol, previousImage, previousSymbol); + previousImage = image; + previousSymbol = symbol; + } + } else + unknownSamples++; + } + + if (unknownSamples == sampleCount) + fUnkownTicks++; + + fTotalTicks++; +} + + +void +CallgrindThreadProfileResult::AddDroppedTicks(int32 dropped) +{ + fDroppedTicks += dropped; +} + + +void +CallgrindThreadProfileResult::PrintResults() +{ + // create output file + mkdir(gOptions.callgrind_directory, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + char fileName[B_PATH_NAME_LENGTH]; + snprintf(fileName, sizeof(fileName), "%s/callgrind.out.%ld", + gOptions.callgrind_directory, fThread->ID()); + + FILE* out = fopen(fileName, "w+"); + if (out == NULL) { + fprintf(stderr, "%s: Failed to open output file \"%s\": %s\n", + kCommandName, fileName, strerror(errno)); + return; + } + + // write the header + fprintf(out, "version: 1\n"); + fprintf(out, "creator: Haiku profile\n"); + fprintf(out, "pid: %ld\n", fThread->ID()); + fprintf(out, "cmd: %s\n", fThread->Name()); + fprintf(out, "part: 1\n\n"); + + fprintf(out, "positions: line\n"); + fprintf(out, "events: Ticks Time\n"); + fprintf(out, "summary: %lld\n", fTotalTicks); + + // get hit images + CallgrindThreadImage* images[fOldImages.Size() + fImages.Size()]; + int32 imageCount = GetHitImages(images); + + for (int32 i = 0; i < imageCount; i++) { + CallgrindThreadImage* image = images[i]; + + CallgrindFunction* functions = image->Functions(); + int32 imageSymbolCount = image->GetImage()->SymbolCount(); + for (int32 k = 0; k < imageSymbolCount; k++) { + CallgrindFunction& function = functions[k]; + if (function.hits == 0 && function.calledFunctions == NULL) + continue; + + fprintf(out, "\n"); + _PrintFunction(out, image, k, false); + fprintf(out, "0 %lld %lld\n", function.hits, + function.hits * fInterval); + + CallgrindCalledFunction* calledFunction = function.calledFunctions; + while (calledFunction != NULL) { + _PrintFunction(out, calledFunction->image, + calledFunction->function, true); + fprintf(out, "calls=%lld 0\n", calledFunction->hits); + fprintf(out, "0 %lld %lld\n", calledFunction->hits, + calledFunction->hits * fInterval); + calledFunction = calledFunction->next; + } + } + } + + // print pseudo-functions for unknown and dropped ticks + if (fUnkownTicks + fDroppedTicks > 0) { + fprintf(out, "\nob=\n"); + + if (fUnkownTicks > 0) { + fprintf(out, "\nfn=unknown\n"); + fprintf(out, "0 %lld\n", fUnkownTicks); + } + + if (fDroppedTicks > 0) { + fprintf(out, "\nfn=dropped\n"); + fprintf(out, "0 %lld\n", fDroppedTicks); + } + } + + fclose(out); +} + + +CallgrindThreadImage* +CallgrindThreadProfileResult::CreateThreadImage(Image* image) +{ + return new(std::nothrow) CallgrindThreadImage(image); +} + + +void +CallgrindThreadProfileResult::_PrintFunction(FILE* out, + CallgrindThreadImage* image, int32 functionIndex, bool called) +{ + if (image->OutputIndex() == 0) { + // need to print the image name + int32 index = fNextImageOutputIndex++; + image->SetOutputIndex(index); + fprintf(out, "%sob=(%ld) %s:%ld\n", called ? "c" : "", index, + image->GetImage()->Info().name, image->ID()); + } else { + // image is already known + // TODO: We may not need to print it at all! + fprintf(out, "%sob=(%ld)\n", called ? "c" : "", image->OutputIndex()); + } + + CallgrindFunction& function = image->Functions()[functionIndex]; + if (function.outputIndex == 0) { + // need to print the function name + function.outputIndex = fNextFunctionOutputIndex++; + fprintf(out, "%sfn=(%ld) %s\n", called ? "c" : "", function.outputIndex, + image->GetImage()->Symbols()[functionIndex]->Name()); + } else { + // function is already known + fprintf(out, "%sfn=(%ld)\n", called ? "c" : "", function.outputIndex); + } +} diff --git a/src/bin/debug/profile/CallgrindThreadProfileResult.h b/src/bin/debug/profile/CallgrindThreadProfileResult.h new file mode 100644 index 0000000000..74f28cbc0c --- /dev/null +++ b/src/bin/debug/profile/CallgrindThreadProfileResult.h @@ -0,0 +1,89 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef CALLGRIND_THREAD_PROFILE_RESULT_H +#define CALLGRIND_THREAD_PROFILE_RESULT_H + +#include + +#include "Thread.h" + + +class CallgrindThreadImage; + + +struct CallgrindCalledFunction { + CallgrindCalledFunction* next; + CallgrindThreadImage* image; + int32 function; + int64 hits; + + CallgrindCalledFunction(CallgrindThreadImage* image, int32 function) + : + next(NULL), + image(image), + function(function), + hits(0) + { + } +}; + + +struct CallgrindFunction { + int64 hits; + CallgrindCalledFunction* calledFunctions; + int32 outputIndex; + // index when generating the output file +}; + + +class CallgrindThreadImage : public ThreadImage, + public DoublyLinkedListLinkImpl { +public: + CallgrindThreadImage(Image* image); + virtual ~CallgrindThreadImage(); + + virtual status_t Init(); + + inline void AddSymbolHit(int32 symbolIndex, + CallgrindThreadImage* calledImage, + int32 calledSymbol); + + inline CallgrindFunction* Functions() const; + + inline int32 OutputIndex() const; + inline void SetOutputIndex(int32 index); + +private: + CallgrindFunction* fFunctions; + int32 fOutputIndex; +}; + + +class CallgrindThreadProfileResult + : public AbstractThreadProfileResult { +public: + CallgrindThreadProfileResult(); + + virtual void AddSamples(addr_t* samples, + int32 sampleCount); + virtual void AddDroppedTicks(int32 dropped); + virtual void PrintResults(); + + virtual CallgrindThreadImage* CreateThreadImage(Image* image); + +private: + void _PrintFunction(FILE* out, + CallgrindThreadImage* image, + int32 functionIndex, bool called); +private: + int64 fTotalTicks; + int64 fUnkownTicks; + int64 fDroppedTicks; + int32 fNextImageOutputIndex; + int32 fNextFunctionOutputIndex; +}; + + +#endif // CALLGRIND_THREAD_PROFILE_RESULT_H diff --git a/src/bin/debug/profile/Jamfile b/src/bin/debug/profile/Jamfile index fdf2d5b017..c5bb6f1aec 100644 --- a/src/bin/debug/profile/Jamfile +++ b/src/bin/debug/profile/Jamfile @@ -11,6 +11,7 @@ SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) ] ; BinCommand profile : BasicThreadProfileResult.cpp + CallgrindThreadProfileResult.cpp Image.cpp Team.cpp Thread.cpp diff --git a/src/bin/debug/profile/Options.h b/src/bin/debug/profile/Options.h index 743c26c983..db2dcc9dae 100644 --- a/src/bin/debug/profile/Options.h +++ b/src/bin/debug/profile/Options.h @@ -16,6 +16,7 @@ struct Options { interval(1000), stack_depth(5), output(NULL), + callgrind_directory(NULL), profile_kernel(true), profile_loading(false), profile_teams(true), @@ -27,6 +28,7 @@ struct Options { bigtime_t interval; int32 stack_depth; FILE* output; + const char* callgrind_directory; bool profile_kernel; bool profile_loading; bool profile_teams; diff --git a/src/bin/debug/profile/profile.cpp b/src/bin/debug/profile/profile.cpp index c5bc344dc2..f56d30425f 100644 --- a/src/bin/debug/profile/profile.cpp +++ b/src/bin/debug/profile/profile.cpp @@ -24,6 +24,7 @@ #include #include "BasicThreadProfileResult.h" +#include "CallgrindThreadProfileResult.h" #include "debug_utils.h" #include "Image.h" #include "Options.h" @@ -67,6 +68,8 @@ static const char* kUsage = " caller stack per tick. If the topmost address doesn't\n" " hit a known image, the next address will be matched\n" " (and so on).\n" + " -v - Create valgrind/callgrind output. is the\n" + " directory where to put the output files.\n" ; @@ -176,7 +179,9 @@ private: status_t _CreateThreadProfileResult(Thread* thread) { ThreadProfileResult* profileResult; - if (gOptions.analyze_full_stack) + if (gOptions.callgrind_directory != NULL) + profileResult = new(std::nothrow) CallgrindThreadProfileResult; + else if (gOptions.analyze_full_stack) profileResult = new(std::nothrow) InclusiveThreadProfileResult; else profileResult = new(std::nothrow) ExclusiveThreadProfileResult; @@ -239,7 +244,7 @@ main(int argc, const char* const* argv) }; opterr = 0; // don't print errors - int c = getopt_long(argc, (char**)argv, "+cCfhi:klo:s:", sLongOptions, + int c = getopt_long(argc, (char**)argv, "+cCfhi:klo:s:v:", sLongOptions, NULL); if (c == -1) break; @@ -273,6 +278,11 @@ main(int argc, const char* const* argv) case 's': stackDepth = atol(optarg); break; + case 'v': + gOptions.callgrind_directory = optarg; + gOptions.analyze_full_stack = true; + gOptions.stack_depth = 64; + break; default: print_usage_and_exit(true); break;