627 lines
15 KiB
C++
627 lines
15 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include <nel/misc/types_nl.h>
|
|
#include <nel/misc/common.h>
|
|
#include <nel/misc/variable.h>
|
|
#include <nel/misc/command.h>
|
|
#include <nel/misc/file.h>
|
|
#include <nel/misc/path.h>
|
|
|
|
#include "game_share/utils.h"
|
|
|
|
#include "pd_lib/pd_lib.h"
|
|
#include "pd_lib/db_description_parser.h"
|
|
#include "pd_lib/pd_messages.h"
|
|
|
|
#include "log_analyser_service.h"
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
using namespace NLNET;
|
|
using namespace RY_PDS;
|
|
|
|
/*
|
|
* SUMMARY OF COMMANDS:
|
|
*
|
|
* displayLogContent <description file> <filename>
|
|
* Display readable content of a log file
|
|
* description file: xml file of database description (with full path),
|
|
* generated by pd_parser, this must be the matching description file
|
|
* to the log file to display (that is the youngest description that
|
|
* is older than the log file)
|
|
* filename: the file to display content of (with full path)
|
|
*
|
|
*/
|
|
|
|
|
|
std::string decodeDbPathId(const std::string& pathid)
|
|
{
|
|
std::string shard("");
|
|
uint id;
|
|
|
|
string::size_type p = pathid.find(':');
|
|
|
|
if (p == std::string::npos)
|
|
{
|
|
NLMISC::fromString(pathid, id);
|
|
}
|
|
else
|
|
{
|
|
shard = pathid.substr(0, p);
|
|
NLMISC::fromString(pathid.substr(p+1), id);
|
|
}
|
|
|
|
return CPDSLib::getLogDirectory(id, shard);
|
|
}
|
|
|
|
|
|
class CSimpleFileDisplayer : public IDisplayer
|
|
{
|
|
public:
|
|
|
|
CSimpleFileDisplayer(const CSString& fileName): _FileName(fileName)
|
|
{
|
|
_File=NULL;
|
|
_TmpFileName= _FileName+ ".tmp";
|
|
|
|
// if the file that we've been asked to create already exists then try to delete the existing file
|
|
if (NLMISC::CFile::fileExists(_FileName))
|
|
NLMISC::CFile::deleteFile(_FileName);
|
|
DROP_IF(NLMISC::CFile::fileExists(_FileName),"Failed to delete file: "+_FileName,return);
|
|
|
|
// if the temp file that we're going to use already exists then try to delete the existing file
|
|
if (NLMISC::CFile::fileExists(_TmpFileName))
|
|
NLMISC::CFile::deleteFile(_TmpFileName);
|
|
DROP_IF(NLMISC::CFile::fileExists(_TmpFileName),"Failed to delete file: "+_TmpFileName,return);
|
|
|
|
_File= fopen(_TmpFileName.c_str(),"wb");
|
|
}
|
|
|
|
~CSimpleFileDisplayer()
|
|
{
|
|
fclose(_File);
|
|
|
|
// rename the created file...
|
|
DROP_IF(!NLMISC::CFile::fileExists(_TmpFileName),"No output file created: "+_TmpFileName,return);
|
|
DROP_IF(NLMISC::CFile::fileExists(_FileName),"Cannot rename output file '"+_TmpFileName+"' because another file is in the way: "+_FileName,return);
|
|
NLMISC::CFile::moveFile(_FileName.c_str(),_TmpFileName.c_str());
|
|
DROP_IF(!NLMISC::CFile::fileExists(_FileName),"Failed to create final output file: '"+_FileName+"' from tmp file: '"+_TmpFileName+"'",return);
|
|
}
|
|
|
|
bool isOK() const
|
|
{
|
|
return _File!= NULL;
|
|
}
|
|
|
|
protected:
|
|
|
|
virtual void doDisplay( const CLog::TDisplayInfo& args, const char *message)
|
|
{
|
|
if (isOK())
|
|
{
|
|
fwrite(message,strlen(message),1,_File);
|
|
}
|
|
}
|
|
|
|
private:
|
|
FILE* _File;
|
|
CSString _FileName;
|
|
CSString _TmpFileName;
|
|
|
|
};
|
|
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, executeToFile, "execute a command, spuing output to a file ", "<filename> <commandline>")
|
|
{
|
|
// split the raw commandline into a file name and remaining commandline
|
|
NLMISC::CSString s= rawCommandString;
|
|
// skip the wrapper command name
|
|
s.strtok(" \t");
|
|
// get the file name
|
|
NLMISC::CSString fileName=s.strtok(" \t",true).unquoteIfQuoted();
|
|
|
|
// cleanup the remaining commandline
|
|
s=s.strip();
|
|
DROP_IF(s.empty(),"Too few elements found in command line",return false);
|
|
|
|
// setup a log object with an attached file displayer
|
|
CSimpleFileDisplayer* displayer= new CSimpleFileDisplayer(fileName);
|
|
DROP_IF(!displayer->isOK(),"Aborting command execution because failed to setup file displayer",return false);
|
|
CLog theLog;
|
|
theLog.addDisplayer(displayer,true);
|
|
|
|
// execute the command...
|
|
NLMISC::ICommand::execute(s, theLog);
|
|
|
|
// remove the displayer and destroy it to ensure files are closed etc
|
|
theLog.removeDisplayer(displayer);
|
|
delete displayer;
|
|
|
|
// success fo return true
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, displayLogFile, "display pd_log file human readable content", "<description file> <filename>")
|
|
{
|
|
if (args.size() != 2)
|
|
return false;
|
|
|
|
string descriptionfile = args[0];
|
|
string filename = args[1];
|
|
|
|
CDBDescriptionParser desc;
|
|
|
|
if (!desc.loadDescriptionFile(descriptionfile))
|
|
{
|
|
log.displayNL("#! Failed to load database description file '%s'", descriptionfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!desc.buildColumns())
|
|
{
|
|
log.displayNL("#! Failed to build database columns for description '%s'", descriptionfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
CIFile ifile;
|
|
std::vector<CUpdateLog> updateLog;
|
|
|
|
if (!ifile.open(filename))
|
|
{
|
|
log.displayNL("#! Failed to open file '%s'", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
while (!ifile.eof())
|
|
{
|
|
try
|
|
{
|
|
ifile.serialCont(updateLog);
|
|
}
|
|
catch (const Exception& e)
|
|
{
|
|
log.displayNL("#! Failed to load file '%s': %s", filename.c_str(), e.what());
|
|
return false;
|
|
}
|
|
|
|
uint i;
|
|
for (i=0; i<updateLog.size(); ++i)
|
|
updateLog[i].display(desc, log);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, displayLog, "display pd_log file human readable content", "[shard:]<databaseId> <filename w/o path>")
|
|
{
|
|
if (args.size() != 2)
|
|
return false;
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
string filename = args[1];
|
|
|
|
std::string descriptionfile = CUpdateLog::electDescription(logpath+filename);
|
|
|
|
if (descriptionfile.empty())
|
|
{
|
|
log.displayNL("#! Failed to elect description file for file '%s'", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
CDBDescriptionParser desc;
|
|
|
|
if (!desc.loadDescriptionFile(descriptionfile))
|
|
{
|
|
log.displayNL("#! Failed to load database description file '%s'", descriptionfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!desc.buildColumns())
|
|
{
|
|
log.displayNL("#! Failed to build database columns for description '%s'", descriptionfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
CIFile ifile;
|
|
std::vector<CUpdateLog> updateLog;
|
|
|
|
if (!ifile.open(logpath+filename))
|
|
{
|
|
log.displayNL("#! Failed to open file '%s'", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
while (!ifile.eof())
|
|
{
|
|
try
|
|
{
|
|
ifile.serialCont(updateLog);
|
|
}
|
|
catch (const Exception& e)
|
|
{
|
|
log.displayNL("#! Failed to load file '%s': %s", filename.c_str(), e.what());
|
|
return false;
|
|
}
|
|
|
|
uint i;
|
|
for (i=0; i<updateLog.size(); ++i)
|
|
updateLog[i].display(desc, log);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, displayLogs,
|
|
"display logs content between 2 dates (date format is YYYY.MM.DD.hh.mm[.ss], or <-|+>nn<d|h|m|s> to specify relative date)",
|
|
"[shard:]<databaseId> <startdate> [enddate]")
|
|
{
|
|
if (args.size() < 2 || args.size() > 3)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
CTimestamp startdate;
|
|
CTimestamp enddate;
|
|
|
|
startdate.setToCurrent();
|
|
startdate.fromString(args[1].c_str());
|
|
|
|
if (args.size() == 3)
|
|
{
|
|
enddate = startdate;
|
|
enddate.fromString(args[2].c_str());
|
|
}
|
|
else
|
|
{
|
|
enddate.setToCurrent();
|
|
}
|
|
|
|
CLogAnalyserService::CQuery* q = CLogAnalyserService::getInstance()->getCurrentQuery();
|
|
CUpdateLog::displayLogs(logpath, startdate, enddate, log, (q != NULL ? (float*)&(q->Progress) : NULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, searchEId,
|
|
"search for references to an entity id in logs between 2 dates (date format is YYYY:MM:DD:hh:mm[:ss], or <-|+>nn<d|h|m|s> to specify relative date)",
|
|
"[shard:]<databaseId> <entityId> <startdate> [enddate]")
|
|
{
|
|
if (args.size() < 3 || args.size() > 4)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
CEntityId entityId;
|
|
entityId.fromString(args[1].c_str());
|
|
|
|
CTimestamp startdate;
|
|
CTimestamp enddate;
|
|
|
|
startdate.setToCurrent();
|
|
startdate.fromString(args[2].c_str());
|
|
|
|
if (args.size() == 4)
|
|
{
|
|
enddate = startdate;
|
|
enddate.fromString(args[3].c_str());
|
|
}
|
|
else
|
|
{
|
|
enddate.setToCurrent();
|
|
}
|
|
|
|
CLogAnalyserService::CQuery* q = CLogAnalyserService::getInstance()->getCurrentQuery();
|
|
CUpdateLog::displayLogs(logpath, entityId, startdate, enddate, log, (q != NULL ? (float*)&(q->Progress) : NULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, searchString,
|
|
"search for references to a string in logs between 2 dates (date format is YYYY:MM:DD:hh:mm[:ss], or <-|+>nn<d|h|m|s> to specify relative date)",
|
|
"[shard:]<databaseId> <string> <startdate> [enddate]")
|
|
{
|
|
if (args.size() < 3 || args.size() > 4)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
std::string str = args[1];
|
|
|
|
CTimestamp startdate;
|
|
CTimestamp enddate;
|
|
|
|
startdate.setToCurrent();
|
|
startdate.fromString(args[2].c_str());
|
|
|
|
if (args.size() == 4)
|
|
{
|
|
enddate = startdate;
|
|
enddate.fromString(args[3].c_str());
|
|
}
|
|
else
|
|
{
|
|
enddate.setToCurrent();
|
|
}
|
|
|
|
CLogAnalyserService::CQuery* q = CLogAnalyserService::getInstance()->getCurrentQuery();
|
|
CUpdateLog::displayLogs(logpath, str, startdate, enddate, log, (q != NULL ? (float*)&(q->Progress) : NULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, searchEIds,
|
|
"search for references to multiple entity ids in logs between 2 dates (date format is YYYY:MM:DD:hh:mm[:ss], or <-|+>nn<d|h|m|s> to specify relative date)",
|
|
"[shard:]<databaseId> [entityId+] - <startdate> [enddate]")
|
|
{
|
|
if (args.size() < 4)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
std::vector<NLMISC::CEntityId> ids;
|
|
uint i = 1;
|
|
while (i < args.size() && args[i] != "-")
|
|
{
|
|
NLMISC::CEntityId id;
|
|
id.fromString(args[i].c_str());
|
|
ids.push_back(id);
|
|
++i;
|
|
}
|
|
|
|
++i;
|
|
|
|
if (i >= args.size())
|
|
return false;
|
|
|
|
CTimestamp startdate;
|
|
CTimestamp enddate;
|
|
|
|
startdate.setToCurrent();
|
|
startdate.fromString(args[i].c_str());
|
|
|
|
++i;
|
|
|
|
if (i < args.size())
|
|
{
|
|
enddate = startdate;
|
|
enddate.fromString(args[i].c_str());
|
|
}
|
|
else
|
|
{
|
|
enddate.setToCurrent();
|
|
}
|
|
|
|
CLogAnalyserService::CQuery* q = CLogAnalyserService::getInstance()->getCurrentQuery();
|
|
CUpdateLog::displayLogs(logpath, ids, startdate, enddate, log, (q != NULL ? (float*)&(q->Progress) : NULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, searchValueByEId,
|
|
"search for references to an entity id in logs between 2 dates (date format is YYYY:MM:DD:hh:mm[:ss], or <-|+>nn<d|h|m|s> to specify relative date)",
|
|
"[shard:]<databaseId> <entityId> <valuePath> <startdate> [enddate]")
|
|
{
|
|
if (args.size() < 4 || args.size() > 5)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
CEntityId entityId;
|
|
entityId.fromString(args[1].c_str());
|
|
|
|
std::string valuePath = args[2];
|
|
|
|
CTimestamp startdate;
|
|
CTimestamp enddate;
|
|
|
|
startdate.setToCurrent();
|
|
startdate.fromString(args[2].c_str());
|
|
|
|
if (args.size() == 5)
|
|
{
|
|
enddate = startdate;
|
|
enddate.fromString(args[3].c_str());
|
|
}
|
|
else
|
|
{
|
|
enddate.setToCurrent();
|
|
}
|
|
|
|
CLogAnalyserService::CQuery* q = CLogAnalyserService::getInstance()->getCurrentQuery();
|
|
CUpdateLog::displayLogs(logpath, entityId, valuePath, startdate, enddate, log, (q != NULL ? (float*)&(q->Progress) : NULL));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, displayDescription,
|
|
"Display database description. Asks for a specific file if more than one found.",
|
|
"[shard:]<databaseId> [descriptionfile]")
|
|
{
|
|
if (args.size() < 1 || args.size() > 2)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
std::string useFile;
|
|
|
|
if (args.size() == 1)
|
|
{
|
|
std::vector<std::string> files, found;
|
|
NLMISC::CPath::getPathContent(logpath, false, false, true, files);
|
|
|
|
std::sort(files.begin(), files.end());
|
|
|
|
uint i;
|
|
for (i=0; i<files.size(); ++i)
|
|
if (NLMISC::CFile::getExtension(files[i]) == "description")
|
|
found.push_back(files[i]);
|
|
|
|
if (found.size() == 0)
|
|
{
|
|
log.displayNL("## no description file found in path '%s'", logpath.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (found.size() > 1)
|
|
{
|
|
log.displayNL("## found multiple description files in path '%s':", logpath.c_str());
|
|
for (i=0; i<found.size(); ++i)
|
|
log.displayNL("#- %s", NLMISC::CFile::getFilename(found[i]).c_str());
|
|
log.displayNL("## found %d files, please select one", found.size());
|
|
return true;
|
|
}
|
|
|
|
useFile = NLMISC::CFile::getFilename(found[0]);
|
|
|
|
log.displayNL("## Using file '%s'", useFile.c_str());
|
|
}
|
|
else
|
|
{
|
|
useFile = args[1];
|
|
}
|
|
|
|
CDBDescriptionParser desc;
|
|
if (!desc.loadDescriptionFile(logpath + useFile))
|
|
return false;
|
|
|
|
desc.buildColumns();
|
|
desc.display(log);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
NLMISC_CATEGORISED_COMMAND(pd_log, displayTableDescription,
|
|
"Display table description. Asks for a specific file if more than one found.",
|
|
"[shard:]<databaseId> <tableName> [descriptionfile]")
|
|
{
|
|
if (args.size() < 2 || args.size() > 3)
|
|
return false;
|
|
|
|
/*
|
|
uint databaseId;
|
|
NLMISC::fromString(args[0], databaseId);
|
|
string logpath = CPDSLib::getLogDirectory(databaseId);
|
|
*/
|
|
string logpath = decodeDbPathId(args[0]);
|
|
|
|
std::string useFile;
|
|
|
|
if (args.size() == 2)
|
|
{
|
|
std::vector<std::string> files, found;
|
|
NLMISC::CPath::getPathContent(logpath, false, false, true, files);
|
|
|
|
std::sort(files.begin(), files.end());
|
|
|
|
uint i;
|
|
for (i=0; i<files.size(); ++i)
|
|
if (NLMISC::CFile::getExtension(files[i]) == "description")
|
|
found.push_back(files[i]);
|
|
|
|
if (found.size() == 0)
|
|
{
|
|
log.displayNL("## no description file found in path '%s'", logpath.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (found.size() > 1)
|
|
{
|
|
log.displayNL("## found multiple description files in path '%s':", logpath.c_str());
|
|
for (i=0; i<found.size(); ++i)
|
|
log.displayNL("#- %s", NLMISC::CFile::getFilename(found[i]).c_str());
|
|
log.displayNL("## found %d files, please select one", found.size());
|
|
return true;
|
|
}
|
|
|
|
useFile = NLMISC::CFile::getFilename(found[0]);
|
|
|
|
log.displayNL("## Using file '%s'", useFile.c_str());
|
|
}
|
|
else
|
|
{
|
|
useFile = logpath + args[2];
|
|
}
|
|
|
|
std::string tableName = toLower(args[1]);
|
|
|
|
CDBDescriptionParser desc;
|
|
if (!desc.loadDescriptionFile(logpath + useFile))
|
|
return false;
|
|
|
|
const CDatabaseNode& db = desc.getDatabaseNode();
|
|
|
|
uint i;
|
|
for (i=0; i<db.Tables.size(); ++i)
|
|
if (toLower(db.Tables[i].Name) == tableName)
|
|
break;
|
|
|
|
if (i == db.Tables.size())
|
|
return false;
|
|
|
|
desc.buildColumns();
|
|
desc.displayTable(i, log);
|
|
|
|
return true;
|
|
}
|
|
|
|
|