sl@0: // Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of the License "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // sl@0: sl@0: #include "analyse.h" sl@0: #include "trace.h" sl@0: #include "tracer.h" sl@0: #include "distribution.h" sl@0: #include "activity.h" sl@0: #include "nonxip.h" sl@0: sl@0: #ifdef __MSVCDOTNET__ sl@0: #include sl@0: #include sl@0: #else //!__MSVCDOTNET__ sl@0: #include sl@0: #include sl@0: #endif //__MSVCDOTNET__ sl@0: sl@0: #include sl@0: sl@0: Analyse::TAction Analyse::sAction; sl@0: Analyse::TFormat Analyse::sFormat; sl@0: Analyse::TPartition Analyse::sPartition; sl@0: int Analyse::sOptions; sl@0: std::vector Analyse::sTraces; sl@0: const char* Analyse::sRomFile; sl@0: const char* Analyse::sThread; sl@0: const char* Analyse::sDll; sl@0: const char* Analyse::sFunction; sl@0: unsigned Analyse::sBase; sl@0: unsigned Analyse::sLim; sl@0: unsigned Analyse::sBuckets = 100; sl@0: unsigned Analyse::sBucketSize; sl@0: double Analyse::sCutOff; sl@0: unsigned Analyse::sBeginSample; sl@0: unsigned Analyse::sEndSample = 0xffffffffu; sl@0: sl@0: sl@0: NonXIP gNonXIP; // sl@0: sl@0: //namespace { sl@0: sl@0: void PartitionByDll::File(const char* aName) sl@0: { sl@0: iCurrentFile = aName; sl@0: } sl@0: sl@0: bool PartitionByDll::Symbol(const char*, PC aPc, int) sl@0: { sl@0: bool is_added = false; sl@0: if (iCurrentFile) sl@0: { sl@0: if (iLastFile && Analyse::Match(iLastFile, iMatch)) sl@0: { sl@0: Add(iLastFileAddress, aPc, iLastFile); sl@0: is_added = true; sl@0: } sl@0: iLastFile = iCurrentFile; sl@0: iLastFileAddress = aPc; sl@0: iCurrentFile = 0; sl@0: } sl@0: return is_added; sl@0: } sl@0: sl@0: sl@0: PartitionByFunction::PartitionByFunction(const char* aFile, const char* aFunction) sl@0: :iFile(aFile), iFunction(aFunction), iActive(false) sl@0: {} sl@0: sl@0: void PartitionByFunction::File(const char* aName) sl@0: { sl@0: iActive = Analyse::Match(aName, iFile); sl@0: } sl@0: sl@0: bool PartitionByFunction::Symbol(const char* aSymbol, PC aPc, int aLength) sl@0: { sl@0: bool is_added = false; sl@0: if (iActive && Analyse::Match(aSymbol, iFunction)) sl@0: { sl@0: Add(aPc, aPc + aLength, aSymbol); sl@0: is_added = true; sl@0: } sl@0: return is_added; sl@0: } sl@0: sl@0: sl@0: sl@0: class FindFunction : public SymbolFile::Parser sl@0: { sl@0: public: sl@0: FindFunction(const char* aFile, const char* aFunction); sl@0: void File(const char* aName); sl@0: bool Symbol(const char* aName, PC aPc, int aLength); sl@0: void Done(PC aFirstPc=0, PC aLastPc=0, int aModuleId=0); sl@0: private: sl@0: const char* iFile; sl@0: const char* iFunction; sl@0: bool iActive; sl@0: public: sl@0: PC iPc; sl@0: int iLength; sl@0: }; sl@0: sl@0: FindFunction::FindFunction(const char* aFile, const char* aFunction) sl@0: :iFile(aFile), iFunction(aFunction), iActive(false), iPc(0) sl@0: {} sl@0: sl@0: void FindFunction::File(const char* aName) sl@0: { sl@0: if (iPc == 0) sl@0: iActive = Analyse::Match(aName, iFile); sl@0: } sl@0: sl@0: bool FindFunction::Symbol(const char* aSymbol, PC aPc, int aLength) sl@0: { sl@0: bool is_added = false; sl@0: if (iPc == 0 && iActive && Analyse::Match(aSymbol, iFunction)) sl@0: { sl@0: iPc = aPc; sl@0: iLength = aLength; sl@0: is_added = true; sl@0: } sl@0: return is_added; sl@0: } sl@0: sl@0: void FindFunction::Done(PC aFirstPc, PC aLastPc, int aModuleId) sl@0: {} sl@0: sl@0: //}; // local namepsace sl@0: sl@0: sl@0: // entry point sl@0: sl@0: int main(int argc,char *argv[]) sl@0: { sl@0: switch(Analyse::ProcessCommandLine(argc,argv)) sl@0: { sl@0: case 1: sl@0: Analyse::ExplainUsage(); sl@0: return 1; sl@0: case 2: sl@0: Analyse::ExplainConfigUsage(); sl@0: return 1; sl@0: } sl@0: Analyse::Run(); sl@0: return 0; sl@0: } sl@0: sl@0: // Class Analyse sl@0: sl@0: void Analyse::Information() sl@0: { sl@0: cout << "\nEPOC Profile Analyser Version " << MajorVersion << '.' \ sl@0: << setw(2) << MinorVersion << "(build " << setw(3) << setfill('0') << Build \ sl@0: << ")\nCopyright (c) Symbian Limited 2000. All rights reserved.\n\n" << flush; sl@0: } sl@0: sl@0: void Analyse::ExplainUsage() sl@0: { sl@0: Information(); sl@0: cout << "Usage: Analyse [options] tracefile\n" \ sl@0: " -h display this information\n" \ sl@0: " -l generate a trace listing\n" \ sl@0: " -p generate a profile distribution\n" \ sl@0: " -v generate a activity trace\n" \ sl@0: " -r supply a Rom symbol file\n" \ sl@0: " -s restrict the profile to the samples specified\n" \ sl@0: " This is specified either as - or\n" \ sl@0: " as + in decimal\n" \ sl@0: " -n include NULL thread\n" \ sl@0: " -t profile threads matching the pattern\n" \ sl@0: " -d profile DLL (or EXE) matching the pattern\n" \ sl@0: " -f profile the function matching the pattern\n" \ sl@0: " -a profile the address range specified\n" \ sl@0: " This is specified either as - or\n" \ sl@0: " as + in hexadecimal\n" \ sl@0: " -bd partition the profile by dll/exe\n" \ sl@0: " -bf partition the profile by function\n" \ sl@0: " -bs partition the profile into buckets of size n\n" \ sl@0: " -bn partition the profile into approx. n buckets\n" \ sl@0: " -c set the cutoff value for discarding output\n" \ sl@0: " -m... setformat options:\n" \ sl@0: " p|s|x use percentages/samples/excel for output\n" \ sl@0: " z output zero values instead of blanks\n" \ sl@0: " t do not show thread break-down\n" \ sl@0: " o do not include the bucket\n" \ sl@0: " -z supply a ROFS symbol file\n" \ sl@0: " -o supply an OBY file\n" \ sl@0: " -x supply a config file\n" \ sl@0: " -h config display an example of config file\n" \ sl@0: << flush; sl@0: } sl@0: sl@0: void Analyse::ExplainConfigUsage() sl@0: { sl@0: Information(); sl@0: cout << "Example of config file:" << endl << endl; sl@0: cout << "[Common]" << endl; sl@0: cout << "TraceFile=PROFILER.DAT" << endl; sl@0: cout << "Mode=listing|profile|activity" << endl; sl@0: cout << "SymbolFile=core4r.bin.symbol" << endl; sl@0: cout << "Range=100-200 | 100+100" << endl; sl@0: cout << "IncludeNullThread=0|1" << endl; sl@0: cout << "[Profile]" << endl; sl@0: cout << "Thread=" << endl; sl@0: cout << "Dll=" << endl; sl@0: cout << "Function=" << endl; sl@0: cout << "Range=1f1a+20 | 1f1a-1f3a" << endl; sl@0: cout << "[Partition]" << endl; sl@0: cout << "Mode=dll|function" << endl; sl@0: cout << "BucketSize=" << endl; sl@0: cout << "NumberOfBuckets=" << endl; sl@0: cout << "[Format]" << endl; sl@0: cout << "Mode=percentages|samples|excel" << endl; sl@0: cout << "ZeroValues=0|1" << endl; sl@0: cout << "NoOthers=0|1" << endl; sl@0: cout << "TotalOnly=0|1" << endl; sl@0: cout << "[NonXIP]" << endl; sl@0: cout << "ObyFile1=myrofs.oby" << endl; sl@0: cout << "RofsSymbolFile1=rofs.bin.symbol" << endl; sl@0: cout << flush; sl@0: } sl@0: sl@0: class Options sl@0: { sl@0: struct Entry sl@0: { sl@0: const char* iName; sl@0: int iOption; sl@0: }; sl@0: const static Entry KOptions[]; sl@0: static int Compare(const char* aLhs, const char* aRhs); sl@0: public: sl@0: static int Get(istrstream& aStr); sl@0: }; sl@0: sl@0: sl@0: const Options::Entry Options::KOptions[] = sl@0: { sl@0: {"activity",'v'}, sl@0: {"address", 'a'}, sl@0: {"by", 'b'}, sl@0: {"cutoff", 'c'}, sl@0: {"dll", 'd'}, sl@0: {"excel", 'x'}, sl@0: {"format", 'm'}, sl@0: {"function",'f'}, sl@0: {"help", 'h'}, sl@0: {"listing", 'l'}, sl@0: {"null", 'n'}, sl@0: {"number", 'n'}, sl@0: {"other", 'o'}, sl@0: {"percent", 'p'}, sl@0: {"profile", 'p'}, sl@0: {"rom", 'r'}, sl@0: {"samples", 's'}, sl@0: {"size", 's'}, sl@0: {"thread", 't'}, sl@0: {"total", 't'}, sl@0: {"zero", 'z'}, sl@0: {"oby", 'o'}, sl@0: {"rofs", 'z'}, sl@0: {"config", 'x'}, sl@0: }; sl@0: sl@0: inline int min(int a, int b) sl@0: { sl@0: return a < b ? a : b; sl@0: } sl@0: sl@0: int Options::Compare(const char* aLhs, const char* aRhs) sl@0: { sl@0: int len = min(strlen(aLhs), strlen(aRhs)); sl@0: return strnicmp(aLhs, aRhs, len); sl@0: } sl@0: sl@0: int Options::Get(istrstream& aStr) sl@0: { sl@0: int pos = aStr.tellg(); sl@0: const char* s = aStr.str() + pos; sl@0: sl@0: if (strlen(s) >= 3) sl@0: { sl@0: int l = 0, r = sizeof(KOptions)/sizeof(KOptions[0]); sl@0: do sl@0: { sl@0: int m = (l + r ) >> 1; sl@0: const Entry& e = KOptions[m]; sl@0: int k = Compare(s, e.iName); sl@0: if (k < 0) sl@0: r = m; sl@0: else if (k > 0) sl@0: l = m + 1; sl@0: else sl@0: { sl@0: // found a match sl@0: aStr.ignore(strlen(e.iName)); sl@0: return e.iOption; sl@0: } sl@0: } while (l < r); sl@0: } sl@0: // no match sl@0: return aStr.get(); sl@0: } sl@0: sl@0: int Analyse::ProcessCommandLine(int argc, char ** argv) sl@0: { sl@0: int initial_argc = argc; sl@0: char ** initial_argv = argv; sl@0: // added 2-nd pass. on the 1-st just look for config file sl@0: for(int pass = 0;pass < 2;pass++) sl@0: { sl@0: argc = initial_argc; sl@0: argv = initial_argv; sl@0: while (--argc>0) sl@0: { sl@0: istrstream arg(*++argv); sl@0: int c = arg.get(); sl@0: if (c != '/' && c != '-') sl@0: { sl@0: if (pass == 0) continue; sl@0: sTraces.clear(); sl@0: sTraces.push_back(arg.str()); sl@0: continue; sl@0: } sl@0: c = Options::Get(arg); sl@0: if (tolower(c) != 'x' && pass == 0) sl@0: continue; sl@0: switch (c) sl@0: { sl@0: case 'h': case 'H': case '?': sl@0: if (--argc > 0 && !stricmp(*++argv,"config")) sl@0: return 2; sl@0: return 1; sl@0: case 'l': case 'L': sl@0: sAction = ETrace; sl@0: break; sl@0: case 'p': case 'P': sl@0: sAction = EProfile; sl@0: break; sl@0: case 'v': case 'V': sl@0: sAction = EActivity; sl@0: break; sl@0: case 'r': case 'R': sl@0: if (--argc == 0) sl@0: Abort("No symbol file specified for option '-r'"); sl@0: sRomFile = *++argv; sl@0: break; sl@0: case 's': case 'S': sl@0: sOptions |= ERange; sl@0: arg >> sBeginSample; sl@0: c = arg.get(); sl@0: arg >> sEndSample; sl@0: if (c == '+') sl@0: sEndSample += sBeginSample; sl@0: else if (c != '-') sl@0: return 1; sl@0: break; sl@0: case 'n': case 'N': sl@0: sOptions|=ENull; sl@0: break; sl@0: case 't': case 'T': sl@0: if (--argc == 0) sl@0: Abort("No thread name specified for option '-t'"); sl@0: sThread = *++argv; sl@0: break; sl@0: case 'd': case 'D': sl@0: if (--argc == 0) sl@0: Abort("No DLL name specified for option '-d'"); sl@0: sDll = *++argv; sl@0: break; sl@0: case 'f': case 'F': sl@0: if (--argc == 0) sl@0: Abort("No function name specified for option '-f'"); sl@0: sFunction = *++argv; sl@0: break; sl@0: case 'a': case 'A': sl@0: sOptions |= EAddress; sl@0: arg >> hex >> sBase; sl@0: c = arg.get(); sl@0: arg >> hex >> sLim; sl@0: if (c == '+') sl@0: sLim += sBase; sl@0: else if (c != '-') sl@0: return 1; sl@0: break; sl@0: case 'b': case 'B': sl@0: switch (c = Options::Get(arg)) sl@0: { sl@0: case 'd': case 'D': sl@0: sPartition = EDll; sl@0: break; sl@0: case 'f': case 'F': sl@0: sPartition = EFunction; sl@0: break; sl@0: case 'n': case 'N': sl@0: sPartition = EBuckets; sl@0: arg >> dec >> sBuckets; sl@0: break; sl@0: case 's': case 'S': sl@0: sPartition = ESize; sl@0: arg >> dec >> sBucketSize; sl@0: break; sl@0: } sl@0: break; sl@0: case 'c': case 'C': sl@0: arg >> sCutOff; sl@0: break; sl@0: case 'm': case 'M': sl@0: while ((c = Options::Get(arg)) != EOF) sl@0: { sl@0: switch (c) sl@0: { sl@0: case 'p': case 'P': sl@0: sFormat = EPercent; sl@0: break; sl@0: case 's': case 'S': sl@0: sFormat = ESamples; sl@0: break; sl@0: case 'x': case 'X': sl@0: sFormat = EExcel; sl@0: break; sl@0: case 'z': case 'Z': sl@0: sOptions |= EZeros; sl@0: break; sl@0: case 'o': case 'O': sl@0: sOptions |= ENoOther; sl@0: break; sl@0: case 't': case 'T': sl@0: sOptions |= ETotalOnly; sl@0: break; sl@0: default: sl@0: arg.putback(c); sl@0: break; sl@0: } sl@0: } sl@0: break; sl@0: case 'o': case 'O': sl@0: if (--argc == 0) sl@0: Abort("No OBY file name specified for option '-o'"); sl@0: gNonXIP.AddObyFile(*++argv); sl@0: break; sl@0: case 'z': case 'Z': sl@0: if (--argc == 0) sl@0: Abort("No ROFS symbol file name specified for option '-z'"); sl@0: gNonXIP.AddSymbolFile(*++argv); sl@0: break; sl@0: case 'x': case 'X': sl@0: if (--argc == 0) sl@0: Abort("No config file name specified for option '-x'"); sl@0: if (pass == 0) sl@0: { sl@0: switch(ProcessCfgFile(*++argv)) sl@0: { sl@0: case ENoCfgFile: sl@0: Abort("Error no config file name specified for option '-x'"); sl@0: case EErrorCfgFile: sl@0: Abort("Error in config file"); sl@0: } sl@0: } sl@0: else sl@0: ++argv; sl@0: break; sl@0: default: // unrecognised option sl@0: arg.putback(c); sl@0: break; sl@0: } sl@0: if (!arg || arg.get() != EOF) sl@0: { sl@0: cerr << "Unrecognised option \'" << arg.str() << '\'' << endl; sl@0: Abort(); sl@0: } sl@0: } // while sl@0: } // for(pass) sl@0: if (sTraces.empty()) sl@0: Abort("No trace files specified"); sl@0: return sTraces.size() != 1; sl@0: } sl@0: sl@0: CodeSpace* Analyse::CreateCodeSpace(SymbolFile* aSymbols, NonXIP *aNonXIP) sl@0: { sl@0: if (Option(EAddress)) sl@0: { sl@0: unsigned size; sl@0: if (Partition() == ESize) sl@0: size = sBucketSize; sl@0: else sl@0: size = (sLim - sBase) / sBuckets; sl@0: return new AddressCodeSpace(sBase, sLim, size, AddressCodeSpace::EAbsolute); sl@0: } sl@0: sl@0: MappedCodeSpace * mapped_code_space = 0; sl@0: if (aSymbols == 0) sl@0: { sl@0: MappedCodeSpace* mapped_code_space = new MappedCodeSpace(); sl@0: if (aNonXIP) sl@0: aNonXIP->SetMappedCodeSpace(mapped_code_space); sl@0: return mapped_code_space; sl@0: } sl@0: sl@0: for (;;) sl@0: { sl@0: switch (Partition()) sl@0: { sl@0: case EDefault: sl@0: if (sFunction != 0) sl@0: { sl@0: sPartition = ESize; sl@0: sBucketSize = 4; sl@0: } sl@0: else if (sDll != 0) sl@0: sPartition = EFunction; sl@0: else sl@0: sPartition = EDll; sl@0: break; sl@0: case EDll: sl@0: { sl@0: PartitionByDll p(sDll); sl@0: mapped_code_space = new MappedCodeSpace(*aSymbols,p); sl@0: if (aNonXIP) sl@0: aNonXIP->SetMappedCodeSpace(mapped_code_space); sl@0: return mapped_code_space; sl@0: } sl@0: case EFunction: sl@0: { sl@0: PartitionByFunction p(sDll, sFunction); sl@0: mapped_code_space = new MappedCodeSpace(*aSymbols,p); sl@0: if (aNonXIP) sl@0: aNonXIP->SetMappedCodeSpace(mapped_code_space); sl@0: return mapped_code_space; sl@0: } sl@0: case ESize: sl@0: case EBuckets: sl@0: if (sFunction == 0) sl@0: sPartition = EFunction; sl@0: else sl@0: { sl@0: FindFunction f(sDll, sFunction); sl@0: aSymbols->Parse(f); sl@0: if (f.iPc == 0) sl@0: { sl@0: cerr << "Cannot find function '" << sFunction << '\''; sl@0: if (sDll) sl@0: cerr << " in '" << sDll << '\''; sl@0: cerr << endl; sl@0: Abort(); sl@0: } sl@0: unsigned size = (Partition() == ESize) ? sBucketSize : f.iLength / sBuckets; sl@0: return new AddressCodeSpace(f.iPc, f.iPc + f.iLength, size, AddressCodeSpace::ERelative); sl@0: } sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: Sampler* Analyse::CreateSampler(SymbolFile* aSymbols, NonXIP *aNonXIP) sl@0: { sl@0: switch (Action()) sl@0: { sl@0: case ETrace: sl@0: { sl@0: MappedCodeSpace * mapped_code_space = 0; sl@0: if (aSymbols == 0) sl@0: //return new Tracer(0); sl@0: mapped_code_space = new MappedCodeSpace(); sl@0: else sl@0: { sl@0: PartitionByFunction p(0, 0); sl@0: mapped_code_space = new MappedCodeSpace(*aSymbols,p); sl@0: } sl@0: if (aNonXIP) aNonXIP->SetMappedCodeSpace(mapped_code_space); sl@0: return new Tracer(mapped_code_space); sl@0: } sl@0: case EProfile: sl@0: { sl@0: CodeSpace * code_space = CreateCodeSpace(aSymbols, aNonXIP); sl@0: return new Distribution(*code_space, sCutOff); sl@0: } sl@0: case EActivity: sl@0: return new Activity(Partition() == ESize ? sBucketSize : 100, sBeginSample, sCutOff); sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: void Analyse::Run() sl@0: // sl@0: // The main part of the program sl@0: // sl@0: { sl@0: Information(); sl@0: Trace trace; sl@0: trace.Load(sTraces[0], sBeginSample, sEndSample); sl@0: // create map of original/segment names sl@0: gNonXIP.CreateNamesMap(); sl@0: sl@0: SymbolFile* symbols = 0; sl@0: if (sRomFile) sl@0: symbols = new SymbolFile(sRomFile); sl@0: Sampler* sampler = CreateSampler(symbols, &gNonXIP); sl@0: trace.Decode(*sampler, &gNonXIP); sl@0: sl@0: // NonXIP footer messages sl@0: cout << endl << "Row buffer errors:" << gNonXIP.iRowBufferErrors; sl@0: cout << " Cook buffer errors:" << gNonXIP.iCookBufferErrors; sl@0: cout << " Mode:"; sl@0: if (gNonXIP.iReportMask & NonXIP::ENonXip) sl@0: cout << "NonXIP"; sl@0: else sl@0: cout << "XIP only"; sl@0: if (gNonXIP.iReportMask & NonXIP::ENodebugSupport) sl@0: cout << " No debug support from Kernel"; sl@0: sl@0: cout << endl; sl@0: sl@0: cout << flush; sl@0: } sl@0: sl@0: bool Analyse::Match(const char* aString, const char* aMatch) sl@0: // sl@0: // Wildcard matching sl@0: // If match string is 0, then matches everything sl@0: // sl@0: { sl@0: if (aMatch == 0) sl@0: return true; sl@0: sl@0: const char* star = strchr(aMatch, '*'); sl@0: if (star == 0) sl@0: return (stricmp(aString, aMatch) == 0); sl@0: sl@0: int mlen = star - aMatch; sl@0: if (strnicmp(aString, aMatch, mlen) != 0) sl@0: return false; sl@0: sl@0: const char* end = aString + strlen(aString); sl@0: sl@0: for (;;) sl@0: { sl@0: aString += mlen; sl@0: aMatch += mlen + 1; sl@0: star = strchr(aMatch, '*'); sl@0: if (star == 0) sl@0: return (stricmp(end - strlen(aMatch), aMatch) == 0); sl@0: mlen = star - aMatch; sl@0: const char* lim = end - mlen; sl@0: for (;;) sl@0: { sl@0: if (aString > lim) sl@0: return false; sl@0: if (strnicmp(aString, aMatch, mlen) == 0) sl@0: break; sl@0: ++aString; sl@0: } sl@0: } sl@0: } sl@0: sl@0: void Analyse::Abort(char const* aMessage) sl@0: { sl@0: cerr << aMessage << endl; sl@0: Abort(); sl@0: } sl@0: sl@0: void Analyse::Abort() sl@0: { sl@0: exit(3); sl@0: } sl@0: sl@0: void Analyse::Corrupt(char const* aMessage) sl@0: // sl@0: // terminate after detecting a fatal corruption error sl@0: // sl@0: { sl@0: cerr << "\nfatal error: " << aMessage << "\ncannot continue\n" << flush; sl@0: exit(2); sl@0: } sl@0: sl@0: ostream& Analyse::Error() sl@0: { sl@0: return cerr << "error: "; sl@0: } sl@0: sl@0: ostream& Analyse::Warning() sl@0: { sl@0: return cerr << "warning: "; sl@0: } sl@0: