// Ryzom - MMORPG Framework // 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 . #include "nel/misc/singleton.h" #include "nel/net/module.h" #include "nel/net/module_builder_parts.h" #include "nel/net/unified_network.h" #include "nel/net/service.h" #include "admin_modules_itf.h" using namespace std; using namespace NLMISC; using namespace NLNET; void aesclient_forceLink() {} namespace ADMIN { class CAdminExecutorServiceClient : /*public CManualSingleton,*/ public CEmptyModuleServiceBehav > >, public CAdminExecutorServiceClientSkel { enum { // Maximum time without sending report string (a kind of 'keep alive') MAX_DELAY_BETWEEN_REPORT = 30, // 30 seconds }; /// Flag to inform AES that we don't want to be affected by shard orders bool _DontUseShardOrders; /// Admin executor service module TModuleProxyPtr _AdminExecutorService; /// Date of last state reporting to AES uint32 _LastStateReport; /// Last date of status string update uint32 _LastStatusStringReport; /// Last status string sent (to avoid double send) string _LastSentStatus; /// The service alias (must be an unique name) string _ServiceAlias; /// A cache of the value because reading it is very slow uint32 _ProcessUsedMemory; struct TGraphSample { // The date of the sample (in second) uint32 TimeStamp; // The date of the sample (in ms) TTime HighRezTimeStamp; // sample value double SampleValue; }; struct TGraphVarInfo { /// Name of the graphed var string VarName; /** Mean time between two sample in ms * (in fact, if will be the min period) * Set it to 1 to have a sample at each tick * If the period is set less than 1000 ms, * then the var is considered 'high rez'. * Otherwise, the period is rounded at the * nearest integer second. * For 'high rez' var, the service buffer * the relative timestamp in ms at each * tick loop and send update every seconds * to the AES service. * In addition, HighRez var are also sent * every second as normal sample. */ uint32 MeanSamplePeriod; /// Date of last sample (low rez) uint32 LastSampleTimeStamp; /// Date of last sample (high rez) TTime LastHighRezTimeStamp; /// The vector of buffered samples vector Samples; TGraphVarInfo() : MeanSamplePeriod(1000), LastSampleTimeStamp(0), LastHighRezTimeStamp(0) { } }; /// The list of variable to graph (build from service config file var 'GraphVars') vector _GraphVars; /// Date of last graph public: CAdminExecutorServiceClient() : _DontUseShardOrders(false), _LastStateReport(0), _LastStatusStringReport(0), _ProcessUsedMemory(0) { CAdminExecutorServiceClientSkel::init(this); } std::string makeServiceAlias() { string serviceAlias = IService::getInstance()->getServiceAliasName(); if (serviceAlias.empty()) { serviceAlias = IService::getInstance()->getHostName()+"."+IService::getInstance()->getServiceUnifiedName(); } return serviceAlias; } string getModuleManifest() const { uint32 pid = getpid (); string serviceAlias = _ServiceAlias; CSString manifest; manifest << "LongName=" << IService::getInstance()->getServiceLongName() << " ShortName=" << IService::getInstance()->getServiceShortName() << " AliasName=" << serviceAlias << " PID=" << pid << " DontUseShardOrders=" << _DontUseShardOrders; return manifest; } bool initModule(const TParsedCommandLine &pcl) { if (!CModuleBase::initModule(pcl)) return false; // try to read the config file IService *service = IService::getInstance(); if (service == NULL) { nlwarning("Failed to get the IService singleton instance"); return false; } CConfigFile::CVar *gv = service->ConfigFile.getVarPtr("GraphVars"); if (gv) { _GraphVars.clear(); for (uint i =0; isize()/2; ++i) { TGraphVarInfo gvi; gvi.VarName = gv->asString(i*2); gvi.MeanSamplePeriod = max(1, gv->asInt((i*2)+1)); _GraphVars.push_back(gvi); } } // precompute the service name _ServiceAlias = makeServiceAlias(); // loop for an optional 'dontUseShardOrders' flag in init params const TParsedCommandLine *duso = pcl.getParam("dontUseShardOrders"); if (duso != NULL) _DontUseShardOrders = (duso->ParamValue == "true" || duso->ParamName == "1"); return true; } void onModuleUp(IModuleProxy *proxy) { if (proxy->getModuleClassName() == "AdminExecutorService") { nldebug("CAdminExecutorServiceClient : admin executor service up as '%s'", proxy->getModuleName().c_str()); // we found the manager of AES if (_AdminExecutorService != NULL) { nlwarning("CAdminExecutorServiceClient : admin executor service already known as '%s', replacing with new one", _AdminExecutorService->getModuleName().c_str()); } _AdminExecutorService = proxy; // // send basic service info to AES // CAdminExecutorServiceProxy aes(proxy); // // uint32 pid = getpid (); // // string serviceAlias = IService::getInstance()->getServiceAliasName(); // if (serviceAlias.empty()) // serviceAlias = getModuleFullyQualifiedName(); // // aes.serviceConnected(this, // IService::getInstance()->getServiceLongName(), // IService::getInstance()->getServiceShortName(), // serviceAlias, // pid); // for resend of the current status to the new AES _LastSentStatus = ""; sendServiceStatus(); } } void onModuleDown(IModuleProxy *proxy) { if (proxy == _AdminExecutorService) { nldebug("CAdminExecutorServiceClient : admin executor service '%s' is down", proxy->getModuleName().c_str()); _AdminExecutorService = NULL; } } void onModuleUpdate() { H_AUTO(CAdminExecutorServiceClient_onModuleUpdate); uint32 now = CTime::getSecondsSince1970(); TTime timer = CTime::getLocalTime(); // update every HR variables for (uint i=0; i<_GraphVars.size(); ++i) { if (_GraphVars[i].MeanSamplePeriod < 1000) { // this is a HR var TGraphVarInfo &gvi = _GraphVars[i]; if (gvi.LastHighRezTimeStamp + gvi.MeanSamplePeriod < timer) { // it's time to get a sample // create a sample gvi.Samples.push_back(TGraphSample()); TGraphSample &gs = gvi.Samples.back(); // inialise it gs.TimeStamp = now; gs.HighRezTimeStamp = timer; IVariable *var = dynamic_cast(ICommand::getCommand(gvi.VarName)); if (var != NULL) gs.SampleValue = atof(var->toString().c_str()); } } } if (_LastStateReport != now) { if ((now & 0xf) == 0) { // every 16 seconds because very slow IVariable *var = dynamic_cast(ICommand::getCommand("ProcessUsedMemory")); if (var != NULL) NLMISC::fromString(var->toString(), _ProcessUsedMemory); } // at least one second as passed, check for updates to send to // AES TGraphDatas graphDatas; graphDatas.setCurrentTime(now); THighRezDatas highRezDatas; highRezDatas.setServiceAlias(_ServiceAlias); highRezDatas.setCurrentTime(now); vector &datas = graphDatas.getDatas(); for (uint i=0; i<_GraphVars.size(); ++i) { if (_GraphVars[i].LastSampleTimeStamp+(_GraphVars[i].MeanSamplePeriod/1000) < now) { TGraphVarInfo &gvi = _GraphVars[i]; // it's time to send update for this var // create a new sample entry datas.push_back(TGraphData()); // and fill it TGraphData &gd = datas.back(); gd.setServiceAlias(_ServiceAlias); gd.setVarName(gvi.VarName); gd.setSamplePeriod(max(uint32(1), uint32(gvi.MeanSamplePeriod/1000))); if (gvi.Samples.empty()) { // no sample collected yet, just ask a new one IVariable *var = dynamic_cast(ICommand::getCommand(gvi.VarName)); if (var != NULL) gd.setValue(atof(var->toString().c_str())); } else { // we have some sample collected, just use the last one gd.setValue(gvi.Samples.back().SampleValue); } // if it's a high rez sampler, send the complete buffer if (gvi.MeanSamplePeriod < 1000 && _AdminExecutorService != NULL) { // build the message highRezDatas.setVarName(gvi.VarName); highRezDatas.getDatas().clear(); for (uint j=0; jgetServiceStatusString(); } if ((status != _LastSentStatus || (now - _LastStatusStringReport) > MAX_DELAY_BETWEEN_REPORT) && _AdminExecutorService != NULL) { CAdminExecutorServiceProxy aes(_AdminExecutorService); aes.serviceStatusUpdate(this, status); _LastSentStatus = status; _LastStatusStringReport = now; } } /////////////////////////////////////////////////////////////// // implementation from Admin executor service client /////////////////////////////////////////////////////////////// // execute a command and return the result. virtual void serviceCmd(NLNET::IModuleProxy *sender, uint32 commandId, const std::string &command) { // create a displayer to gather the output of the command class CStringDisplayer: public IDisplayer { public: virtual void doDisplay( const CLog::TDisplayInfo& args, const char *message) { _Data += message; } std::string _Data; }; CStringDisplayer stringDisplayer; IService::getInstance()->CommandLog.addDisplayer(&stringDisplayer); // retrieve the command from the input message and execute it nlinfo ("ADMIN: Executing command from network : '%s'", command.c_str()); ICommand::execute (command, IService::getInstance()->CommandLog); // unhook our displayer as it's work is now done IService::getInstance()->CommandLog.removeDisplayer(&stringDisplayer); string serviceAlias = IService::getInstance()->getServiceAliasName(); if (serviceAlias.empty()) serviceAlias = getModuleFullyQualifiedName(); // return the result to AES CAdminExecutorServiceProxy aes(sender); aes.commandResult(this, commandId, serviceAlias, stringDisplayer._Data); } // execute a command without result virtual void serviceCmdNoReturn(NLNET::IModuleProxy *sender, const std::string &command) { // retrieve the command from the input message and execute it nlinfo ("ADMIN: Executing command from network : '%s'", command.c_str()); ICommand::execute (command, IService::getInstance()->CommandLog); } }; NLNET_REGISTER_MODULE_FACTORY(CAdminExecutorServiceClient, "AdminExecutorServiceClient"); } // namespace ADMIN