clockwerk-opensim – Blame information for rev 2
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections.Generic; |
||
30 | using System.Diagnostics; |
||
31 | using System.IO; |
||
32 | using System.Reflection; |
||
33 | using System.Text; |
||
34 | using System.Text.RegularExpressions; |
||
35 | using System.Threading; |
||
36 | using log4net; |
||
37 | using log4net.Appender; |
||
38 | using log4net.Core; |
||
39 | using log4net.Repository; |
||
40 | using Nini.Config; |
||
41 | using OpenSim.Framework.Console; |
||
42 | using OpenSim.Framework.Monitoring; |
||
43 | |||
44 | namespace OpenSim.Framework.Servers |
||
45 | { |
||
46 | public class ServerBase |
||
47 | { |
||
48 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
49 | |||
50 | public IConfigSource Config { get; protected set; } |
||
51 | |||
52 | /// <summary> |
||
53 | /// Console to be used for any command line output. Can be null, in which case there should be no output. |
||
54 | /// </summary> |
||
55 | protected ICommandConsole m_console; |
||
56 | |||
57 | protected OpenSimAppender m_consoleAppender; |
||
58 | protected FileAppender m_logFileAppender; |
||
59 | |||
60 | protected DateTime m_startuptime; |
||
61 | protected string m_startupDirectory = Environment.CurrentDirectory; |
||
62 | |||
63 | protected string m_pidFile = String.Empty; |
||
64 | |||
65 | protected ServerStatsCollector m_serverStatsCollector; |
||
66 | |||
67 | /// <summary> |
||
68 | /// Server version information. Usually VersionInfo + information about git commit, operating system, etc. |
||
69 | /// </summary> |
||
70 | protected string m_version; |
||
71 | |||
72 | public ServerBase() |
||
73 | { |
||
74 | m_startuptime = DateTime.Now; |
||
75 | m_version = VersionInfo.Version; |
||
76 | EnhanceVersionInformation(); |
||
77 | } |
||
78 | |||
79 | protected void CreatePIDFile(string path) |
||
80 | { |
||
81 | if (File.Exists(path)) |
||
82 | m_log.ErrorFormat( |
||
83 | "[SERVER BASE]: Previous pid file {0} still exists on startup. Possibly previously unclean shutdown.", |
||
84 | path); |
||
85 | |||
86 | try |
||
87 | { |
||
88 | string pidstring = System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); |
||
89 | |||
90 | using (FileStream fs = File.Create(path)) |
||
91 | { |
||
92 | Byte[] buf = Encoding.ASCII.GetBytes(pidstring); |
||
93 | fs.Write(buf, 0, buf.Length); |
||
94 | } |
||
95 | |||
96 | m_pidFile = path; |
||
97 | |||
98 | m_log.InfoFormat("[SERVER BASE]: Created pid file {0}", m_pidFile); |
||
99 | } |
||
100 | catch (Exception e) |
||
101 | { |
||
102 | m_log.Warn(string.Format("[SERVER BASE]: Could not create PID file at {0} ", path), e); |
||
103 | } |
||
104 | } |
||
105 | |||
106 | protected void RemovePIDFile() |
||
107 | { |
||
108 | if (m_pidFile != String.Empty) |
||
109 | { |
||
110 | try |
||
111 | { |
||
112 | File.Delete(m_pidFile); |
||
113 | } |
||
114 | catch (Exception e) |
||
115 | { |
||
116 | m_log.Error(string.Format("[SERVER BASE]: Error whilst removing {0} ", m_pidFile), e); |
||
117 | } |
||
118 | |||
119 | m_pidFile = String.Empty; |
||
120 | } |
||
121 | } |
||
122 | |||
123 | /// <summary> |
||
124 | /// Log information about the circumstances in which we're running (OpenSimulator version number, CLR details, |
||
125 | /// etc.). |
||
126 | /// </summary> |
||
127 | public void LogEnvironmentInformation() |
||
128 | { |
||
129 | // FIXME: This should be done down in ServerBase but we need to sort out and refactor the log4net |
||
130 | // XmlConfigurator calls first accross servers. |
||
131 | m_log.InfoFormat("[SERVER BASE]: Starting in {0}", m_startupDirectory); |
||
132 | |||
133 | m_log.InfoFormat("[SERVER BASE]: OpenSimulator version: {0}", m_version); |
||
134 | |||
135 | // clr version potentially is more confusing than helpful, since it doesn't tell us if we're running under Mono/MS .NET and |
||
136 | // the clr version number doesn't match the project version number under Mono. |
||
137 | //m_log.Info("[STARTUP]: Virtual machine runtime version: " + Environment.Version + Environment.NewLine); |
||
138 | m_log.InfoFormat( |
||
139 | "[SERVER BASE]: Operating system version: {0}, .NET platform {1}, {2}-bit", |
||
140 | Environment.OSVersion, Environment.OSVersion.Platform, Util.Is64BitProcess() ? "64" : "32"); |
||
141 | } |
||
142 | |||
143 | public void RegisterCommonAppenders(IConfig startupConfig) |
||
144 | { |
||
145 | ILoggerRepository repository = LogManager.GetRepository(); |
||
146 | IAppender[] appenders = repository.GetAppenders(); |
||
147 | |||
148 | foreach (IAppender appender in appenders) |
||
149 | { |
||
150 | if (appender.Name == "Console") |
||
151 | { |
||
152 | m_consoleAppender = (OpenSimAppender)appender; |
||
153 | } |
||
154 | else if (appender.Name == "LogFileAppender") |
||
155 | { |
||
156 | m_logFileAppender = (FileAppender)appender; |
||
157 | } |
||
158 | } |
||
159 | |||
160 | if (null == m_consoleAppender) |
||
161 | { |
||
162 | Notice("No appender named Console found (see the log4net config file for this executable)!"); |
||
163 | } |
||
164 | else |
||
165 | { |
||
166 | // FIXME: This should be done through an interface rather than casting. |
||
167 | m_consoleAppender.Console = (ConsoleBase)m_console; |
||
168 | |||
169 | // If there is no threshold set then the threshold is effectively everything. |
||
170 | if (null == m_consoleAppender.Threshold) |
||
171 | m_consoleAppender.Threshold = Level.All; |
||
172 | |||
173 | Notice(String.Format("Console log level is {0}", m_consoleAppender.Threshold)); |
||
174 | } |
||
175 | |||
176 | if (m_logFileAppender != null && startupConfig != null) |
||
177 | { |
||
178 | string cfgFileName = startupConfig.GetString("LogFile", null); |
||
179 | if (cfgFileName != null) |
||
180 | { |
||
181 | m_logFileAppender.File = cfgFileName; |
||
182 | m_logFileAppender.ActivateOptions(); |
||
183 | } |
||
184 | |||
185 | m_log.InfoFormat("[SERVER BASE]: Logging started to file {0}", m_logFileAppender.File); |
||
186 | } |
||
187 | } |
||
188 | |||
189 | /// <summary> |
||
190 | /// Register common commands once m_console has been set if it is going to be set |
||
191 | /// </summary> |
||
192 | public void RegisterCommonCommands() |
||
193 | { |
||
194 | if (m_console == null) |
||
195 | return; |
||
196 | |||
197 | m_console.Commands.AddCommand( |
||
198 | "General", false, "show info", "show info", "Show general information about the server", HandleShow); |
||
199 | |||
200 | m_console.Commands.AddCommand( |
||
201 | "General", false, "show version", "show version", "Show server version", HandleShow); |
||
202 | |||
203 | m_console.Commands.AddCommand( |
||
204 | "General", false, "show uptime", "show uptime", "Show server uptime", HandleShow); |
||
205 | |||
206 | m_console.Commands.AddCommand( |
||
207 | "General", false, "get log level", "get log level", "Get the current console logging level", |
||
208 | (mod, cmd) => ShowLogLevel()); |
||
209 | |||
210 | m_console.Commands.AddCommand( |
||
211 | "General", false, "set log level", "set log level <level>", |
||
212 | "Set the console logging level for this session.", HandleSetLogLevel); |
||
213 | |||
214 | m_console.Commands.AddCommand( |
||
215 | "General", false, "config set", |
||
216 | "config set <section> <key> <value>", |
||
217 | "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); |
||
218 | |||
219 | m_console.Commands.AddCommand( |
||
220 | "General", false, "config get", |
||
221 | "config get [<section>] [<key>]", |
||
222 | "Synonym for config show", |
||
223 | HandleConfig); |
||
224 | |||
225 | m_console.Commands.AddCommand( |
||
226 | "General", false, "config show", |
||
227 | "config show [<section>] [<key>]", |
||
228 | "Show config information", |
||
229 | "If neither section nor field are specified, then the whole current configuration is printed." + Environment.NewLine |
||
230 | + "If a section is given but not a field, then all fields in that section are printed.", |
||
231 | HandleConfig); |
||
232 | |||
233 | m_console.Commands.AddCommand( |
||
234 | "General", false, "config save", |
||
235 | "config save <path>", |
||
236 | "Save current configuration to a file at the given path", HandleConfig); |
||
237 | |||
238 | m_console.Commands.AddCommand( |
||
239 | "General", false, "command-script", |
||
240 | "command-script <script>", |
||
241 | "Run a command script from file", HandleScript); |
||
242 | |||
243 | m_console.Commands.AddCommand( |
||
244 | "General", false, "show threads", |
||
245 | "show threads", |
||
246 | "Show thread status", HandleShow); |
||
247 | |||
248 | m_console.Commands.AddCommand( |
||
249 | "Debug", false, "threads abort", |
||
250 | "threads abort <thread-id>", |
||
251 | "Abort a managed thread. Use \"show threads\" to find possible threads.", HandleThreadsAbort); |
||
252 | |||
253 | m_console.Commands.AddCommand( |
||
254 | "General", false, "threads show", |
||
255 | "threads show", |
||
256 | "Show thread status. Synonym for \"show threads\"", |
||
257 | (string module, string[] args) => Notice(GetThreadsReport())); |
||
258 | |||
259 | m_console.Commands.AddCommand ( |
||
260 | "Debug", false, "debug comms set", |
||
261 | "debug comms set serialosdreq true|false", |
||
262 | "Set comms parameters. For debug purposes.", |
||
263 | HandleDebugCommsSet); |
||
264 | |||
265 | m_console.Commands.AddCommand ( |
||
266 | "Debug", false, "debug comms status", |
||
267 | "debug comms status", |
||
268 | "Show current debug comms parameters.", |
||
269 | HandleDebugCommsStatus); |
||
270 | |||
271 | m_console.Commands.AddCommand ( |
||
272 | "Debug", false, "debug threadpool set", |
||
273 | "debug threadpool set worker|iocp min|max <n>", |
||
274 | "Set threadpool parameters. For debug purposes.", |
||
275 | HandleDebugThreadpoolSet); |
||
276 | |||
277 | m_console.Commands.AddCommand ( |
||
278 | "Debug", false, "debug threadpool status", |
||
279 | "debug threadpool status", |
||
280 | "Show current debug threadpool parameters.", |
||
281 | HandleDebugThreadpoolStatus); |
||
282 | |||
283 | m_console.Commands.AddCommand( |
||
284 | "Debug", false, "debug threadpool level", |
||
285 | "debug threadpool level 0.." + Util.MAX_THREADPOOL_LEVEL, |
||
286 | "Turn on logging of activity in the main thread pool.", |
||
287 | "Log levels:\n" |
||
288 | + " 0 = no logging\n" |
||
289 | + " 1 = only first line of stack trace; don't log common threads\n" |
||
290 | + " 2 = full stack trace; don't log common threads\n" |
||
291 | + " 3 = full stack trace, including common threads\n", |
||
292 | HandleDebugThreadpoolLevel); |
||
293 | |||
294 | m_console.Commands.AddCommand( |
||
295 | "Debug", false, "force gc", |
||
296 | "force gc", |
||
297 | "Manually invoke runtime garbage collection. For debugging purposes", |
||
298 | HandleForceGc); |
||
299 | |||
300 | m_console.Commands.AddCommand( |
||
301 | "General", false, "quit", |
||
302 | "quit", |
||
303 | "Quit the application", (mod, args) => Shutdown()); |
||
304 | |||
305 | m_console.Commands.AddCommand( |
||
306 | "General", false, "shutdown", |
||
307 | "shutdown", |
||
308 | "Quit the application", (mod, args) => Shutdown()); |
||
309 | |||
310 | ChecksManager.RegisterConsoleCommands(m_console); |
||
311 | StatsManager.RegisterConsoleCommands(m_console); |
||
312 | } |
||
313 | |||
314 | public void RegisterCommonComponents(IConfigSource configSource) |
||
315 | { |
||
316 | IConfig networkConfig = configSource.Configs["Network"]; |
||
317 | |||
318 | if (networkConfig != null) |
||
319 | { |
||
320 | WebUtil.SerializeOSDRequestsPerEndpoint = networkConfig.GetBoolean("SerializeOSDRequests", false); |
||
321 | } |
||
322 | |||
323 | m_serverStatsCollector = new ServerStatsCollector(); |
||
324 | m_serverStatsCollector.Initialise(configSource); |
||
325 | m_serverStatsCollector.Start(); |
||
326 | } |
||
327 | |||
328 | private void HandleDebugCommsStatus(string module, string[] args) |
||
329 | { |
||
330 | Notice("serialosdreq is {0}", WebUtil.SerializeOSDRequestsPerEndpoint); |
||
331 | } |
||
332 | |||
333 | private void HandleDebugCommsSet(string module, string[] args) |
||
334 | { |
||
335 | if (args.Length != 5) |
||
336 | { |
||
337 | Notice("Usage: debug comms set serialosdreq true|false"); |
||
338 | return; |
||
339 | } |
||
340 | |||
341 | if (args[3] != "serialosdreq") |
||
342 | { |
||
343 | Notice("Usage: debug comms set serialosdreq true|false"); |
||
344 | return; |
||
345 | } |
||
346 | |||
347 | bool setSerializeOsdRequests; |
||
348 | |||
349 | if (!ConsoleUtil.TryParseConsoleBool(m_console, args[4], out setSerializeOsdRequests)) |
||
350 | return; |
||
351 | |||
352 | WebUtil.SerializeOSDRequestsPerEndpoint = setSerializeOsdRequests; |
||
353 | |||
354 | Notice("serialosdreq is now {0}", setSerializeOsdRequests); |
||
355 | } |
||
356 | |||
357 | private void HandleDebugThreadpoolStatus(string module, string[] args) |
||
358 | { |
||
359 | int workerThreads, iocpThreads; |
||
360 | |||
361 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
362 | Notice("Min worker threads: {0}", workerThreads); |
||
363 | Notice("Min IOCP threads: {0}", iocpThreads); |
||
364 | |||
365 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
366 | Notice("Max worker threads: {0}", workerThreads); |
||
367 | Notice("Max IOCP threads: {0}", iocpThreads); |
||
368 | |||
369 | ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads); |
||
370 | Notice("Available worker threads: {0}", workerThreads); |
||
371 | Notice("Available IOCP threads: {0}", iocpThreads); |
||
372 | } |
||
373 | |||
374 | private void HandleDebugThreadpoolSet(string module, string[] args) |
||
375 | { |
||
376 | if (args.Length != 6) |
||
377 | { |
||
378 | Notice("Usage: debug threadpool set worker|iocp min|max <n>"); |
||
379 | return; |
||
380 | } |
||
381 | |||
382 | int newThreads; |
||
383 | |||
384 | if (!ConsoleUtil.TryParseConsoleInt(m_console, args[5], out newThreads)) |
||
385 | return; |
||
386 | |||
387 | string poolType = args[3]; |
||
388 | string bound = args[4]; |
||
389 | |||
390 | bool fail = false; |
||
391 | int workerThreads, iocpThreads; |
||
392 | |||
393 | if (poolType == "worker") |
||
394 | { |
||
395 | if (bound == "min") |
||
396 | { |
||
397 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
398 | |||
399 | if (!ThreadPool.SetMinThreads(newThreads, iocpThreads)) |
||
400 | fail = true; |
||
401 | } |
||
402 | else |
||
403 | { |
||
404 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
405 | |||
406 | if (!ThreadPool.SetMaxThreads(newThreads, iocpThreads)) |
||
407 | fail = true; |
||
408 | } |
||
409 | } |
||
410 | else |
||
411 | { |
||
412 | if (bound == "min") |
||
413 | { |
||
414 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
415 | |||
416 | if (!ThreadPool.SetMinThreads(workerThreads, newThreads)) |
||
417 | fail = true; |
||
418 | } |
||
419 | else |
||
420 | { |
||
421 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
422 | |||
423 | if (!ThreadPool.SetMaxThreads(workerThreads, newThreads)) |
||
424 | fail = true; |
||
425 | } |
||
426 | } |
||
427 | |||
428 | if (fail) |
||
429 | { |
||
430 | Notice("ERROR: Could not set {0} {1} threads to {2}", poolType, bound, newThreads); |
||
431 | } |
||
432 | else |
||
433 | { |
||
434 | int minWorkerThreads, maxWorkerThreads, minIocpThreads, maxIocpThreads; |
||
435 | |||
436 | ThreadPool.GetMinThreads(out minWorkerThreads, out minIocpThreads); |
||
437 | ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIocpThreads); |
||
438 | |||
439 | Notice("Min worker threads now {0}", minWorkerThreads); |
||
440 | Notice("Min IOCP threads now {0}", minIocpThreads); |
||
441 | Notice("Max worker threads now {0}", maxWorkerThreads); |
||
442 | Notice("Max IOCP threads now {0}", maxIocpThreads); |
||
443 | } |
||
444 | } |
||
445 | |||
446 | private static void HandleDebugThreadpoolLevel(string module, string[] cmdparams) |
||
447 | { |
||
448 | if (cmdparams.Length < 4) |
||
449 | { |
||
450 | MainConsole.Instance.Output("Usage: debug threadpool level 0.." + Util.MAX_THREADPOOL_LEVEL); |
||
451 | return; |
||
452 | } |
||
453 | |||
454 | string rawLevel = cmdparams[3]; |
||
455 | int newLevel; |
||
456 | |||
457 | if (!int.TryParse(rawLevel, out newLevel)) |
||
458 | { |
||
459 | MainConsole.Instance.OutputFormat("{0} is not a valid debug level", rawLevel); |
||
460 | return; |
||
461 | } |
||
462 | |||
463 | if (newLevel < 0 || newLevel > Util.MAX_THREADPOOL_LEVEL) |
||
464 | { |
||
465 | MainConsole.Instance.OutputFormat("{0} is outside the valid debug level range of 0.." + Util.MAX_THREADPOOL_LEVEL, newLevel); |
||
466 | return; |
||
467 | } |
||
468 | |||
469 | Util.LogThreadPool = newLevel; |
||
470 | MainConsole.Instance.OutputFormat("LogThreadPool set to {0}", newLevel); |
||
471 | } |
||
472 | |||
473 | private void HandleForceGc(string module, string[] args) |
||
474 | { |
||
475 | Notice("Manually invoking runtime garbage collection"); |
||
476 | GC.Collect(); |
||
477 | } |
||
478 | |||
479 | public virtual void HandleShow(string module, string[] cmd) |
||
480 | { |
||
481 | List<string> args = new List<string>(cmd); |
||
482 | |||
483 | args.RemoveAt(0); |
||
484 | |||
485 | string[] showParams = args.ToArray(); |
||
486 | |||
487 | switch (showParams[0]) |
||
488 | { |
||
489 | case "info": |
||
490 | ShowInfo(); |
||
491 | break; |
||
492 | |||
493 | case "version": |
||
494 | Notice(GetVersionText()); |
||
495 | break; |
||
496 | |||
497 | case "uptime": |
||
498 | Notice(GetUptimeReport()); |
||
499 | break; |
||
500 | |||
501 | case "threads": |
||
502 | Notice(GetThreadsReport()); |
||
503 | break; |
||
504 | } |
||
505 | } |
||
506 | |||
507 | /// <summary> |
||
508 | /// Change and load configuration file data. |
||
509 | /// </summary> |
||
510 | /// <param name="module"></param> |
||
511 | /// <param name="cmd"></param> |
||
512 | private void HandleConfig(string module, string[] cmd) |
||
513 | { |
||
514 | List<string> args = new List<string>(cmd); |
||
515 | args.RemoveAt(0); |
||
516 | string[] cmdparams = args.ToArray(); |
||
517 | |||
518 | if (cmdparams.Length > 0) |
||
519 | { |
||
520 | string firstParam = cmdparams[0].ToLower(); |
||
521 | |||
522 | switch (firstParam) |
||
523 | { |
||
524 | case "set": |
||
525 | if (cmdparams.Length < 4) |
||
526 | { |
||
527 | Notice("Syntax: config set <section> <key> <value>"); |
||
528 | Notice("Example: config set ScriptEngine.DotNetEngine NumberOfScriptThreads 5"); |
||
529 | } |
||
530 | else |
||
531 | { |
||
532 | IConfig c; |
||
533 | IConfigSource source = new IniConfigSource(); |
||
534 | c = source.AddConfig(cmdparams[1]); |
||
535 | if (c != null) |
||
536 | { |
||
537 | string _value = String.Join(" ", cmdparams, 3, cmdparams.Length - 3); |
||
538 | c.Set(cmdparams[2], _value); |
||
539 | Config.Merge(source); |
||
540 | |||
541 | Notice("In section [{0}], set {1} = {2}", c.Name, cmdparams[2], _value); |
||
542 | } |
||
543 | } |
||
544 | break; |
||
545 | |||
546 | case "get": |
||
547 | case "show": |
||
548 | if (cmdparams.Length == 1) |
||
549 | { |
||
550 | foreach (IConfig config in Config.Configs) |
||
551 | { |
||
552 | Notice("[{0}]", config.Name); |
||
553 | string[] keys = config.GetKeys(); |
||
554 | foreach (string key in keys) |
||
555 | Notice(" {0} = {1}", key, config.GetString(key)); |
||
556 | } |
||
557 | } |
||
558 | else if (cmdparams.Length == 2 || cmdparams.Length == 3) |
||
559 | { |
||
560 | IConfig config = Config.Configs[cmdparams[1]]; |
||
561 | if (config == null) |
||
562 | { |
||
563 | Notice("Section \"{0}\" does not exist.",cmdparams[1]); |
||
564 | break; |
||
565 | } |
||
566 | else |
||
567 | { |
||
568 | if (cmdparams.Length == 2) |
||
569 | { |
||
570 | Notice("[{0}]", config.Name); |
||
571 | foreach (string key in config.GetKeys()) |
||
572 | Notice(" {0} = {1}", key, config.GetString(key)); |
||
573 | } |
||
574 | else |
||
575 | { |
||
576 | Notice( |
||
577 | "config get {0} {1} : {2}", |
||
578 | cmdparams[1], cmdparams[2], config.GetString(cmdparams[2])); |
||
579 | } |
||
580 | } |
||
581 | } |
||
582 | else |
||
583 | { |
||
584 | Notice("Syntax: config {0} [<section>] [<key>]", firstParam); |
||
585 | Notice("Example: config {0} ScriptEngine.DotNetEngine NumberOfScriptThreads", firstParam); |
||
586 | } |
||
587 | |||
588 | break; |
||
589 | |||
590 | case "save": |
||
591 | if (cmdparams.Length < 2) |
||
592 | { |
||
593 | Notice("Syntax: config save <path>"); |
||
594 | return; |
||
595 | } |
||
596 | |||
597 | string path = cmdparams[1]; |
||
598 | Notice("Saving configuration file: {0}", path); |
||
599 | |||
600 | if (Config is IniConfigSource) |
||
601 | { |
||
602 | IniConfigSource iniCon = (IniConfigSource)Config; |
||
603 | iniCon.Save(path); |
||
604 | } |
||
605 | else if (Config is XmlConfigSource) |
||
606 | { |
||
607 | XmlConfigSource xmlCon = (XmlConfigSource)Config; |
||
608 | xmlCon.Save(path); |
||
609 | } |
||
610 | |||
611 | break; |
||
612 | } |
||
613 | } |
||
614 | } |
||
615 | |||
616 | private void HandleSetLogLevel(string module, string[] cmd) |
||
617 | { |
||
618 | if (cmd.Length != 4) |
||
619 | { |
||
620 | Notice("Usage: set log level <level>"); |
||
621 | return; |
||
622 | } |
||
623 | |||
624 | if (null == m_consoleAppender) |
||
625 | { |
||
626 | Notice("No appender named Console found (see the log4net config file for this executable)!"); |
||
627 | return; |
||
628 | } |
||
629 | |||
630 | string rawLevel = cmd[3]; |
||
631 | |||
632 | ILoggerRepository repository = LogManager.GetRepository(); |
||
633 | Level consoleLevel = repository.LevelMap[rawLevel]; |
||
634 | |||
635 | if (consoleLevel != null) |
||
636 | m_consoleAppender.Threshold = consoleLevel; |
||
637 | else |
||
638 | Notice( |
||
639 | "{0} is not a valid logging level. Valid logging levels are ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF", |
||
640 | rawLevel); |
||
641 | |||
642 | ShowLogLevel(); |
||
643 | } |
||
644 | |||
645 | private void ShowLogLevel() |
||
646 | { |
||
647 | Notice("Console log level is {0}", m_consoleAppender.Threshold); |
||
648 | } |
||
649 | |||
650 | protected virtual void HandleScript(string module, string[] parms) |
||
651 | { |
||
652 | if (parms.Length != 2) |
||
653 | { |
||
654 | Notice("Usage: command-script <path-to-script"); |
||
655 | return; |
||
656 | } |
||
657 | |||
658 | RunCommandScript(parms[1]); |
||
659 | } |
||
660 | |||
661 | /// <summary> |
||
662 | /// Run an optional startup list of commands |
||
663 | /// </summary> |
||
664 | /// <param name="fileName"></param> |
||
665 | protected void RunCommandScript(string fileName) |
||
666 | { |
||
667 | if (m_console == null) |
||
668 | return; |
||
669 | |||
670 | if (File.Exists(fileName)) |
||
671 | { |
||
672 | m_log.Info("[SERVER BASE]: Running " + fileName); |
||
673 | |||
674 | using (StreamReader readFile = File.OpenText(fileName)) |
||
675 | { |
||
676 | string currentCommand; |
||
677 | while ((currentCommand = readFile.ReadLine()) != null) |
||
678 | { |
||
679 | currentCommand = currentCommand.Trim(); |
||
680 | if (!(currentCommand == "" |
||
681 | || currentCommand.StartsWith(";") |
||
682 | || currentCommand.StartsWith("//") |
||
683 | || currentCommand.StartsWith("#"))) |
||
684 | { |
||
685 | m_log.Info("[SERVER BASE]: Running '" + currentCommand + "'"); |
||
686 | m_console.RunCommand(currentCommand); |
||
687 | } |
||
688 | } |
||
689 | } |
||
690 | } |
||
691 | } |
||
692 | |||
693 | /// <summary> |
||
694 | /// Return a report about the uptime of this server |
||
695 | /// </summary> |
||
696 | /// <returns></returns> |
||
697 | protected string GetUptimeReport() |
||
698 | { |
||
699 | StringBuilder sb = new StringBuilder(String.Format("Time now is {0}\n", DateTime.Now)); |
||
700 | sb.Append(String.Format("Server has been running since {0}, {1}\n", m_startuptime.DayOfWeek, m_startuptime)); |
||
701 | sb.Append(String.Format("That is an elapsed time of {0}\n", DateTime.Now - m_startuptime)); |
||
702 | |||
703 | return sb.ToString(); |
||
704 | } |
||
705 | |||
706 | protected void ShowInfo() |
||
707 | { |
||
708 | Notice(GetVersionText()); |
||
709 | Notice("Startup directory: " + m_startupDirectory); |
||
710 | if (null != m_consoleAppender) |
||
711 | Notice(String.Format("Console log level: {0}", m_consoleAppender.Threshold)); |
||
712 | } |
||
713 | |||
714 | /// <summary> |
||
715 | /// Enhance the version string with extra information if it's available. |
||
716 | /// </summary> |
||
717 | protected void EnhanceVersionInformation() |
||
718 | { |
||
719 | string buildVersion = string.Empty; |
||
720 | |||
721 | // The subversion information is deprecated and will be removed at a later date |
||
722 | // Add subversion revision information if available |
||
723 | // Try file "svn_revision" in the current directory first, then the .svn info. |
||
724 | // This allows to make the revision available in simulators not running from the source tree. |
||
725 | // FIXME: Making an assumption about the directory we're currently in - we do this all over the place |
||
726 | // elsewhere as well |
||
727 | string gitDir = "../.git/"; |
||
728 | string gitRefPointerPath = gitDir + "HEAD"; |
||
729 | |||
730 | string svnRevisionFileName = "svn_revision"; |
||
731 | string svnFileName = ".svn/entries"; |
||
732 | string manualVersionFileName = ".version"; |
||
733 | string inputLine; |
||
734 | int strcmp; |
||
735 | |||
736 | if (File.Exists(manualVersionFileName)) |
||
737 | { |
||
738 | using (StreamReader CommitFile = File.OpenText(manualVersionFileName)) |
||
739 | buildVersion = CommitFile.ReadLine(); |
||
740 | |||
741 | m_version += buildVersion ?? ""; |
||
742 | } |
||
743 | else if (File.Exists(gitRefPointerPath)) |
||
744 | { |
||
745 | // m_log.DebugFormat("[SERVER BASE]: Found {0}", gitRefPointerPath); |
||
746 | |||
747 | string rawPointer = ""; |
||
748 | |||
749 | using (StreamReader pointerFile = File.OpenText(gitRefPointerPath)) |
||
750 | rawPointer = pointerFile.ReadLine(); |
||
751 | |||
752 | // m_log.DebugFormat("[SERVER BASE]: rawPointer [{0}]", rawPointer); |
||
753 | |||
754 | Match m = Regex.Match(rawPointer, "^ref: (.+)$"); |
||
755 | |||
756 | if (m.Success) |
||
757 | { |
||
758 | // m_log.DebugFormat("[SERVER BASE]: Matched [{0}]", m.Groups[1].Value); |
||
759 | |||
760 | string gitRef = m.Groups[1].Value; |
||
761 | string gitRefPath = gitDir + gitRef; |
||
762 | if (File.Exists(gitRefPath)) |
||
763 | { |
||
764 | // m_log.DebugFormat("[SERVER BASE]: Found gitRefPath [{0}]", gitRefPath); |
||
765 | |||
766 | using (StreamReader refFile = File.OpenText(gitRefPath)) |
||
767 | { |
||
768 | string gitHash = refFile.ReadLine(); |
||
769 | m_version += gitHash.Substring(0, 7); |
||
770 | } |
||
771 | } |
||
772 | } |
||
773 | } |
||
774 | else |
||
775 | { |
||
776 | // Remove the else logic when subversion mirror is no longer used |
||
777 | if (File.Exists(svnRevisionFileName)) |
||
778 | { |
||
779 | StreamReader RevisionFile = File.OpenText(svnRevisionFileName); |
||
780 | buildVersion = RevisionFile.ReadLine(); |
||
781 | buildVersion.Trim(); |
||
782 | RevisionFile.Close(); |
||
783 | } |
||
784 | |||
785 | if (string.IsNullOrEmpty(buildVersion) && File.Exists(svnFileName)) |
||
786 | { |
||
787 | StreamReader EntriesFile = File.OpenText(svnFileName); |
||
788 | inputLine = EntriesFile.ReadLine(); |
||
789 | while (inputLine != null) |
||
790 | { |
||
791 | // using the dir svn revision at the top of entries file |
||
792 | strcmp = String.Compare(inputLine, "dir"); |
||
793 | if (strcmp == 0) |
||
794 | { |
||
795 | buildVersion = EntriesFile.ReadLine(); |
||
796 | break; |
||
797 | } |
||
798 | else |
||
799 | { |
||
800 | inputLine = EntriesFile.ReadLine(); |
||
801 | } |
||
802 | } |
||
803 | EntriesFile.Close(); |
||
804 | } |
||
805 | |||
806 | m_version += string.IsNullOrEmpty(buildVersion) ? " " : ("." + buildVersion + " ").Substring(0, 6); |
||
807 | } |
||
808 | } |
||
809 | |||
810 | protected string GetVersionText() |
||
811 | { |
||
812 | return String.Format("Version: {0} (interface version {1})", m_version, VersionInfo.MajorInterfaceVersion); |
||
813 | } |
||
814 | |||
815 | /// <summary> |
||
816 | /// Get a report about the registered threads in this server. |
||
817 | /// </summary> |
||
818 | protected string GetThreadsReport() |
||
819 | { |
||
820 | // This should be a constant field. |
||
821 | string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}"; |
||
822 | |||
823 | StringBuilder sb = new StringBuilder(); |
||
824 | Watchdog.ThreadWatchdogInfo[] threads = Watchdog.GetThreadsInfo(); |
||
825 | |||
826 | sb.Append(threads.Length + " threads are being tracked:" + Environment.NewLine); |
||
827 | |||
828 | int timeNow = Environment.TickCount & Int32.MaxValue; |
||
829 | |||
830 | sb.AppendFormat(reportFormat, "ID", "NAME", "LAST UPDATE (MS)", "LIFETIME (MS)", "PRIORITY", "STATE"); |
||
831 | sb.Append(Environment.NewLine); |
||
832 | |||
833 | foreach (Watchdog.ThreadWatchdogInfo twi in threads) |
||
834 | { |
||
835 | Thread t = twi.Thread; |
||
836 | |||
837 | sb.AppendFormat( |
||
838 | reportFormat, |
||
839 | t.ManagedThreadId, |
||
840 | t.Name, |
||
841 | timeNow - twi.LastTick, |
||
842 | timeNow - twi.FirstTick, |
||
843 | t.Priority, |
||
844 | t.ThreadState); |
||
845 | |||
846 | sb.Append("\n"); |
||
847 | } |
||
848 | |||
849 | sb.Append("\n"); |
||
850 | |||
851 | // For some reason mono 2.6.7 returns an empty threads set! Not going to confuse people by reporting |
||
852 | // zero active threads. |
||
853 | int totalThreads = Process.GetCurrentProcess().Threads.Count; |
||
854 | if (totalThreads > 0) |
||
855 | sb.AppendFormat("Total threads active: {0}\n\n", totalThreads); |
||
856 | |||
857 | sb.Append("Main threadpool (excluding script engine pools)\n"); |
||
858 | sb.Append(GetThreadPoolReport()); |
||
859 | |||
860 | return sb.ToString(); |
||
861 | } |
||
862 | |||
863 | /// <summary> |
||
864 | /// Get a thread pool report. |
||
865 | /// </summary> |
||
866 | /// <returns></returns> |
||
867 | public static string GetThreadPoolReport() |
||
868 | { |
||
869 | string threadPoolUsed = null; |
||
870 | int maxThreads = 0; |
||
871 | int minThreads = 0; |
||
872 | int allocatedThreads = 0; |
||
873 | int inUseThreads = 0; |
||
874 | int waitingCallbacks = 0; |
||
875 | int completionPortThreads = 0; |
||
876 | |||
877 | StringBuilder sb = new StringBuilder(); |
||
878 | if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool) |
||
879 | { |
||
880 | STPInfo stpi = Util.GetSmartThreadPoolInfo(); |
||
881 | |||
882 | // ROBUST currently leaves this the FireAndForgetMethod but never actually initializes the threadpool. |
||
883 | if (stpi != null) |
||
884 | { |
||
885 | threadPoolUsed = "SmartThreadPool"; |
||
886 | maxThreads = stpi.MaxThreads; |
||
887 | minThreads = stpi.MinThreads; |
||
888 | inUseThreads = stpi.InUseThreads; |
||
889 | allocatedThreads = stpi.ActiveThreads; |
||
890 | waitingCallbacks = stpi.WaitingCallbacks; |
||
891 | } |
||
892 | } |
||
893 | else if ( |
||
894 | Util.FireAndForgetMethod == FireAndForgetMethod.QueueUserWorkItem |
||
895 | || Util.FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem) |
||
896 | { |
||
897 | threadPoolUsed = "BuiltInThreadPool"; |
||
898 | ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads); |
||
899 | ThreadPool.GetMinThreads(out minThreads, out completionPortThreads); |
||
900 | int availableThreads; |
||
901 | ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads); |
||
902 | inUseThreads = maxThreads - availableThreads; |
||
903 | allocatedThreads = -1; |
||
904 | waitingCallbacks = -1; |
||
905 | } |
||
906 | |||
907 | if (threadPoolUsed != null) |
||
908 | { |
||
909 | sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed); |
||
910 | sb.AppendFormat("Max threads : {0}\n", maxThreads); |
||
911 | sb.AppendFormat("Min threads : {0}\n", minThreads); |
||
912 | sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString()); |
||
913 | sb.AppendFormat("In use threads : {0}\n", inUseThreads); |
||
914 | sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString()); |
||
915 | } |
||
916 | else |
||
917 | { |
||
918 | sb.AppendFormat("Thread pool not used\n"); |
||
919 | } |
||
920 | |||
921 | return sb.ToString(); |
||
922 | } |
||
923 | |||
924 | public virtual void HandleThreadsAbort(string module, string[] cmd) |
||
925 | { |
||
926 | if (cmd.Length != 3) |
||
927 | { |
||
928 | MainConsole.Instance.Output("Usage: threads abort <thread-id>"); |
||
929 | return; |
||
930 | } |
||
931 | |||
932 | int threadId; |
||
933 | if (!int.TryParse(cmd[2], out threadId)) |
||
934 | { |
||
935 | MainConsole.Instance.Output("ERROR: Thread id must be an integer"); |
||
936 | return; |
||
937 | } |
||
938 | |||
939 | if (Watchdog.AbortThread(threadId)) |
||
940 | MainConsole.Instance.OutputFormat("Aborted thread with id {0}", threadId); |
||
941 | else |
||
942 | MainConsole.Instance.OutputFormat("ERROR - Thread with id {0} not found in managed threads", threadId); |
||
943 | } |
||
944 | |||
945 | /// <summary> |
||
946 | /// Console output is only possible if a console has been established. |
||
947 | /// That is something that cannot be determined within this class. So |
||
948 | /// all attempts to use the console MUST be verified. |
||
949 | /// </summary> |
||
950 | /// <param name="msg"></param> |
||
951 | protected void Notice(string msg) |
||
952 | { |
||
953 | if (m_console != null) |
||
954 | { |
||
955 | m_console.Output(msg); |
||
956 | } |
||
957 | } |
||
958 | |||
959 | /// <summary> |
||
960 | /// Console output is only possible if a console has been established. |
||
961 | /// That is something that cannot be determined within this class. So |
||
962 | /// all attempts to use the console MUST be verified. |
||
963 | /// </summary> |
||
964 | /// <param name="format"></param> |
||
965 | /// <param name="components"></param> |
||
966 | protected void Notice(string format, params object[] components) |
||
967 | { |
||
968 | if (m_console != null) |
||
969 | m_console.OutputFormat(format, components); |
||
970 | } |
||
971 | |||
972 | public virtual void Shutdown() |
||
973 | { |
||
974 | m_serverStatsCollector.Close(); |
||
975 | ShutdownSpecific(); |
||
976 | } |
||
977 | |||
978 | /// <summary> |
||
979 | /// Should be overriden and referenced by descendents if they need to perform extra shutdown processing |
||
980 | /// </summary> |
||
981 | protected virtual void ShutdownSpecific() {} |
||
982 | } |
||
983 | } |