1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "nel/misc/singleton.h"
20 #include "nel/net/module.h"
21 #include "nel/net/module_builder_parts.h"
22 #include "nel/net/unified_network.h"
23 #include "nel/net/service.h"
25 #include "admin_modules_itf.h"
28 using namespace NLMISC
;
29 using namespace NLNET
;
31 void aesclient_forceLink() {}
35 class CAdminExecutorServiceClient
36 : /*public CManualSingleton<CAdminExecutorService>,*/
37 public CEmptyModuleServiceBehav
<CEmptyModuleCommBehav
<CEmptySocketBehav
<CModuleBase
> > >,
38 public CAdminExecutorServiceClientSkel
43 // Maximum time without sending report string (a kind of 'keep alive')
44 MAX_DELAY_BETWEEN_REPORT
= 30, // 30 seconds
47 /// Flag to inform AES that we don't want to be affected by shard orders
48 bool _DontUseShardOrders
;
50 /// Admin executor service module
51 TModuleProxyPtr _AdminExecutorService
;
53 /// Date of last state reporting to AES
54 uint32 _LastStateReport
;
56 /// Last date of status string update
57 uint32 _LastStatusStringReport
;
58 /// Last status string sent (to avoid double send)
59 string _LastSentStatus
;
61 /// The service alias (must be an unique name)
64 /// A cache of the value because reading it is very slow
65 uint32 _ProcessUsedMemory
;
69 // The date of the sample (in second)
71 // The date of the sample (in ms)
72 TTime HighRezTimeStamp
;
78 /// Name of the graphed var
80 /** Mean time between two sample in ms
81 * (in fact, if will be the min period)
82 * Set it to 1 to have a sample at each tick
83 * If the period is set less than 1000 ms,
84 * then the var is considered 'high rez'.
85 * Otherwise, the period is rounded at the
86 * nearest integer second.
87 * For 'high rez' var, the service buffer
88 * the relative timestamp in ms at each
89 * tick loop and send update every seconds
91 * In addition, HighRez var are also sent
92 * every second as normal sample.
94 uint32 MeanSamplePeriod
;
96 /// Date of last sample (low rez)
97 uint32 LastSampleTimeStamp
;
98 /// Date of last sample (high rez)
99 TTime LastHighRezTimeStamp
;
101 /// The vector of buffered samples
102 vector
<TGraphSample
> Samples
;
105 : MeanSamplePeriod(1000),
106 LastSampleTimeStamp(0),
107 LastHighRezTimeStamp(0)
112 /// The list of variable to graph (build from service config file var 'GraphVars')
113 vector
<TGraphVarInfo
> _GraphVars
;
115 /// Date of last graph
119 CAdminExecutorServiceClient()
120 : _DontUseShardOrders(false),
122 _LastStatusStringReport(0),
123 _ProcessUsedMemory(0)
125 CAdminExecutorServiceClientSkel::init(this);
129 std::string
makeServiceAlias()
131 string serviceAlias
= IService::getInstance()->getServiceAliasName();
132 if (serviceAlias
.empty())
134 serviceAlias
= IService::getInstance()->getHostName()+"."+IService::getInstance()->getServiceUnifiedName();
139 string
getModuleManifest() const
141 uint32 pid
= getpid ();
143 string serviceAlias
= _ServiceAlias
;
147 manifest
<< "LongName=" << IService::getInstance()->getServiceLongName()
148 << " ShortName=" << IService::getInstance()->getServiceShortName()
149 << " AliasName=" << serviceAlias
151 << " DontUseShardOrders=" << _DontUseShardOrders
;
156 bool initModule(const TParsedCommandLine
&pcl
)
158 if (!CModuleBase::initModule(pcl
))
161 // try to read the config file
162 IService
*service
= IService::getInstance();
165 nlwarning("Failed to get the IService singleton instance");
169 CConfigFile::CVar
*gv
= service
->ConfigFile
.getVarPtr("GraphVars");
173 for (uint i
=0; i
<gv
->size()/2; ++i
)
177 gvi
.VarName
= gv
->asString(i
*2);
178 gvi
.MeanSamplePeriod
= max(1, gv
->asInt((i
*2)+1));
180 _GraphVars
.push_back(gvi
);
184 // precompute the service name
185 _ServiceAlias
= makeServiceAlias();
187 // loop for an optional 'dontUseShardOrders' flag in init params
188 const TParsedCommandLine
*duso
= pcl
.getParam("dontUseShardOrders");
190 _DontUseShardOrders
= (duso
->ParamValue
== "true" || duso
->ParamName
== "1");
196 void onModuleUp(IModuleProxy
*proxy
)
198 if (proxy
->getModuleClassName() == "AdminExecutorService")
200 nldebug("CAdminExecutorServiceClient : admin executor service up as '%s'", proxy
->getModuleName().c_str());
201 // we found the manager of AES
202 if (_AdminExecutorService
!= NULL
)
204 nlwarning("CAdminExecutorServiceClient : admin executor service already known as '%s', replacing with new one", _AdminExecutorService
->getModuleName().c_str());
206 _AdminExecutorService
= proxy
;
208 // // send basic service info to AES
209 // CAdminExecutorServiceProxy aes(proxy);
211 // uint32 pid = getpid ();
213 // string serviceAlias = IService::getInstance()->getServiceAliasName();
214 // if (serviceAlias.empty())
215 // serviceAlias = getModuleFullyQualifiedName();
217 // aes.serviceConnected(this,
218 // IService::getInstance()->getServiceLongName(),
219 // IService::getInstance()->getServiceShortName(),
222 // for resend of the current status to the new AES
223 _LastSentStatus
= "";
228 void onModuleDown(IModuleProxy
*proxy
)
230 if (proxy
== _AdminExecutorService
)
232 nldebug("CAdminExecutorServiceClient : admin executor service '%s' is down", proxy
->getModuleName().c_str());
234 _AdminExecutorService
= NULL
;
238 void onModuleUpdate()
240 H_AUTO(CAdminExecutorServiceClient_onModuleUpdate
);
242 uint32 now
= CTime::getSecondsSince1970();
243 TTime timer
= CTime::getLocalTime();
245 // update every HR variables
246 for (uint i
=0; i
<_GraphVars
.size(); ++i
)
248 if (_GraphVars
[i
].MeanSamplePeriod
< 1000)
251 TGraphVarInfo
&gvi
= _GraphVars
[i
];
252 if (gvi
.LastHighRezTimeStamp
+ gvi
.MeanSamplePeriod
< timer
)
254 // it's time to get a sample
256 gvi
.Samples
.push_back(TGraphSample());
257 TGraphSample
&gs
= gvi
.Samples
.back();
260 gs
.HighRezTimeStamp
= timer
;
261 IVariable
*var
= dynamic_cast<IVariable
*>(ICommand::getCommand(gvi
.VarName
));
263 NLMISC::fromString(var
->toString(), gs
.SampleValue
);
268 if (_LastStateReport
!= now
)
271 if ((now
& 0xf) == 0)
273 // every 16 seconds because very slow
275 // FIXME: This is too slow
276 IVariable *var = dynamic_cast<IVariable*>(ICommand::getCommand("ProcessUsedMemory"));
278 NLMISC::fromString(var->toString(), _ProcessUsedMemory);
280 _ProcessUsedMemory
= 0;
283 // at least one second as passed, check for updates to send to
286 TGraphDatas graphDatas
;
287 graphDatas
.setCurrentTime(now
);
289 THighRezDatas highRezDatas
;
290 highRezDatas
.setServiceAlias(_ServiceAlias
);
291 highRezDatas
.setCurrentTime(now
);
293 vector
<TGraphData
> &datas
= graphDatas
.getDatas();
295 for (uint i
=0; i
<_GraphVars
.size(); ++i
)
297 if (_GraphVars
[i
].LastSampleTimeStamp
+(_GraphVars
[i
].MeanSamplePeriod
/1000) < now
)
299 TGraphVarInfo
&gvi
= _GraphVars
[i
];
300 // it's time to send update for this var
301 // create a new sample entry
302 datas
.push_back(TGraphData());
304 TGraphData
&gd
= datas
.back();
305 gd
.setServiceAlias(_ServiceAlias
);
306 gd
.setVarName(gvi
.VarName
);
307 gd
.setSamplePeriod(max(uint32(1), uint32(gvi
.MeanSamplePeriod
/1000)));
308 if (gvi
.Samples
.empty())
310 // no sample collected yet, just ask a new one
311 IVariable
*var
= dynamic_cast<IVariable
*>(ICommand::getCommand(gvi
.VarName
));
315 NLMISC::fromString(var
->toString(), val
);
321 // we have some sample collected, just use the last one
322 gd
.setValue(gvi
.Samples
.back().SampleValue
);
325 // if it's a high rez sampler, send the complete buffer
326 if (gvi
.MeanSamplePeriod
< 1000 && _AdminExecutorService
!= NULL
)
329 highRezDatas
.setVarName(gvi
.VarName
);
330 highRezDatas
.getDatas().clear();
332 for (uint j
=0; j
<gvi
.Samples
.size(); ++j
)
334 highRezDatas
.getDatas().push_back(THighRezData());
335 THighRezData
&hrd
= highRezDatas
.getDatas().back();
336 hrd
.setSampleTick(gvi
.Samples
[j
].HighRezTimeStamp
);
337 hrd
.setValue(gvi
.Samples
[j
].SampleValue
);
340 if (!highRezDatas
.getDatas().empty() && _AdminExecutorService
!= NULL
)
342 // send the high rez data
343 CAdminExecutorServiceProxy
aes(_AdminExecutorService
);
344 aes
.highRezGraphUpdate(this, highRezDatas
);
347 // we don't send normal update for high rez sampler
351 // update the time stamp
352 gvi
.LastSampleTimeStamp
= now
;
358 // if we have some data to send, send them
359 if (!datas
.empty() && _AdminExecutorService
!= NULL
)
361 CAdminExecutorServiceProxy
aes(_AdminExecutorService
);
362 aes
.graphUpdate(this, graphDatas
);
365 // update the last report date
366 _LastStateReport
= now
;
369 // send an update of the status (if needed)
373 void sendServiceStatus()
376 uint32 now
= NLMISC::CTime::getSecondsSince1970();
378 status
<< "\tServiceAlias=" << _ServiceAlias
;
380 // build the status string
381 IVariable
*var
= dynamic_cast<IVariable
*>(ICommand::getCommand("State"));
383 status
<< "\tState=" <<var
->toString();
385 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("UserSpeedLoop"));
387 status
<< "\tUserSpeedLoop=" <<var
->toString();
389 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("TickSpeedLoop"));
391 status
<< "\tTickSpeedLoop=" <<var
->toString();
393 if (_ProcessUsedMemory
!= 0)
394 status
<< "\tProcessUsedMemory=" <<_ProcessUsedMemory
;
396 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("Uptime"));
398 status
<< "\tUpTime=" <<var
->toString();
400 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("NbPlayers"));
402 status
<< "\tNbPlayers=" <<var
->toString();
404 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("NbEntities"));
406 status
<< "\tNbEntities=" <<var
->toString();
408 var
= dynamic_cast<IVariable
*>(ICommand::getCommand("LocalEntities"));
410 status
<< "\tLocalEntities=" <<var
->toString();
412 uint32 shardId
= IService::getInstance()->getShardId();
413 status
<< "\tShardId=" <<toString(shardId
);
415 // add any service specific info
416 if (IService::isServiceInitialized())
418 status
<< "\t" << IService::getInstance()->getServiceStatusString();
421 if ((status
!= _LastSentStatus
|| (now
- _LastStatusStringReport
) > MAX_DELAY_BETWEEN_REPORT
)
422 && _AdminExecutorService
!= NULL
)
424 CAdminExecutorServiceProxy
aes(_AdminExecutorService
);
425 aes
.serviceStatusUpdate(this, status
);
427 _LastSentStatus
= status
;
428 _LastStatusStringReport
= now
;
432 ///////////////////////////////////////////////////////////////
433 // implementation from Admin executor service client
434 ///////////////////////////////////////////////////////////////
436 // execute a command and return the result.
437 virtual void serviceCmd(NLNET::IModuleProxy
*sender
, uint32 commandId
, const std::string
&command
)
439 // create a displayer to gather the output of the command
440 class CStringDisplayer
: public IDisplayer
443 virtual void doDisplay( const CLog::TDisplayInfo
& args
, const char *message
)
450 CStringDisplayer stringDisplayer
;
451 IService::getInstance()->CommandLog
.addDisplayer(&stringDisplayer
);
453 // retrieve the command from the input message and execute it
454 nlinfo ("ADMIN: Executing command from network : '%s'", command
.c_str());
455 ICommand::execute (command
, IService::getInstance()->CommandLog
);
457 // unhook our displayer as it's work is now done
458 IService::getInstance()->CommandLog
.removeDisplayer(&stringDisplayer
);
460 string serviceAlias
= IService::getInstance()->getServiceAliasName();
461 if (serviceAlias
.empty())
462 serviceAlias
= getModuleFullyQualifiedName();
464 // return the result to AES
465 CAdminExecutorServiceProxy
aes(sender
);
466 aes
.commandResult(this, commandId
, serviceAlias
, stringDisplayer
._Data
);
469 // execute a command without result
470 virtual void serviceCmdNoReturn(NLNET::IModuleProxy
*sender
, const std::string
&command
)
472 // retrieve the command from the input message and execute it
473 nlinfo ("ADMIN: Executing command from network : '%s'", command
.c_str());
474 ICommand::execute (command
, IService::getInstance()->CommandLog
);
478 NLNET_REGISTER_MODULE_FACTORY(CAdminExecutorServiceClient
, "AdminExecutorServiceClient");