#include "nel/misc/file.h" #include "nel/misc/path.h" #include "nel/misc/vector.h" #include "nel/misc/algo.h" using namespace std; using namespace NLMISC; #define myinfo NLMISC::createDebug (), NLMISC::InfoLog->setPosition( __LINE__, __FILE__ ), NLMISC::InfoLog->displayRawNL // *************************************************************************** void filterRyzomBug(const char *dirSrc, const char *dirDst, uint patchVersionWanted, const string specialFilter) { if(!CFile::isDirectory(dirDst)) { if(!CFile::createDirectory(dirDst)) { myinfo("%s is not a directory and cannot create it", dirDst); return; } } vector<string> fileList; CPath::getPathContent(dirSrc, false, false, true, fileList, NULL, true); for(uint i=0;i<fileList.size();i++) { const string &fileFullPath= fileList[i]; CIFile f; if(f.open(fileFullPath, true)) { // Parse all "UserId: ", this get the number of crash in this file const string userIdTok= "UserId: "; const string patchVersionTok= "PatchVersion: "; uint numUserId= 0; uint numPatchVersion= 0; bool precUserId= false; bool ok= true; bool filterFound= false; while(!f.eof()) { char tmp[1000]; f.getline(tmp, 1000); string str= tmp; if(str.compare(0, userIdTok.size(), userIdTok)==0) { numUserId++; if(precUserId) { nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str()); ok= false; break; } else precUserId= true; } if(str.compare(0, patchVersionTok.size(), patchVersionTok)==0) { numPatchVersion++; if(!precUserId) { nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str()); ok= false; break; } else precUserId= false; // parse the version number sint version; sscanf(tmp, "PatchVersion: %d", &version); if(version!=(sint)patchVersionWanted) { nlwarning("The Log %s contains a PatchVersion different: %d", fileFullPath.c_str(), version); ok= false; break; } } if(!specialFilter.empty() && str.compare(0, specialFilter.size(), specialFilter)==0) filterFound= true; } f.close(); if(ok && numUserId!=numPatchVersion) { nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str()); ok= false; } // if specialFitler defined, but not found in the file, abort if(ok && !specialFilter.empty() && !filterFound) ok =false; // if ok, copy the file and the associated .dmp dir if(ok) { //myinfo("Copy %s", fileFullPath.c_str()); // get the log size uint size= CFile::getFileSize(fileFullPath); // copy the log string fileNoDir= CFile::getFilename(fileFullPath); string fileNoExt= CFile::getFilenameWithoutExtension(fileFullPath); string dirDest= dirDst; dirDest+= "/" + toString("%05d_", size/1024) + fileNoExt; string dmpDirSrc= string(dirSrc) + "/" + fileNoExt; CFile::createDirectory(dirDest); // copy near the dmp CFile::copyFile((dirDest + "/" + fileNoDir).c_str(), fileFullPath.c_str()); // copy all the .dmp in a new dir static vector<string> dmpList; dmpList.clear(); CPath::getPathContent(dmpDirSrc, false, false, true, dmpList, NULL); for(uint j=0;j<dmpList.size();j++) { string dmpNoDir= CFile::getFilename(dmpList[j]); CFile::copyFile((dirDest+ "/" + dmpNoDir).c_str(), dmpList[j].c_str()); } } } else nlwarning("cannot open %s", fileFullPath.c_str()); } } // *************************************************************************** struct CStatVal { uint Val; CStatVal() { Val = 0; } }; typedef map<sint, CStatVal> TStatMap; typedef map<string, CStatVal> TStatStrMap; typedef map<string, TStatStrMap> TStatStrStrMap; class CCrashCont { public: string Name; sint X0,Y0,X1,Y1; uint NumCrash; CCrashCont(const std::string &name, sint x0, sint y0, sint x1, sint y1) { Name= name; X0= min(x0,x1); Y0= min(y0,y1); X1= max(x0,x1); Y1= max(y0,y1); NumCrash= 0; } bool testPos(const CVector &pos) { if(pos.x>=X0 && pos.x<=X1 && pos.y>=Y0 && pos.y<=Y1) { NumCrash++; return true; } return false; } }; void statRyzomBug(const char *dirSrc) { vector<string> fileList; CPath::getPathContent(dirSrc, false, false, true, fileList, NULL, true); // delete the log.log CFile::deleteFile("log.log"); TStatStrMap senderMap; TStatMap shardMap; TStatMap timeInGameMap; TStatMap patchVersionMap; TStatMap info3dMap; std::vector<CVector> userPosArray; TStatStrStrMap senderToCrashFileMap; TStatStrStrMap crashFileToSenderMap; uint totalCrash= 0; uint totalCrashDuplicate= 0; // **** parse all files for(uint i=0;i<fileList.size();i++) { const string &fileFullPath= fileList[i]; string fileNoDir= CFile::getFilename(fileFullPath); // skip not .log files if(!testWildCard(fileFullPath, "*.log")) continue; // parse CIFile f; if(f.open(fileFullPath, true)) { const string senderIdTok= "Sender: "; const string shardIdTok= "ShardId: "; const string userPosTok= "UserPosition: "; const string timeInGameIdTok= "Time in game: "; const string patchVersionIdTok= "PatchVersion: "; const string localTimeIdTok= "LocalTime: "; const string nel3dIdTok= "NeL3D: "; const string card3dIdTok= "3DCard: "; string precSenderId; string precSenderId2; sint precShardId= -1; sint precTimeInGame= -1; // 0 means "never in game", 1 means < 10 min, 2 means more sint precNel3DMode= -1; // 0 OpenGL, 1 D3D, 2 ???? sint precCard3D= -1; // 0 NVidia, 1 ATI, 2 ???? sint precPatchVersion= -1; sint64 precLocalTime= -1; // local time in second sint64 precLocalTime2= -1; // local time in second CVector precUserPos; while(!f.eof()) { char tmp[1000]; f.getline(tmp, 1000); string str= tmp; if(str.compare(0, senderIdTok.size(), senderIdTok)==0) { precSenderId2= precSenderId; precSenderId= str.c_str()+senderIdTok.size(); } else if(str.compare(0, shardIdTok.size(), shardIdTok)==0) { precShardId= atoi(str.c_str()+shardIdTok.size()); } else if(str.compare(0, userPosTok.size(), userPosTok)==0) { string posStr= str.substr(userPosTok.size()); sscanf(posStr.c_str(), "%f%f%f", &precUserPos.x, &precUserPos.y, &precUserPos.z); } else if(str.compare(0, timeInGameIdTok.size(), timeInGameIdTok)==0) { string timeStr= str.substr(timeInGameIdTok.size(), string::npos); if(timeStr=="0h 0min 0sec" || timeStr.size()<12) precTimeInGame= 0; else { if(timeStr[1]=='h' && timeStr[4]=='m' && timeStr[3]<='5') precTimeInGame= 1; else precTimeInGame= 2; } } else if(str.compare(0, patchVersionIdTok.size(), patchVersionIdTok)==0) { precPatchVersion= atoi(str.c_str()+patchVersionIdTok.size()); } else if(str.compare(0, localTimeIdTok.size(), localTimeIdTok)==0) { precLocalTime2= precLocalTime; // 2004/09/17 04:21:16 string timeStr= str.substr(localTimeIdTok.size(), string::npos); if(timeStr.size()<19) precLocalTime= 0; else { sint64 year= atoi(timeStr.substr(0,4).c_str()); sint64 month= atoi(timeStr.substr(5,2).c_str()); sint64 day= atoi(timeStr.substr(8,2).c_str()); sint64 hour= atoi(timeStr.substr(11,2).c_str()); sint64 minute= atoi(timeStr.substr(14,2).c_str()); sint64 sec= atoi(timeStr.substr(17,2).c_str()); year= max(year, (sint64)2004); year-=2004; precLocalTime= ((year*366+month)*12)+day; precLocalTime= (((precLocalTime*24)+hour)*60+minute)*60+sec; } } else if(str.compare(0, nel3dIdTok.size(), nel3dIdTok)==0) { string tmp= str; strlwr(tmp); if(tmp.find("opengl")!=string::npos) precNel3DMode= 0; else if(tmp.find("direct3d")!=string::npos) precNel3DMode= 1; else precNel3DMode= 2; } else if(str.compare(0, card3dIdTok.size(), card3dIdTok)==0) { string tmp= str; if(tmp.find("NVIDIA")!=string::npos) precCard3D= 0; else if(tmp.find("RADEON")!=string::npos) precCard3D= 1; else precCard3D= 2; // END a block, add info in map (only if not repetition) sint64 absTime= precLocalTime-precLocalTime2; if(absTime<0) absTime= -absTime; if( precSenderId!=precSenderId2 || absTime>sint64(60) ) { senderMap[precSenderId].Val++; shardMap[precShardId].Val++; timeInGameMap[precTimeInGame].Val++; patchVersionMap[precPatchVersion].Val++; if(precNel3DMode!=0 && precNel3DMode!=1) precNel3DMode= 2; if(precCard3D!=0 && precCard3D!=1) precCard3D= 2; info3dMap[precNel3DMode*256+precCard3D].Val++; userPosArray.push_back(precUserPos); crashFileToSenderMap[fileNoDir][precSenderId].Val++; senderToCrashFileMap[precSenderId][fileNoDir].Val++; totalCrash++; } totalCrashDuplicate++; } } } } // **** display Stats // general stats myinfo("**** Total: %d Crashs (%d with duplicates)", totalCrash, totalCrashDuplicate); myinfo("NB: 'duplicates' means: crashs that are removed because suppose the player click 'ignore' (same sender/same Localtime, within about 1 min)"); // senderId TStatMap::iterator it; TStatStrMap::iterator itStr; myinfo(""); myinfo("**** Stat Per Sender:"); multimap<uint, string> resortSender; for(itStr=senderMap.begin();itStr!=senderMap.end();itStr++) { resortSender.insert(make_pair(itStr->second.Val, itStr->first)); } multimap<uint, string>::iterator it2; for(it2=resortSender.begin();it2!=resortSender.end();it2++) { myinfo("**** %d Crashs for UserId %s", it2->first, it2->second.c_str()); } // shardId myinfo(""); myinfo("**** Stat Per ShardId:"); for(it=shardMap.begin();it!=shardMap.end();it++) { myinfo("**** %d Crashs for ShardId %d", it->second.Val, it->first); } // timeInGame myinfo(""); myinfo("**** Stat Per TimeInGame:"); for(it=timeInGameMap.begin();it!=timeInGameMap.end();it++) { myinfo("**** %d Crashs for TimeInGame %s", it->second.Val, it->first==0?"0h 0min 0sec": (it->first==1?"<=5 min": it->first==2?"> 5 min":"??? Bad parse ???")); } // infoPatch myinfo(""); myinfo("**** Stat Per PatchVersion:"); for(it=patchVersionMap.begin();it!=patchVersionMap.end();it++) { myinfo("**** %d Crashs for PatchVersion %d", it->second.Val, it->first); } // info3d myinfo(""); myinfo("**** Stat Per 3d Mode:"); for(it=info3dMap.begin();it!=info3dMap.end();it++) { uint card3d= it->first&255; uint mode3d= it->first>>8; myinfo("**** %d Crashs for %s / Card %s", it->second.Val, mode3d==0?"OpenGL":(mode3d==1?"Direct3D":"??? No Driver ???"), card3d==0?"NVIDIA":(card3d==1?"RADEON":"Misc")); } // crash by continent { // init cont info CCrashCont crashCont[]= { // New First, because bbox may be included in Main bbox CCrashCont("Matis Newb", 0,-5000,2800,-8500), CCrashCont("Zorai Newb", 6500,-4000,9300,-6000), CCrashCont("Trykr Newb", 20000,-32000,24000,-36000), CCrashCont("Fyros Newb", 20500,-24500,24000,-27500), CCrashCont("Matis Main", 0,0,6500,-8500), CCrashCont("Zorai Main", 6500,0,13000,-6000), CCrashCont("Trykr Main ", 13000,-29000,20000,-36000), CCrashCont("Fyros Main ", 15000,-23000,20500,-27500) }; uint numCont= sizeof(crashCont)/sizeof(crashCont[0]); uint numNotFound= 0; // count stats uint i; for(i=0;i<userPosArray.size();i++) { bool ok= false; for(uint j=0;j<numCont;j++) { if(crashCont[j].testPos(userPosArray[i])) { ok= true; break; } } if(!ok) numNotFound++; } myinfo(""); myinfo("**** Stat Per continent:"); // display stats for(i=0;i<numCont;i++) { myinfo(" %s: %d", crashCont[i].Name.c_str(), crashCont[i].NumCrash); } myinfo(" NotFound: %d", numNotFound); } // **** display detailed Stats myinfo(""); myinfo(""); myinfo("**************************"); myinfo("**************************"); myinfo("********* DETAIL *********"); myinfo("**************************"); myinfo("**************************"); myinfo(""); // Stats per User myinfo(""); myinfo("**** Detailed Crashs per user:"); for(it2=resortSender.begin();it2!=resortSender.end();it2++) { string userId= it2->second; TStatStrMap &crashFileMap= senderToCrashFileMap[userId]; if(crashFileMap.empty()) { myinfo(" Error parsing Crashs for UserId %s (?????)", userId.c_str()); } else { myinfo(" %d Crashs for %s:", it2->first, userId.c_str()); for(TStatStrMap::iterator it=crashFileMap.begin();it!=crashFileMap.end();it++) { myinfo(" %d in %s", it->second.Val, it->first.c_str()); } } } // Stats per Crash File myinfo(""); myinfo("**** Detailed Crashs per crash Log:"); multimap<uint, string> resortCrashLog; for(TStatStrStrMap::iterator itStrStr=crashFileToSenderMap.begin();itStrStr!=crashFileToSenderMap.end();itStrStr++) { // count total crash instance uint numCrash= 0; TStatStrMap &userIdMap= itStrStr->second; for(TStatStrMap::iterator it=userIdMap.begin();it!=userIdMap.end();it++) numCrash+= it->second.Val; // insert for resort by this number resortCrashLog.insert(make_pair(numCrash, itStrStr->first)); } for(it2=resortCrashLog.begin();it2!=resortCrashLog.end();it2++) { string crashLog= it2->second; TStatStrMap &userIdMap= crashFileToSenderMap[crashLog]; if(userIdMap.empty()) { myinfo(" Error parsing Crashs for CrashFile %s (?????)", crashLog.c_str()); } else { myinfo(" %d Crashs in %s:", it2->first, crashLog.c_str()); for(TStatStrMap::iterator it=userIdMap.begin();it!=userIdMap.end();it++) { myinfo(" %d for %s", it->second.Val, it->first.c_str()); } } } // RAW userPos myinfo(""); myinfo("**** RAW Crashs Pos (copy in excel, and use insert/chart/(X/Y)Scatter):"); for(uint i=0;i<userPosArray.size();i++) { myinfo("%.2f\t%.2f\t%.2f", userPosArray[i].x, userPosArray[i].y, userPosArray[i].z); } } // *************************************************************************** int main(int argc, char *argv[]) { bool ok= false; bool statMode= false; bool filterMode= false; if(argc == 3 && argv[2]==string("-s")) ok= true,statMode= true; if(argc >= 5 && argv[2]==string("-p")) ok= true,filterMode= true; if(!ok) { myinfo("Usage1 (stats):\n\t%s src_dir -s\n", CFile::getFilename(argv[0]).c_str()); myinfo("Usage2 (patch filter):\n\t%s src_dir -p patch_version dst_dir [specialFilter]\n", CFile::getFilename(argv[0]).c_str()); } else { if(!CFile::isDirectory(argv[1])) { myinfo("%s is not a directory", argv[1]); return 1; } if(filterMode) { string specialFilter; if(argc>=6) specialFilter= argv[5]; filterRyzomBug(argv[1], argv[4], atoi(argv[3]), specialFilter); } else if(statMode) { statRyzomBug(argv[1]); } } return 0; }