2012-11-22 03:43:21 +00:00
/ *
* Copyright ( c ) Contributors , http : //opensimulator.org/
* See CONTRIBUTORS . TXT for a full list of copyright holders .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ` ` AS IS ' ' AND ANY
* EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
* LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* /
using System ;
2012-11-22 04:05:09 +00:00
using System.Collections.Generic ;
2012-11-23 04:40:49 +00:00
using System.Diagnostics ;
2012-11-22 04:45:53 +00:00
using System.IO ;
2014-11-03 23:49:11 +00:00
using System.Linq ;
2012-11-22 04:45:53 +00:00
using System.Reflection ;
2018-09-29 16:42:12 +00:00
using System.Runtime ;
2012-11-22 03:43:21 +00:00
using System.Text ;
2012-11-22 04:45:53 +00:00
using System.Text.RegularExpressions ;
2012-11-23 04:40:49 +00:00
using System.Threading ;
2012-11-22 04:45:53 +00:00
using log4net ;
using log4net.Appender ;
using log4net.Core ;
using log4net.Repository ;
using Nini.Config ;
2012-11-22 04:05:09 +00:00
using OpenSim.Framework.Console ;
2012-11-23 04:40:49 +00:00
using OpenSim.Framework.Monitoring ;
2012-11-22 03:43:21 +00:00
namespace OpenSim.Framework.Servers
{
public class ServerBase
{
2012-11-22 04:45:53 +00:00
private static readonly ILog m_log = LogManager . GetLogger ( MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2012-11-22 05:48:41 +00:00
public IConfigSource Config { get ; protected set ; }
2012-11-22 04:05:09 +00:00
/// <summary>
/// Console to be used for any command line output. Can be null, in which case there should be no output.
/// </summary>
protected ICommandConsole m_console ;
2012-11-22 04:45:53 +00:00
protected OpenSimAppender m_consoleAppender ;
2017-01-05 19:07:37 +00:00
protected FileAppender m_logFileAppender ;
2017-02-24 01:33:02 +00:00
protected FileAppender m_statsLogFileAppender ;
2012-11-22 04:45:53 +00:00
protected DateTime m_startuptime ;
protected string m_startupDirectory = Environment . CurrentDirectory ;
2012-11-22 05:14:08 +00:00
protected string m_pidFile = String . Empty ;
2013-06-17 21:39:00 +00:00
protected ServerStatsCollector m_serverStatsCollector ;
2012-11-22 03:43:21 +00:00
/// <summary>
2012-11-22 04:45:53 +00:00
/// Server version information. Usually VersionInfo + information about git commit, operating system, etc.
2012-11-22 03:43:21 +00:00
/// </summary>
2012-11-22 04:45:53 +00:00
protected string m_version ;
2012-11-22 03:43:21 +00:00
public ServerBase ( )
{
m_startuptime = DateTime . Now ;
2012-11-22 04:45:53 +00:00
m_version = VersionInfo . Version ;
EnhanceVersionInformation ( ) ;
}
2012-11-22 05:14:08 +00:00
protected void CreatePIDFile ( string path )
{
2013-04-09 21:38:47 +00:00
if ( File . Exists ( path ) )
m_log . ErrorFormat (
2017-01-05 19:07:37 +00:00
"[SERVER BASE]: Previous pid file {0} still exists on startup. Possibly previously unclean shutdown." ,
2013-04-09 21:38:47 +00:00
path ) ;
2012-11-22 05:14:08 +00:00
try
{
string pidstring = System . Diagnostics . Process . GetCurrentProcess ( ) . Id . ToString ( ) ;
using ( FileStream fs = File . Create ( path ) )
{
Byte [ ] buf = Encoding . ASCII . GetBytes ( pidstring ) ;
fs . Write ( buf , 0 , buf . Length ) ;
}
m_pidFile = path ;
m_log . InfoFormat ( "[SERVER BASE]: Created pid file {0}" , m_pidFile ) ;
}
catch ( Exception e )
{
m_log . Warn ( string . Format ( "[SERVER BASE]: Could not create PID file at {0} " , path ) , e ) ;
}
}
2017-01-05 19:07:37 +00:00
2012-11-22 05:14:08 +00:00
protected void RemovePIDFile ( )
{
if ( m_pidFile ! = String . Empty )
{
try
{
File . Delete ( m_pidFile ) ;
}
catch ( Exception e )
{
m_log . Error ( string . Format ( "[SERVER BASE]: Error whilst removing {0} " , m_pidFile ) , e ) ;
}
m_pidFile = String . Empty ;
}
}
2013-03-14 23:39:15 +00:00
/// <summary>
/// Log information about the circumstances in which we're running (OpenSimulator version number, CLR details,
/// etc.).
/// </summary>
public void LogEnvironmentInformation ( )
{
// FIXME: This should be done down in ServerBase but we need to sort out and refactor the log4net
// XmlConfigurator calls first accross servers.
m_log . InfoFormat ( "[SERVER BASE]: Starting in {0}" , m_startupDirectory ) ;
m_log . InfoFormat ( "[SERVER BASE]: OpenSimulator version: {0}" , m_version ) ;
// clr version potentially is more confusing than helpful, since it doesn't tell us if we're running under Mono/MS .NET and
// the clr version number doesn't match the project version number under Mono.
//m_log.Info("[STARTUP]: Virtual machine runtime version: " + Environment.Version + Environment.NewLine);
m_log . InfoFormat (
"[SERVER BASE]: Operating system version: {0}, .NET platform {1}, {2}-bit" ,
2020-03-18 17:41:29 +00:00
Environment . OSVersion , Environment . OSVersion . Platform , Environment . Is64BitProcess ? "64" : "32" ) ;
2013-03-14 23:39:15 +00:00
}
2012-11-22 04:45:53 +00:00
public void RegisterCommonAppenders ( IConfig startupConfig )
{
ILoggerRepository repository = LogManager . GetRepository ( ) ;
IAppender [ ] appenders = repository . GetAppenders ( ) ;
foreach ( IAppender appender in appenders )
{
if ( appender . Name = = "Console" )
{
m_consoleAppender = ( OpenSimAppender ) appender ;
}
else if ( appender . Name = = "LogFileAppender" )
{
m_logFileAppender = ( FileAppender ) appender ;
}
2017-02-24 01:33:02 +00:00
else if ( appender . Name = = "StatsLogFileAppender" )
{
m_statsLogFileAppender = ( FileAppender ) appender ;
}
2012-11-22 04:45:53 +00:00
}
if ( null = = m_consoleAppender )
{
Notice ( "No appender named Console found (see the log4net config file for this executable)!" ) ;
}
else
{
// FIXME: This should be done through an interface rather than casting.
m_consoleAppender . Console = ( ConsoleBase ) m_console ;
2017-01-05 19:07:37 +00:00
2012-11-22 04:45:53 +00:00
// If there is no threshold set then the threshold is effectively everything.
if ( null = = m_consoleAppender . Threshold )
m_consoleAppender . Threshold = Level . All ;
2017-01-05 19:07:37 +00:00
2012-11-22 04:45:53 +00:00
Notice ( String . Format ( "Console log level is {0}" , m_consoleAppender . Threshold ) ) ;
}
if ( m_logFileAppender ! = null & & startupConfig ! = null )
{
string cfgFileName = startupConfig . GetString ( "LogFile" , null ) ;
if ( cfgFileName ! = null )
{
m_logFileAppender . File = cfgFileName ;
m_logFileAppender . ActivateOptions ( ) ;
}
2012-11-22 05:14:08 +00:00
m_log . InfoFormat ( "[SERVER BASE]: Logging started to file {0}" , m_logFileAppender . File ) ;
2017-02-24 01:33:02 +00:00
}
if ( m_statsLogFileAppender ! = null & & startupConfig ! = null )
{
string cfgStatsFileName = startupConfig . GetString ( "StatsLogFile" , null ) ;
if ( cfgStatsFileName ! = null )
{
m_statsLogFileAppender . File = cfgStatsFileName ;
m_statsLogFileAppender . ActivateOptions ( ) ;
}
m_log . InfoFormat ( "[SERVER BASE]: Stats Logging started to file {0}" , m_statsLogFileAppender . File ) ;
2012-11-22 04:45:53 +00:00
}
2012-11-22 03:43:21 +00:00
}
2012-11-22 04:11:03 +00:00
/// <summary>
/// Register common commands once m_console has been set if it is going to be set
/// </summary>
public void RegisterCommonCommands ( )
{
if ( m_console = = null )
return ;
2012-11-22 04:45:53 +00:00
m_console . Commands . AddCommand (
"General" , false , "show info" , "show info" , "Show general information about the server" , HandleShow ) ;
2012-11-23 04:19:08 +00:00
m_console . Commands . AddCommand (
"General" , false , "show version" , "show version" , "Show server version" , HandleShow ) ;
2012-11-22 04:11:03 +00:00
m_console . Commands . AddCommand (
"General" , false , "show uptime" , "show uptime" , "Show server uptime" , HandleShow ) ;
2012-11-22 04:50:09 +00:00
m_console . Commands . AddCommand (
2017-01-05 19:07:37 +00:00
"General" , false , "get log level" , "get log level" , "Get the current console logging level" ,
2012-11-22 04:57:45 +00:00
( mod , cmd ) = > ShowLogLevel ( ) ) ;
m_console . Commands . AddCommand (
2017-01-05 19:07:37 +00:00
"General" , false , "set log level" , "set log level <level>" ,
2012-11-22 04:57:45 +00:00
"Set the console logging level for this session." , HandleSetLogLevel ) ;
2012-11-22 05:48:41 +00:00
m_console . Commands . AddCommand (
"General" , false , "config set" ,
"config set <section> <key> <value>" ,
"Set a config option. In most cases this is not useful since changed parameters are not dynamically reloaded. Neither do changed parameters persist - you will have to change a config file manually and restart." , HandleConfig ) ;
m_console . Commands . AddCommand (
"General" , false , "config get" ,
"config get [<section>] [<key>]" ,
"Synonym for config show" ,
HandleConfig ) ;
2017-01-05 19:07:37 +00:00
2012-11-22 05:48:41 +00:00
m_console . Commands . AddCommand (
"General" , false , "config show" ,
"config show [<section>] [<key>]" ,
2017-01-05 19:07:37 +00:00
"Show config information" ,
2012-11-22 05:48:41 +00:00
"If neither section nor field are specified, then the whole current configuration is printed." + Environment . NewLine
+ "If a section is given but not a field, then all fields in that section are printed." ,
2017-01-05 19:07:37 +00:00
HandleConfig ) ;
2012-11-22 05:48:41 +00:00
m_console . Commands . AddCommand (
"General" , false , "config save" ,
"config save <path>" ,
"Save current configuration to a file at the given path" , HandleConfig ) ;
2012-11-22 05:57:20 +00:00
m_console . Commands . AddCommand (
"General" , false , "command-script" ,
"command-script <script>" ,
"Run a command script from file" , HandleScript ) ;
2012-11-23 04:27:04 +00:00
2012-11-23 04:40:49 +00:00
m_console . Commands . AddCommand (
"General" , false , "show threads" ,
"show threads" ,
"Show thread status" , HandleShow ) ;
m_console . Commands . AddCommand (
2013-08-05 22:06:17 +00:00
"Debug" , false , "threads abort" ,
2012-11-23 04:40:49 +00:00
"threads abort <thread-id>" ,
"Abort a managed thread. Use \"show threads\" to find possible threads." , HandleThreadsAbort ) ;
m_console . Commands . AddCommand (
"General" , false , "threads show" ,
"threads show" ,
"Show thread status. Synonym for \"show threads\"" ,
( string module , string [ ] args ) = > Notice ( GetThreadsReport ( ) ) ) ;
2013-08-05 22:04:36 +00:00
m_console . Commands . AddCommand (
"Debug" , false , "debug threadpool set" ,
"debug threadpool set worker|iocp min|max <n>" ,
"Set threadpool parameters. For debug purposes." ,
HandleDebugThreadpoolSet ) ;
2013-08-05 23:00:12 +00:00
m_console . Commands . AddCommand (
"Debug" , false , "debug threadpool status" ,
"debug threadpool status" ,
"Show current debug threadpool parameters." ,
HandleDebugThreadpoolStatus ) ;
2017-01-05 19:07:37 +00:00
2014-01-16 13:08:45 +00:00
m_console . Commands . AddCommand (
"Debug" , false , "debug threadpool level" ,
"debug threadpool level 0.." + Util . MAX_THREADPOOL_LEVEL ,
"Turn on logging of activity in the main thread pool." ,
"Log levels:\n"
+ " 0 = no logging\n"
+ " 1 = only first line of stack trace; don't log common threads\n"
+ " 2 = full stack trace; don't log common threads\n"
+ " 3 = full stack trace, including common threads\n" ,
HandleDebugThreadpoolLevel ) ;
2013-08-05 23:00:12 +00:00
2015-05-12 18:54:25 +00:00
// m_console.Commands.AddCommand(
// "Debug", false, "show threadpool calls active",
// "show threadpool calls active",
// "Show details about threadpool calls that are still active (currently waiting or in progress)",
// HandleShowThreadpoolCallsActive);
2014-11-04 17:21:22 +00:00
m_console . Commands . AddCommand (
"Debug" , false , "show threadpool calls complete" ,
"show threadpool calls complete" ,
"Show details about threadpool calls that have been completed." ,
HandleShowThreadpoolCallsComplete ) ;
2014-11-03 23:49:11 +00:00
2012-11-23 04:27:04 +00:00
m_console . Commands . AddCommand (
2013-08-05 22:06:17 +00:00
"Debug" , false , "force gc" ,
2012-11-23 04:27:04 +00:00
"force gc" ,
"Manually invoke runtime garbage collection. For debugging purposes" ,
HandleForceGc ) ;
2013-06-17 21:39:00 +00:00
m_console . Commands . AddCommand (
"General" , false , "quit" ,
"quit" ,
"Quit the application" , ( mod , args ) = > Shutdown ( ) ) ;
m_console . Commands . AddCommand (
"General" , false , "shutdown" ,
"shutdown" ,
"Quit the application" , ( mod , args ) = > Shutdown ( ) ) ;
2013-08-05 18:22:47 +00:00
ChecksManager . RegisterConsoleCommands ( m_console ) ;
2013-06-17 21:39:00 +00:00
StatsManager . RegisterConsoleCommands ( m_console ) ;
}
public void RegisterCommonComponents ( IConfigSource configSource )
{
2017-05-30 07:20:58 +00:00
// IConfig networkConfig = configSource.Configs["Network"];
2017-01-05 19:07:37 +00:00
2013-06-17 21:39:00 +00:00
m_serverStatsCollector = new ServerStatsCollector ( ) ;
m_serverStatsCollector . Initialise ( configSource ) ;
m_serverStatsCollector . Start ( ) ;
2012-11-23 04:27:04 +00:00
}
2014-11-04 17:21:22 +00:00
private void HandleShowThreadpoolCallsActive ( string module , string [ ] args )
{
List < KeyValuePair < string , int > > calls = Util . GetFireAndForgetCallsInProgress ( ) . ToList ( ) ;
calls . Sort ( ( kvp1 , kvp2 ) = > kvp2 . Value . CompareTo ( kvp1 . Value ) ) ;
int namedCalls = 0 ;
ConsoleDisplayList cdl = new ConsoleDisplayList ( ) ;
foreach ( KeyValuePair < string , int > kvp in calls )
{
if ( kvp . Value > 0 )
{
cdl . AddRow ( kvp . Key , kvp . Value ) ;
namedCalls + = kvp . Value ;
}
}
cdl . AddRow ( "TOTAL NAMED" , namedCalls ) ;
long allQueuedCalls = Util . TotalQueuedFireAndForgetCalls ;
long allRunningCalls = Util . TotalRunningFireAndForgetCalls ;
cdl . AddRow ( "TOTAL QUEUED" , allQueuedCalls ) ;
cdl . AddRow ( "TOTAL RUNNING" , allRunningCalls ) ;
cdl . AddRow ( "TOTAL ANONYMOUS" , allQueuedCalls + allRunningCalls - namedCalls ) ;
cdl . AddRow ( "TOTAL ALL" , allQueuedCalls + allRunningCalls ) ;
MainConsole . Instance . Output ( cdl . ToString ( ) ) ;
}
private void HandleShowThreadpoolCallsComplete ( string module , string [ ] args )
2014-11-03 23:49:11 +00:00
{
List < KeyValuePair < string , int > > calls = Util . GetFireAndForgetCallsMade ( ) . ToList ( ) ;
calls . Sort ( ( kvp1 , kvp2 ) = > kvp2 . Value . CompareTo ( kvp1 . Value ) ) ;
2014-11-04 00:55:48 +00:00
int namedCallsMade = 0 ;
2014-11-03 23:49:11 +00:00
ConsoleDisplayList cdl = new ConsoleDisplayList ( ) ;
foreach ( KeyValuePair < string , int > kvp in calls )
{
cdl . AddRow ( kvp . Key , kvp . Value ) ;
2014-11-04 00:55:48 +00:00
namedCallsMade + = kvp . Value ;
2014-11-03 23:49:11 +00:00
}
2014-11-04 00:55:48 +00:00
cdl . AddRow ( "TOTAL NAMED" , namedCallsMade ) ;
long allCallsMade = Util . TotalFireAndForgetCallsMade ;
cdl . AddRow ( "TOTAL ANONYMOUS" , allCallsMade - namedCallsMade ) ;
cdl . AddRow ( "TOTAL ALL" , allCallsMade ) ;
2014-11-03 23:49:11 +00:00
MainConsole . Instance . Output ( cdl . ToString ( ) ) ;
}
2013-08-05 23:00:12 +00:00
private void HandleDebugThreadpoolStatus ( string module , string [ ] args )
{
int workerThreads , iocpThreads ;
ThreadPool . GetMinThreads ( out workerThreads , out iocpThreads ) ;
Notice ( "Min worker threads: {0}" , workerThreads ) ;
Notice ( "Min IOCP threads: {0}" , iocpThreads ) ;
ThreadPool . GetMaxThreads ( out workerThreads , out iocpThreads ) ;
Notice ( "Max worker threads: {0}" , workerThreads ) ;
Notice ( "Max IOCP threads: {0}" , iocpThreads ) ;
ThreadPool . GetAvailableThreads ( out workerThreads , out iocpThreads ) ;
Notice ( "Available worker threads: {0}" , workerThreads ) ;
2017-01-05 19:07:37 +00:00
Notice ( "Available IOCP threads: {0}" , iocpThreads ) ;
2013-08-05 23:00:12 +00:00
}
2013-08-05 22:04:36 +00:00
private void HandleDebugThreadpoolSet ( string module , string [ ] args )
{
if ( args . Length ! = 6 )
{
Notice ( "Usage: debug threadpool set worker|iocp min|max <n>" ) ;
return ;
}
int newThreads ;
if ( ! ConsoleUtil . TryParseConsoleInt ( m_console , args [ 5 ] , out newThreads ) )
return ;
string poolType = args [ 3 ] ;
string bound = args [ 4 ] ;
bool fail = false ;
int workerThreads , iocpThreads ;
if ( poolType = = "worker" )
{
if ( bound = = "min" )
{
ThreadPool . GetMinThreads ( out workerThreads , out iocpThreads ) ;
if ( ! ThreadPool . SetMinThreads ( newThreads , iocpThreads ) )
fail = true ;
}
else
{
ThreadPool . GetMaxThreads ( out workerThreads , out iocpThreads ) ;
if ( ! ThreadPool . SetMaxThreads ( newThreads , iocpThreads ) )
fail = true ;
}
}
else
{
if ( bound = = "min" )
{
ThreadPool . GetMinThreads ( out workerThreads , out iocpThreads ) ;
if ( ! ThreadPool . SetMinThreads ( workerThreads , newThreads ) )
fail = true ;
}
else
{
ThreadPool . GetMaxThreads ( out workerThreads , out iocpThreads ) ;
if ( ! ThreadPool . SetMaxThreads ( workerThreads , newThreads ) )
fail = true ;
}
}
2017-01-05 19:07:37 +00:00
2013-08-05 22:04:36 +00:00
if ( fail )
{
Notice ( "ERROR: Could not set {0} {1} threads to {2}" , poolType , bound , newThreads ) ;
}
else
{
int minWorkerThreads , maxWorkerThreads , minIocpThreads , maxIocpThreads ;
ThreadPool . GetMinThreads ( out minWorkerThreads , out minIocpThreads ) ;
ThreadPool . GetMaxThreads ( out maxWorkerThreads , out maxIocpThreads ) ;
Notice ( "Min worker threads now {0}" , minWorkerThreads ) ;
Notice ( "Min IOCP threads now {0}" , minIocpThreads ) ;
Notice ( "Max worker threads now {0}" , maxWorkerThreads ) ;
Notice ( "Max IOCP threads now {0}" , maxIocpThreads ) ;
}
}
2014-01-16 13:08:45 +00:00
private static void HandleDebugThreadpoolLevel ( string module , string [ ] cmdparams )
{
if ( cmdparams . Length < 4 )
{
MainConsole . Instance . Output ( "Usage: debug threadpool level 0.." + Util . MAX_THREADPOOL_LEVEL ) ;
return ;
}
string rawLevel = cmdparams [ 3 ] ;
int newLevel ;
if ( ! int . TryParse ( rawLevel , out newLevel ) )
{
2019-10-22 11:23:19 +00:00
MainConsole . Instance . Output ( "{0} is not a valid debug level" , rawLevel ) ;
2014-01-16 13:08:45 +00:00
return ;
}
if ( newLevel < 0 | | newLevel > Util . MAX_THREADPOOL_LEVEL )
{
2019-10-22 11:39:50 +00:00
MainConsole . Instance . Output ( "{0} is outside the valid debug level range of 0.." + Util . MAX_THREADPOOL_LEVEL , newLevel ) ;
2014-01-16 13:08:45 +00:00
return ;
}
Util . LogThreadPool = newLevel ;
2019-10-22 11:23:19 +00:00
MainConsole . Instance . Output ( "LogThreadPool set to {0}" , newLevel ) ;
2014-01-16 13:08:45 +00:00
}
2012-11-23 04:27:04 +00:00
private void HandleForceGc ( string module , string [ ] args )
{
Notice ( "Manually invoking runtime garbage collection" ) ;
2018-09-29 16:42:12 +00:00
GCSettings . LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode . CompactOnce ;
2012-11-23 04:27:04 +00:00
GC . Collect ( ) ;
2018-09-29 16:42:12 +00:00
GC . WaitForPendingFinalizers ( ) ;
GC . Collect ( ) ;
GCSettings . LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode . Default ;
2012-11-22 04:11:03 +00:00
}
2012-11-22 04:05:09 +00:00
public virtual void HandleShow ( string module , string [ ] cmd )
{
List < string > args = new List < string > ( cmd ) ;
args . RemoveAt ( 0 ) ;
string [ ] showParams = args . ToArray ( ) ;
switch ( showParams [ 0 ] )
{
2012-11-22 04:45:53 +00:00
case "info" :
ShowInfo ( ) ;
break ;
2012-11-23 04:19:08 +00:00
case "version" :
Notice ( GetVersionText ( ) ) ;
break ;
2012-11-22 04:05:09 +00:00
case "uptime" :
Notice ( GetUptimeReport ( ) ) ;
break ;
2012-11-23 04:40:49 +00:00
case "threads" :
Notice ( GetThreadsReport ( ) ) ;
break ;
2012-11-22 04:05:09 +00:00
}
}
2012-11-22 05:48:41 +00:00
/// <summary>
/// Change and load configuration file data.
/// </summary>
/// <param name="module"></param>
/// <param name="cmd"></param>
private void HandleConfig ( string module , string [ ] cmd )
{
List < string > args = new List < string > ( cmd ) ;
args . RemoveAt ( 0 ) ;
string [ ] cmdparams = args . ToArray ( ) ;
if ( cmdparams . Length > 0 )
{
string firstParam = cmdparams [ 0 ] . ToLower ( ) ;
2017-01-05 19:07:37 +00:00
2012-11-22 05:48:41 +00:00
switch ( firstParam )
{
case "set" :
if ( cmdparams . Length < 4 )
{
Notice ( "Syntax: config set <section> <key> <value>" ) ;
Notice ( "Example: config set ScriptEngine.DotNetEngine NumberOfScriptThreads 5" ) ;
}
else
{
IConfig c ;
IConfigSource source = new IniConfigSource ( ) ;
c = source . AddConfig ( cmdparams [ 1 ] ) ;
if ( c ! = null )
{
string _value = String . Join ( " " , cmdparams , 3 , cmdparams . Length - 3 ) ;
c . Set ( cmdparams [ 2 ] , _value ) ;
Config . Merge ( source ) ;
Notice ( "In section [{0}], set {1} = {2}" , c . Name , cmdparams [ 2 ] , _value ) ;
}
}
break ;
case "get" :
case "show" :
if ( cmdparams . Length = = 1 )
{
foreach ( IConfig config in Config . Configs )
{
Notice ( "[{0}]" , config . Name ) ;
string [ ] keys = config . GetKeys ( ) ;
foreach ( string key in keys )
Notice ( " {0} = {1}" , key , config . GetString ( key ) ) ;
}
}
else if ( cmdparams . Length = = 2 | | cmdparams . Length = = 3 )
{
IConfig config = Config . Configs [ cmdparams [ 1 ] ] ;
if ( config = = null )
{
Notice ( "Section \"{0}\" does not exist." , cmdparams [ 1 ] ) ;
break ;
}
else
{
if ( cmdparams . Length = = 2 )
{
Notice ( "[{0}]" , config . Name ) ;
foreach ( string key in config . GetKeys ( ) )
2017-01-05 19:07:37 +00:00
Notice ( " {0} = {1}" , key , config . GetString ( key ) ) ;
2012-11-22 05:48:41 +00:00
}
else
{
Notice (
2017-01-05 19:07:37 +00:00
"config get {0} {1} : {2}" ,
2012-11-22 05:48:41 +00:00
cmdparams [ 1 ] , cmdparams [ 2 ] , config . GetString ( cmdparams [ 2 ] ) ) ;
}
}
}
else
{
Notice ( "Syntax: config {0} [<section>] [<key>]" , firstParam ) ;
Notice ( "Example: config {0} ScriptEngine.DotNetEngine NumberOfScriptThreads" , firstParam ) ;
}
break ;
case "save" :
if ( cmdparams . Length < 2 )
{
Notice ( "Syntax: config save <path>" ) ;
return ;
}
string path = cmdparams [ 1 ] ;
Notice ( "Saving configuration file: {0}" , path ) ;
if ( Config is IniConfigSource )
{
IniConfigSource iniCon = ( IniConfigSource ) Config ;
iniCon . Save ( path ) ;
}
else if ( Config is XmlConfigSource )
{
XmlConfigSource xmlCon = ( XmlConfigSource ) Config ;
xmlCon . Save ( path ) ;
}
break ;
}
}
}
2012-11-22 04:57:45 +00:00
private void HandleSetLogLevel ( string module , string [ ] cmd )
2012-11-22 04:50:09 +00:00
{
2012-11-22 04:57:45 +00:00
if ( cmd . Length ! = 4 )
2012-11-22 04:50:09 +00:00
{
2012-11-22 04:57:45 +00:00
Notice ( "Usage: set log level <level>" ) ;
2012-11-22 04:50:09 +00:00
return ;
}
2012-11-22 04:57:45 +00:00
if ( null = = m_consoleAppender )
2012-11-22 04:50:09 +00:00
{
2012-11-22 04:57:45 +00:00
Notice ( "No appender named Console found (see the log4net config file for this executable)!" ) ;
return ;
2012-11-22 04:50:09 +00:00
}
2012-11-22 04:57:45 +00:00
string rawLevel = cmd [ 3 ] ;
2017-01-05 19:07:37 +00:00
2012-11-22 04:57:45 +00:00
ILoggerRepository repository = LogManager . GetRepository ( ) ;
Level consoleLevel = repository . LevelMap [ rawLevel ] ;
2017-01-05 19:07:37 +00:00
2012-11-22 04:57:45 +00:00
if ( consoleLevel ! = null )
m_consoleAppender . Threshold = consoleLevel ;
else
Notice (
2012-11-22 05:48:41 +00:00
"{0} is not a valid logging level. Valid logging levels are ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF" ,
rawLevel ) ;
2012-11-22 04:57:45 +00:00
ShowLogLevel ( ) ;
}
private void ShowLogLevel ( )
{
2020-04-15 10:48:52 +00:00
if ( null = = m_consoleAppender )
{
Notice ( "No appender named Console found (see the log4net config file for this executable)!" ) ;
return ;
}
2012-11-22 05:57:20 +00:00
Notice ( "Console log level is {0}" , m_consoleAppender . Threshold ) ;
}
protected virtual void HandleScript ( string module , string [ ] parms )
{
if ( parms . Length ! = 2 )
{
Notice ( "Usage: command-script <path-to-script" ) ;
return ;
}
RunCommandScript ( parms [ 1 ] ) ;
}
/// <summary>
/// Run an optional startup list of commands
/// </summary>
/// <param name="fileName"></param>
protected void RunCommandScript ( string fileName )
{
if ( m_console = = null )
return ;
if ( File . Exists ( fileName ) )
{
m_log . Info ( "[SERVER BASE]: Running " + fileName ) ;
using ( StreamReader readFile = File . OpenText ( fileName ) )
{
string currentCommand ;
while ( ( currentCommand = readFile . ReadLine ( ) ) ! = null )
{
currentCommand = currentCommand . Trim ( ) ;
if ( ! ( currentCommand = = ""
| | currentCommand . StartsWith ( ";" )
| | currentCommand . StartsWith ( "//" )
| | currentCommand . StartsWith ( "#" ) ) )
{
m_log . Info ( "[SERVER BASE]: Running '" + currentCommand + "'" ) ;
m_console . RunCommand ( currentCommand ) ;
}
}
}
}
2012-11-22 04:50:09 +00:00
}
2012-11-22 03:43:21 +00:00
/// <summary>
/// Return a report about the uptime of this server
/// </summary>
/// <returns></returns>
protected string GetUptimeReport ( )
{
2020-06-09 01:20:35 +00:00
StringBuilder sb = new StringBuilder ( 512 ) ;
sb . AppendFormat ( "Time now is {0}\n" , DateTime . Now . ToString ( "yyyy-MM-dd HH:mm:ss" ) ) ;
sb . AppendFormat ( "Server has been running since {0}, {1}\n" , m_startuptime . DayOfWeek , m_startuptime . ToString ( "yyyy-MM-dd HH:mm:ss" ) ) ;
sb . AppendFormat ( "That is an elapsed time of {0}\n" , DateTime . Now - m_startuptime ) ;
2012-11-22 03:43:21 +00:00
return sb . ToString ( ) ;
}
2012-11-22 04:05:09 +00:00
2012-11-22 04:45:53 +00:00
protected void ShowInfo ( )
{
Notice ( GetVersionText ( ) ) ;
2017-01-05 19:07:37 +00:00
Notice ( "Startup directory: " + m_startupDirectory ) ;
2012-11-22 04:45:53 +00:00
if ( null ! = m_consoleAppender )
2017-01-05 19:07:37 +00:00
Notice ( String . Format ( "Console log level: {0}" , m_consoleAppender . Threshold ) ) ;
2012-11-22 04:45:53 +00:00
}
/// <summary>
/// Enhance the version string with extra information if it's available.
/// </summary>
protected void EnhanceVersionInformation ( )
{
string buildVersion = string . Empty ;
// The subversion information is deprecated and will be removed at a later date
// Add subversion revision information if available
// Try file "svn_revision" in the current directory first, then the .svn info.
// This allows to make the revision available in simulators not running from the source tree.
// FIXME: Making an assumption about the directory we're currently in - we do this all over the place
// elsewhere as well
string gitDir = "../.git/" ;
string gitRefPointerPath = gitDir + "HEAD" ;
string svnRevisionFileName = "svn_revision" ;
string svnFileName = ".svn/entries" ;
string manualVersionFileName = ".version" ;
string inputLine ;
int strcmp ;
if ( File . Exists ( manualVersionFileName ) )
{
using ( StreamReader CommitFile = File . OpenText ( manualVersionFileName ) )
buildVersion = CommitFile . ReadLine ( ) ;
m_version + = buildVersion ? ? "" ;
}
else if ( File . Exists ( gitRefPointerPath ) )
{
2012-11-22 05:14:08 +00:00
// m_log.DebugFormat("[SERVER BASE]: Found {0}", gitRefPointerPath);
2012-11-22 04:45:53 +00:00
string rawPointer = "" ;
using ( StreamReader pointerFile = File . OpenText ( gitRefPointerPath ) )
rawPointer = pointerFile . ReadLine ( ) ;
2012-11-22 05:14:08 +00:00
// m_log.DebugFormat("[SERVER BASE]: rawPointer [{0}]", rawPointer);
2012-11-22 04:45:53 +00:00
Match m = Regex . Match ( rawPointer , "^ref: (.+)$" ) ;
if ( m . Success )
{
2012-11-22 05:14:08 +00:00
// m_log.DebugFormat("[SERVER BASE]: Matched [{0}]", m.Groups[1].Value);
2012-11-22 04:45:53 +00:00
string gitRef = m . Groups [ 1 ] . Value ;
string gitRefPath = gitDir + gitRef ;
if ( File . Exists ( gitRefPath ) )
{
2012-11-22 05:14:08 +00:00
// m_log.DebugFormat("[SERVER BASE]: Found gitRefPath [{0}]", gitRefPath);
2012-11-22 04:45:53 +00:00
using ( StreamReader refFile = File . OpenText ( gitRefPath ) )
{
string gitHash = refFile . ReadLine ( ) ;
m_version + = gitHash . Substring ( 0 , 7 ) ;
}
}
}
}
else
{
// Remove the else logic when subversion mirror is no longer used
if ( File . Exists ( svnRevisionFileName ) )
{
StreamReader RevisionFile = File . OpenText ( svnRevisionFileName ) ;
buildVersion = RevisionFile . ReadLine ( ) ;
2016-08-23 23:20:45 +00:00
buildVersion = buildVersion . Trim ( ) ;
2012-11-22 04:45:53 +00:00
RevisionFile . Close ( ) ;
}
if ( string . IsNullOrEmpty ( buildVersion ) & & File . Exists ( svnFileName ) )
{
StreamReader EntriesFile = File . OpenText ( svnFileName ) ;
inputLine = EntriesFile . ReadLine ( ) ;
while ( inputLine ! = null )
{
// using the dir svn revision at the top of entries file
strcmp = String . Compare ( inputLine , "dir" ) ;
if ( strcmp = = 0 )
{
buildVersion = EntriesFile . ReadLine ( ) ;
break ;
}
else
{
inputLine = EntriesFile . ReadLine ( ) ;
}
}
EntriesFile . Close ( ) ;
}
m_version + = string . IsNullOrEmpty ( buildVersion ) ? " " : ( "." + buildVersion + " " ) . Substring ( 0 , 6 ) ;
}
}
2013-01-23 22:12:48 +00:00
public string GetVersionText ( )
2012-11-22 04:45:53 +00:00
{
2017-01-05 19:07:37 +00:00
return String . Format ( "Version: {0} (SIMULATION/{1} - SIMULATION/{2})" ,
2015-10-30 23:01:35 +00:00
m_version , VersionInfo . SimulationServiceVersionSupportedMin , VersionInfo . SimulationServiceVersionSupportedMax ) ;
2012-11-22 04:45:53 +00:00
}
2012-11-22 04:05:09 +00:00
/// <summary>
2012-11-23 04:40:49 +00:00
/// Get a report about the registered threads in this server.
/// </summary>
protected string GetThreadsReport ( )
{
// This should be a constant field.
string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}" ;
StringBuilder sb = new StringBuilder ( ) ;
Watchdog . ThreadWatchdogInfo [ ] threads = Watchdog . GetThreadsInfo ( ) ;
sb . Append ( threads . Length + " threads are being tracked:" + Environment . NewLine ) ;
int timeNow = Environment . TickCount & Int32 . MaxValue ;
sb . AppendFormat ( reportFormat , "ID" , "NAME" , "LAST UPDATE (MS)" , "LIFETIME (MS)" , "PRIORITY" , "STATE" ) ;
sb . Append ( Environment . NewLine ) ;
foreach ( Watchdog . ThreadWatchdogInfo twi in threads )
{
Thread t = twi . Thread ;
2017-01-05 19:07:37 +00:00
2012-11-23 04:40:49 +00:00
sb . AppendFormat (
reportFormat ,
t . ManagedThreadId ,
t . Name ,
timeNow - twi . LastTick ,
timeNow - twi . FirstTick ,
t . Priority ,
t . ThreadState ) ;
sb . Append ( "\n" ) ;
}
2017-06-16 17:16:26 +00:00
sb . Append ( GetThreadPoolReport ( ) ) ;
2012-11-23 04:40:49 +00:00
2017-06-16 17:16:26 +00:00
sb . Append ( "\n" ) ;
2012-11-23 04:40:49 +00:00
int totalThreads = Process . GetCurrentProcess ( ) . Threads . Count ;
if ( totalThreads > 0 )
2017-06-16 17:16:26 +00:00
sb . AppendFormat ( "Total process threads active: {0}\n\n" , totalThreads ) ;
2013-06-17 22:57:10 +00:00
return sb . ToString ( ) ;
}
/// <summary>
/// Get a thread pool report.
/// </summary>
/// <returns></returns>
public static string GetThreadPoolReport ( )
{
2017-06-16 17:16:26 +00:00
StringBuilder sb = new StringBuilder ( ) ;
// framework pool is alwasy active
int maxWorkers ;
int minWorkers ;
int curWorkers ;
int maxComp ;
int minComp ;
int curComp ;
try
{
ThreadPool . GetMaxThreads ( out maxWorkers , out maxComp ) ;
ThreadPool . GetMinThreads ( out minWorkers , out minComp ) ;
ThreadPool . GetAvailableThreads ( out curWorkers , out curComp ) ;
curWorkers = maxWorkers - curWorkers ;
curComp = maxComp - curComp ;
sb . Append ( "\nFramework main threadpool \n" ) ;
sb . AppendFormat ( "workers: {0} ({1} / {2})\n" , curWorkers , maxWorkers , minWorkers ) ;
sb . AppendFormat ( "Completion: {0} ({1} / {2})\n" , curComp , maxComp , minComp ) ;
}
catch { }
2018-11-06 15:23:20 +00:00
if ( Util . FireAndForgetMethod = = FireAndForgetMethod . QueueUserWorkItem )
2017-06-16 17:16:26 +00:00
{
sb . AppendFormat ( "\nThread pool used: Framework main threadpool\n" ) ;
return sb . ToString ( ) ;
}
2013-06-17 22:57:10 +00:00
string threadPoolUsed = null ;
int maxThreads = 0 ;
int minThreads = 0 ;
int allocatedThreads = 0 ;
int inUseThreads = 0 ;
int waitingCallbacks = 0 ;
if ( Util . FireAndForgetMethod = = FireAndForgetMethod . SmartThreadPool )
{
STPInfo stpi = Util . GetSmartThreadPoolInfo ( ) ;
// ROBUST currently leaves this the FireAndForgetMethod but never actually initializes the threadpool.
if ( stpi ! = null )
{
threadPoolUsed = "SmartThreadPool" ;
maxThreads = stpi . MaxThreads ;
minThreads = stpi . MinThreads ;
inUseThreads = stpi . InUseThreads ;
allocatedThreads = stpi . ActiveThreads ;
waitingCallbacks = stpi . WaitingCallbacks ;
}
}
2017-06-16 17:16:26 +00:00
2013-06-17 22:57:10 +00:00
if ( threadPoolUsed ! = null )
{
2017-06-16 17:16:26 +00:00
sb . Append ( "\nThreadpool (excluding script engine pools)\n" ) ;
2013-06-17 22:57:10 +00:00
sb . AppendFormat ( "Thread pool used : {0}\n" , threadPoolUsed ) ;
sb . AppendFormat ( "Max threads : {0}\n" , maxThreads ) ;
sb . AppendFormat ( "Min threads : {0}\n" , minThreads ) ;
sb . AppendFormat ( "Allocated threads : {0}\n" , allocatedThreads < 0 ? "not applicable" : allocatedThreads . ToString ( ) ) ;
sb . AppendFormat ( "In use threads : {0}\n" , inUseThreads ) ;
sb . AppendFormat ( "Work items waiting : {0}\n" , waitingCallbacks < 0 ? "not available" : waitingCallbacks . ToString ( ) ) ;
}
else
{
sb . AppendFormat ( "Thread pool not used\n" ) ;
}
2012-11-23 04:40:49 +00:00
return sb . ToString ( ) ;
}
public virtual void HandleThreadsAbort ( string module , string [ ] cmd )
{
if ( cmd . Length ! = 3 )
{
MainConsole . Instance . Output ( "Usage: threads abort <thread-id>" ) ;
return ;
}
int threadId ;
if ( ! int . TryParse ( cmd [ 2 ] , out threadId ) )
{
MainConsole . Instance . Output ( "ERROR: Thread id must be an integer" ) ;
return ;
}
if ( Watchdog . AbortThread ( threadId ) )
2019-10-22 11:23:19 +00:00
MainConsole . Instance . Output ( "Aborted thread with id {0}" , threadId ) ;
2012-11-23 04:40:49 +00:00
else
2019-10-22 11:23:19 +00:00
MainConsole . Instance . Output ( "ERROR - Thread with id {0} not found in managed threads" , threadId ) ;
2017-01-05 19:07:37 +00:00
}
2012-11-23 04:40:49 +00:00
/// <summary>
2012-11-22 04:05:09 +00:00
/// Console output is only possible if a console has been established.
/// That is something that cannot be determined within this class. So
/// all attempts to use the console MUST be verified.
/// </summary>
/// <param name="msg"></param>
protected void Notice ( string msg )
{
if ( m_console ! = null )
{
m_console . Output ( msg ) ;
}
}
2017-01-05 19:07:37 +00:00
2012-11-22 04:05:09 +00:00
/// <summary>
/// Console output is only possible if a console has been established.
/// That is something that cannot be determined within this class. So
/// all attempts to use the console MUST be verified.
/// </summary>
2017-01-05 19:07:37 +00:00
/// <param name="format"></param>
2012-11-22 04:05:09 +00:00
/// <param name="components"></param>
2012-11-22 05:57:20 +00:00
protected void Notice ( string format , params object [ ] components )
2012-11-22 04:05:09 +00:00
{
if ( m_console ! = null )
2019-10-22 11:23:19 +00:00
m_console . Output ( format , components ) ;
2012-11-22 04:05:09 +00:00
}
2013-06-17 21:39:00 +00:00
public virtual void Shutdown ( )
{
m_serverStatsCollector . Close ( ) ;
ShutdownSpecific ( ) ;
}
/// <summary>
/// Should be overriden and referenced by descendents if they need to perform extra shutdown processing
/// </summary>
protected virtual void ShutdownSpecific ( ) { }
2012-11-22 03:43:21 +00:00
}
2013-01-23 22:12:48 +00:00
}