opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 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, "force gc", |
||
285 | "force gc", |
||
286 | "Manually invoke runtime garbage collection. For debugging purposes", |
||
287 | HandleForceGc); |
||
288 | |||
289 | m_console.Commands.AddCommand( |
||
290 | "General", false, "quit", |
||
291 | "quit", |
||
292 | "Quit the application", (mod, args) => Shutdown()); |
||
293 | |||
294 | m_console.Commands.AddCommand( |
||
295 | "General", false, "shutdown", |
||
296 | "shutdown", |
||
297 | "Quit the application", (mod, args) => Shutdown()); |
||
298 | |||
299 | ChecksManager.RegisterConsoleCommands(m_console); |
||
300 | StatsManager.RegisterConsoleCommands(m_console); |
||
301 | } |
||
302 | |||
303 | public void RegisterCommonComponents(IConfigSource configSource) |
||
304 | { |
||
305 | IConfig networkConfig = configSource.Configs["Network"]; |
||
306 | |||
307 | if (networkConfig != null) |
||
308 | { |
||
309 | WebUtil.SerializeOSDRequestsPerEndpoint = networkConfig.GetBoolean("SerializeOSDRequests", false); |
||
310 | } |
||
311 | |||
312 | m_serverStatsCollector = new ServerStatsCollector(); |
||
313 | m_serverStatsCollector.Initialise(configSource); |
||
314 | m_serverStatsCollector.Start(); |
||
315 | } |
||
316 | |||
317 | private void HandleDebugCommsStatus(string module, string[] args) |
||
318 | { |
||
319 | Notice("serialosdreq is {0}", WebUtil.SerializeOSDRequestsPerEndpoint); |
||
320 | } |
||
321 | |||
322 | private void HandleDebugCommsSet(string module, string[] args) |
||
323 | { |
||
324 | if (args.Length != 5) |
||
325 | { |
||
326 | Notice("Usage: debug comms set serialosdreq true|false"); |
||
327 | return; |
||
328 | } |
||
329 | |||
330 | if (args[3] != "serialosdreq") |
||
331 | { |
||
332 | Notice("Usage: debug comms set serialosdreq true|false"); |
||
333 | return; |
||
334 | } |
||
335 | |||
336 | bool setSerializeOsdRequests; |
||
337 | |||
338 | if (!ConsoleUtil.TryParseConsoleBool(m_console, args[4], out setSerializeOsdRequests)) |
||
339 | return; |
||
340 | |||
341 | WebUtil.SerializeOSDRequestsPerEndpoint = setSerializeOsdRequests; |
||
342 | |||
343 | Notice("serialosdreq is now {0}", setSerializeOsdRequests); |
||
344 | } |
||
345 | |||
346 | private void HandleDebugThreadpoolStatus(string module, string[] args) |
||
347 | { |
||
348 | int workerThreads, iocpThreads; |
||
349 | |||
350 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
351 | Notice("Min worker threads: {0}", workerThreads); |
||
352 | Notice("Min IOCP threads: {0}", iocpThreads); |
||
353 | |||
354 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
355 | Notice("Max worker threads: {0}", workerThreads); |
||
356 | Notice("Max IOCP threads: {0}", iocpThreads); |
||
357 | |||
358 | ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads); |
||
359 | Notice("Available worker threads: {0}", workerThreads); |
||
360 | Notice("Available IOCP threads: {0}", iocpThreads); |
||
361 | } |
||
362 | |||
363 | private void HandleDebugThreadpoolSet(string module, string[] args) |
||
364 | { |
||
365 | if (args.Length != 6) |
||
366 | { |
||
367 | Notice("Usage: debug threadpool set worker|iocp min|max <n>"); |
||
368 | return; |
||
369 | } |
||
370 | |||
371 | int newThreads; |
||
372 | |||
373 | if (!ConsoleUtil.TryParseConsoleInt(m_console, args[5], out newThreads)) |
||
374 | return; |
||
375 | |||
376 | string poolType = args[3]; |
||
377 | string bound = args[4]; |
||
378 | |||
379 | bool fail = false; |
||
380 | int workerThreads, iocpThreads; |
||
381 | |||
382 | if (poolType == "worker") |
||
383 | { |
||
384 | if (bound == "min") |
||
385 | { |
||
386 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
387 | |||
388 | if (!ThreadPool.SetMinThreads(newThreads, iocpThreads)) |
||
389 | fail = true; |
||
390 | } |
||
391 | else |
||
392 | { |
||
393 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
394 | |||
395 | if (!ThreadPool.SetMaxThreads(newThreads, iocpThreads)) |
||
396 | fail = true; |
||
397 | } |
||
398 | } |
||
399 | else |
||
400 | { |
||
401 | if (bound == "min") |
||
402 | { |
||
403 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); |
||
404 | |||
405 | if (!ThreadPool.SetMinThreads(workerThreads, newThreads)) |
||
406 | fail = true; |
||
407 | } |
||
408 | else |
||
409 | { |
||
410 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); |
||
411 | |||
412 | if (!ThreadPool.SetMaxThreads(workerThreads, newThreads)) |
||
413 | fail = true; |
||
414 | } |
||
415 | } |
||
416 | |||
417 | if (fail) |
||
418 | { |
||
419 | Notice("ERROR: Could not set {0} {1} threads to {2}", poolType, bound, newThreads); |
||
420 | } |
||
421 | else |
||
422 | { |
||
423 | int minWorkerThreads, maxWorkerThreads, minIocpThreads, maxIocpThreads; |
||
424 | |||
425 | ThreadPool.GetMinThreads(out minWorkerThreads, out minIocpThreads); |
||
426 | ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIocpThreads); |
||
427 | |||
428 | Notice("Min worker threads now {0}", minWorkerThreads); |
||
429 | Notice("Min IOCP threads now {0}", minIocpThreads); |
||
430 | Notice("Max worker threads now {0}", maxWorkerThreads); |
||
431 | Notice("Max IOCP threads now {0}", maxIocpThreads); |
||
432 | } |
||
433 | } |
||
434 | |||
435 | private void HandleForceGc(string module, string[] args) |
||
436 | { |
||
437 | Notice("Manually invoking runtime garbage collection"); |
||
438 | GC.Collect(); |
||
439 | } |
||
440 | |||
441 | public virtual void HandleShow(string module, string[] cmd) |
||
442 | { |
||
443 | List<string> args = new List<string>(cmd); |
||
444 | |||
445 | args.RemoveAt(0); |
||
446 | |||
447 | string[] showParams = args.ToArray(); |
||
448 | |||
449 | switch (showParams[0]) |
||
450 | { |
||
451 | case "info": |
||
452 | ShowInfo(); |
||
453 | break; |
||
454 | |||
455 | case "version": |
||
456 | Notice(GetVersionText()); |
||
457 | break; |
||
458 | |||
459 | case "uptime": |
||
460 | Notice(GetUptimeReport()); |
||
461 | break; |
||
462 | |||
463 | case "threads": |
||
464 | Notice(GetThreadsReport()); |
||
465 | break; |
||
466 | } |
||
467 | } |
||
468 | |||
469 | /// <summary> |
||
470 | /// Change and load configuration file data. |
||
471 | /// </summary> |
||
472 | /// <param name="module"></param> |
||
473 | /// <param name="cmd"></param> |
||
474 | private void HandleConfig(string module, string[] cmd) |
||
475 | { |
||
476 | List<string> args = new List<string>(cmd); |
||
477 | args.RemoveAt(0); |
||
478 | string[] cmdparams = args.ToArray(); |
||
479 | |||
480 | if (cmdparams.Length > 0) |
||
481 | { |
||
482 | string firstParam = cmdparams[0].ToLower(); |
||
483 | |||
484 | switch (firstParam) |
||
485 | { |
||
486 | case "set": |
||
487 | if (cmdparams.Length < 4) |
||
488 | { |
||
489 | Notice("Syntax: config set <section> <key> <value>"); |
||
490 | Notice("Example: config set ScriptEngine.DotNetEngine NumberOfScriptThreads 5"); |
||
491 | } |
||
492 | else |
||
493 | { |
||
494 | IConfig c; |
||
495 | IConfigSource source = new IniConfigSource(); |
||
496 | c = source.AddConfig(cmdparams[1]); |
||
497 | if (c != null) |
||
498 | { |
||
499 | string _value = String.Join(" ", cmdparams, 3, cmdparams.Length - 3); |
||
500 | c.Set(cmdparams[2], _value); |
||
501 | Config.Merge(source); |
||
502 | |||
503 | Notice("In section [{0}], set {1} = {2}", c.Name, cmdparams[2], _value); |
||
504 | } |
||
505 | } |
||
506 | break; |
||
507 | |||
508 | case "get": |
||
509 | case "show": |
||
510 | if (cmdparams.Length == 1) |
||
511 | { |
||
512 | foreach (IConfig config in Config.Configs) |
||
513 | { |
||
514 | Notice("[{0}]", config.Name); |
||
515 | string[] keys = config.GetKeys(); |
||
516 | foreach (string key in keys) |
||
517 | Notice(" {0} = {1}", key, config.GetString(key)); |
||
518 | } |
||
519 | } |
||
520 | else if (cmdparams.Length == 2 || cmdparams.Length == 3) |
||
521 | { |
||
522 | IConfig config = Config.Configs[cmdparams[1]]; |
||
523 | if (config == null) |
||
524 | { |
||
525 | Notice("Section \"{0}\" does not exist.",cmdparams[1]); |
||
526 | break; |
||
527 | } |
||
528 | else |
||
529 | { |
||
530 | if (cmdparams.Length == 2) |
||
531 | { |
||
532 | Notice("[{0}]", config.Name); |
||
533 | foreach (string key in config.GetKeys()) |
||
534 | Notice(" {0} = {1}", key, config.GetString(key)); |
||
535 | } |
||
536 | else |
||
537 | { |
||
538 | Notice( |
||
539 | "config get {0} {1} : {2}", |
||
540 | cmdparams[1], cmdparams[2], config.GetString(cmdparams[2])); |
||
541 | } |
||
542 | } |
||
543 | } |
||
544 | else |
||
545 | { |
||
546 | Notice("Syntax: config {0} [<section>] [<key>]", firstParam); |
||
547 | Notice("Example: config {0} ScriptEngine.DotNetEngine NumberOfScriptThreads", firstParam); |
||
548 | } |
||
549 | |||
550 | break; |
||
551 | |||
552 | case "save": |
||
553 | if (cmdparams.Length < 2) |
||
554 | { |
||
555 | Notice("Syntax: config save <path>"); |
||
556 | return; |
||
557 | } |
||
558 | |||
559 | string path = cmdparams[1]; |
||
560 | Notice("Saving configuration file: {0}", path); |
||
561 | |||
562 | if (Config is IniConfigSource) |
||
563 | { |
||
564 | IniConfigSource iniCon = (IniConfigSource)Config; |
||
565 | iniCon.Save(path); |
||
566 | } |
||
567 | else if (Config is XmlConfigSource) |
||
568 | { |
||
569 | XmlConfigSource xmlCon = (XmlConfigSource)Config; |
||
570 | xmlCon.Save(path); |
||
571 | } |
||
572 | |||
573 | break; |
||
574 | } |
||
575 | } |
||
576 | } |
||
577 | |||
578 | private void HandleSetLogLevel(string module, string[] cmd) |
||
579 | { |
||
580 | if (cmd.Length != 4) |
||
581 | { |
||
582 | Notice("Usage: set log level <level>"); |
||
583 | return; |
||
584 | } |
||
585 | |||
586 | if (null == m_consoleAppender) |
||
587 | { |
||
588 | Notice("No appender named Console found (see the log4net config file for this executable)!"); |
||
589 | return; |
||
590 | } |
||
591 | |||
592 | string rawLevel = cmd[3]; |
||
593 | |||
594 | ILoggerRepository repository = LogManager.GetRepository(); |
||
595 | Level consoleLevel = repository.LevelMap[rawLevel]; |
||
596 | |||
597 | if (consoleLevel != null) |
||
598 | m_consoleAppender.Threshold = consoleLevel; |
||
599 | else |
||
600 | Notice( |
||
601 | "{0} is not a valid logging level. Valid logging levels are ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF", |
||
602 | rawLevel); |
||
603 | |||
604 | ShowLogLevel(); |
||
605 | } |
||
606 | |||
607 | private void ShowLogLevel() |
||
608 | { |
||
609 | Notice("Console log level is {0}", m_consoleAppender.Threshold); |
||
610 | } |
||
611 | |||
612 | protected virtual void HandleScript(string module, string[] parms) |
||
613 | { |
||
614 | if (parms.Length != 2) |
||
615 | { |
||
616 | Notice("Usage: command-script <path-to-script"); |
||
617 | return; |
||
618 | } |
||
619 | |||
620 | RunCommandScript(parms[1]); |
||
621 | } |
||
622 | |||
623 | /// <summary> |
||
624 | /// Run an optional startup list of commands |
||
625 | /// </summary> |
||
626 | /// <param name="fileName"></param> |
||
627 | protected void RunCommandScript(string fileName) |
||
628 | { |
||
629 | if (m_console == null) |
||
630 | return; |
||
631 | |||
632 | if (File.Exists(fileName)) |
||
633 | { |
||
634 | m_log.Info("[SERVER BASE]: Running " + fileName); |
||
635 | |||
636 | using (StreamReader readFile = File.OpenText(fileName)) |
||
637 | { |
||
638 | string currentCommand; |
||
639 | while ((currentCommand = readFile.ReadLine()) != null) |
||
640 | { |
||
641 | currentCommand = currentCommand.Trim(); |
||
642 | if (!(currentCommand == "" |
||
643 | || currentCommand.StartsWith(";") |
||
644 | || currentCommand.StartsWith("//") |
||
645 | || currentCommand.StartsWith("#"))) |
||
646 | { |
||
647 | m_log.Info("[SERVER BASE]: Running '" + currentCommand + "'"); |
||
648 | m_console.RunCommand(currentCommand); |
||
649 | } |
||
650 | } |
||
651 | } |
||
652 | } |
||
653 | } |
||
654 | |||
655 | /// <summary> |
||
656 | /// Return a report about the uptime of this server |
||
657 | /// </summary> |
||
658 | /// <returns></returns> |
||
659 | protected string GetUptimeReport() |
||
660 | { |
||
661 | StringBuilder sb = new StringBuilder(String.Format("Time now is {0}\n", DateTime.Now)); |
||
662 | sb.Append(String.Format("Server has been running since {0}, {1}\n", m_startuptime.DayOfWeek, m_startuptime)); |
||
663 | sb.Append(String.Format("That is an elapsed time of {0}\n", DateTime.Now - m_startuptime)); |
||
664 | |||
665 | return sb.ToString(); |
||
666 | } |
||
667 | |||
668 | protected void ShowInfo() |
||
669 | { |
||
670 | Notice(GetVersionText()); |
||
671 | Notice("Startup directory: " + m_startupDirectory); |
||
672 | if (null != m_consoleAppender) |
||
673 | Notice(String.Format("Console log level: {0}", m_consoleAppender.Threshold)); |
||
674 | } |
||
675 | |||
676 | /// <summary> |
||
677 | /// Enhance the version string with extra information if it's available. |
||
678 | /// </summary> |
||
679 | protected void EnhanceVersionInformation() |
||
680 | { |
||
681 | string buildVersion = string.Empty; |
||
682 | |||
683 | // The subversion information is deprecated and will be removed at a later date |
||
684 | // Add subversion revision information if available |
||
685 | // Try file "svn_revision" in the current directory first, then the .svn info. |
||
686 | // This allows to make the revision available in simulators not running from the source tree. |
||
687 | // FIXME: Making an assumption about the directory we're currently in - we do this all over the place |
||
688 | // elsewhere as well |
||
689 | string gitDir = "../.git/"; |
||
690 | string gitRefPointerPath = gitDir + "HEAD"; |
||
691 | |||
692 | string svnRevisionFileName = "svn_revision"; |
||
693 | string svnFileName = ".svn/entries"; |
||
694 | string manualVersionFileName = ".version"; |
||
695 | string inputLine; |
||
696 | int strcmp; |
||
697 | |||
698 | if (File.Exists(manualVersionFileName)) |
||
699 | { |
||
700 | using (StreamReader CommitFile = File.OpenText(manualVersionFileName)) |
||
701 | buildVersion = CommitFile.ReadLine(); |
||
702 | |||
703 | m_version += buildVersion ?? ""; |
||
704 | } |
||
705 | else if (File.Exists(gitRefPointerPath)) |
||
706 | { |
||
707 | // m_log.DebugFormat("[SERVER BASE]: Found {0}", gitRefPointerPath); |
||
708 | |||
709 | string rawPointer = ""; |
||
710 | |||
711 | using (StreamReader pointerFile = File.OpenText(gitRefPointerPath)) |
||
712 | rawPointer = pointerFile.ReadLine(); |
||
713 | |||
714 | // m_log.DebugFormat("[SERVER BASE]: rawPointer [{0}]", rawPointer); |
||
715 | |||
716 | Match m = Regex.Match(rawPointer, "^ref: (.+)$"); |
||
717 | |||
718 | if (m.Success) |
||
719 | { |
||
720 | // m_log.DebugFormat("[SERVER BASE]: Matched [{0}]", m.Groups[1].Value); |
||
721 | |||
722 | string gitRef = m.Groups[1].Value; |
||
723 | string gitRefPath = gitDir + gitRef; |
||
724 | if (File.Exists(gitRefPath)) |
||
725 | { |
||
726 | // m_log.DebugFormat("[SERVER BASE]: Found gitRefPath [{0}]", gitRefPath); |
||
727 | |||
728 | using (StreamReader refFile = File.OpenText(gitRefPath)) |
||
729 | { |
||
730 | string gitHash = refFile.ReadLine(); |
||
731 | m_version += gitHash.Substring(0, 7); |
||
732 | } |
||
733 | } |
||
734 | } |
||
735 | } |
||
736 | else |
||
737 | { |
||
738 | // Remove the else logic when subversion mirror is no longer used |
||
739 | if (File.Exists(svnRevisionFileName)) |
||
740 | { |
||
741 | StreamReader RevisionFile = File.OpenText(svnRevisionFileName); |
||
742 | buildVersion = RevisionFile.ReadLine(); |
||
743 | buildVersion.Trim(); |
||
744 | RevisionFile.Close(); |
||
745 | } |
||
746 | |||
747 | if (string.IsNullOrEmpty(buildVersion) && File.Exists(svnFileName)) |
||
748 | { |
||
749 | StreamReader EntriesFile = File.OpenText(svnFileName); |
||
750 | inputLine = EntriesFile.ReadLine(); |
||
751 | while (inputLine != null) |
||
752 | { |
||
753 | // using the dir svn revision at the top of entries file |
||
754 | strcmp = String.Compare(inputLine, "dir"); |
||
755 | if (strcmp == 0) |
||
756 | { |
||
757 | buildVersion = EntriesFile.ReadLine(); |
||
758 | break; |
||
759 | } |
||
760 | else |
||
761 | { |
||
762 | inputLine = EntriesFile.ReadLine(); |
||
763 | } |
||
764 | } |
||
765 | EntriesFile.Close(); |
||
766 | } |
||
767 | |||
768 | m_version += string.IsNullOrEmpty(buildVersion) ? " " : ("." + buildVersion + " ").Substring(0, 6); |
||
769 | } |
||
770 | } |
||
771 | |||
772 | protected string GetVersionText() |
||
773 | { |
||
774 | return String.Format("Version: {0} (interface version {1})", m_version, VersionInfo.MajorInterfaceVersion); |
||
775 | } |
||
776 | |||
777 | /// <summary> |
||
778 | /// Get a report about the registered threads in this server. |
||
779 | /// </summary> |
||
780 | protected string GetThreadsReport() |
||
781 | { |
||
782 | // This should be a constant field. |
||
783 | string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}"; |
||
784 | |||
785 | StringBuilder sb = new StringBuilder(); |
||
786 | Watchdog.ThreadWatchdogInfo[] threads = Watchdog.GetThreadsInfo(); |
||
787 | |||
788 | sb.Append(threads.Length + " threads are being tracked:" + Environment.NewLine); |
||
789 | |||
790 | int timeNow = Environment.TickCount & Int32.MaxValue; |
||
791 | |||
792 | sb.AppendFormat(reportFormat, "ID", "NAME", "LAST UPDATE (MS)", "LIFETIME (MS)", "PRIORITY", "STATE"); |
||
793 | sb.Append(Environment.NewLine); |
||
794 | |||
795 | foreach (Watchdog.ThreadWatchdogInfo twi in threads) |
||
796 | { |
||
797 | Thread t = twi.Thread; |
||
798 | |||
799 | sb.AppendFormat( |
||
800 | reportFormat, |
||
801 | t.ManagedThreadId, |
||
802 | t.Name, |
||
803 | timeNow - twi.LastTick, |
||
804 | timeNow - twi.FirstTick, |
||
805 | t.Priority, |
||
806 | t.ThreadState); |
||
807 | |||
808 | sb.Append("\n"); |
||
809 | } |
||
810 | |||
811 | sb.Append("\n"); |
||
812 | |||
813 | // For some reason mono 2.6.7 returns an empty threads set! Not going to confuse people by reporting |
||
814 | // zero active threads. |
||
815 | int totalThreads = Process.GetCurrentProcess().Threads.Count; |
||
816 | if (totalThreads > 0) |
||
817 | sb.AppendFormat("Total threads active: {0}\n\n", totalThreads); |
||
818 | |||
819 | sb.Append("Main threadpool (excluding script engine pools)\n"); |
||
820 | sb.Append(GetThreadPoolReport()); |
||
821 | |||
822 | return sb.ToString(); |
||
823 | } |
||
824 | |||
825 | /// <summary> |
||
826 | /// Get a thread pool report. |
||
827 | /// </summary> |
||
828 | /// <returns></returns> |
||
829 | public static string GetThreadPoolReport() |
||
830 | { |
||
831 | string threadPoolUsed = null; |
||
832 | int maxThreads = 0; |
||
833 | int minThreads = 0; |
||
834 | int allocatedThreads = 0; |
||
835 | int inUseThreads = 0; |
||
836 | int waitingCallbacks = 0; |
||
837 | int completionPortThreads = 0; |
||
838 | |||
839 | StringBuilder sb = new StringBuilder(); |
||
840 | if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool) |
||
841 | { |
||
842 | STPInfo stpi = Util.GetSmartThreadPoolInfo(); |
||
843 | |||
844 | // ROBUST currently leaves this the FireAndForgetMethod but never actually initializes the threadpool. |
||
845 | if (stpi != null) |
||
846 | { |
||
847 | threadPoolUsed = "SmartThreadPool"; |
||
848 | maxThreads = stpi.MaxThreads; |
||
849 | minThreads = stpi.MinThreads; |
||
850 | inUseThreads = stpi.InUseThreads; |
||
851 | allocatedThreads = stpi.ActiveThreads; |
||
852 | waitingCallbacks = stpi.WaitingCallbacks; |
||
853 | } |
||
854 | } |
||
855 | else if ( |
||
856 | Util.FireAndForgetMethod == FireAndForgetMethod.QueueUserWorkItem |
||
857 | || Util.FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem) |
||
858 | { |
||
859 | threadPoolUsed = "BuiltInThreadPool"; |
||
860 | ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads); |
||
861 | ThreadPool.GetMinThreads(out minThreads, out completionPortThreads); |
||
862 | int availableThreads; |
||
863 | ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads); |
||
864 | inUseThreads = maxThreads - availableThreads; |
||
865 | allocatedThreads = -1; |
||
866 | waitingCallbacks = -1; |
||
867 | } |
||
868 | |||
869 | if (threadPoolUsed != null) |
||
870 | { |
||
871 | sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed); |
||
872 | sb.AppendFormat("Max threads : {0}\n", maxThreads); |
||
873 | sb.AppendFormat("Min threads : {0}\n", minThreads); |
||
874 | sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString()); |
||
875 | sb.AppendFormat("In use threads : {0}\n", inUseThreads); |
||
876 | sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString()); |
||
877 | } |
||
878 | else |
||
879 | { |
||
880 | sb.AppendFormat("Thread pool not used\n"); |
||
881 | } |
||
882 | |||
883 | return sb.ToString(); |
||
884 | } |
||
885 | |||
886 | public virtual void HandleThreadsAbort(string module, string[] cmd) |
||
887 | { |
||
888 | if (cmd.Length != 3) |
||
889 | { |
||
890 | MainConsole.Instance.Output("Usage: threads abort <thread-id>"); |
||
891 | return; |
||
892 | } |
||
893 | |||
894 | int threadId; |
||
895 | if (!int.TryParse(cmd[2], out threadId)) |
||
896 | { |
||
897 | MainConsole.Instance.Output("ERROR: Thread id must be an integer"); |
||
898 | return; |
||
899 | } |
||
900 | |||
901 | if (Watchdog.AbortThread(threadId)) |
||
902 | MainConsole.Instance.OutputFormat("Aborted thread with id {0}", threadId); |
||
903 | else |
||
904 | MainConsole.Instance.OutputFormat("ERROR - Thread with id {0} not found in managed threads", threadId); |
||
905 | } |
||
906 | |||
907 | /// <summary> |
||
908 | /// Console output is only possible if a console has been established. |
||
909 | /// That is something that cannot be determined within this class. So |
||
910 | /// all attempts to use the console MUST be verified. |
||
911 | /// </summary> |
||
912 | /// <param name="msg"></param> |
||
913 | protected void Notice(string msg) |
||
914 | { |
||
915 | if (m_console != null) |
||
916 | { |
||
917 | m_console.Output(msg); |
||
918 | } |
||
919 | } |
||
920 | |||
921 | /// <summary> |
||
922 | /// Console output is only possible if a console has been established. |
||
923 | /// That is something that cannot be determined within this class. So |
||
924 | /// all attempts to use the console MUST be verified. |
||
925 | /// </summary> |
||
926 | /// <param name="format"></param> |
||
927 | /// <param name="components"></param> |
||
928 | protected void Notice(string format, params object[] components) |
||
929 | { |
||
930 | if (m_console != null) |
||
931 | m_console.OutputFormat(format, components); |
||
932 | } |
||
933 | |||
934 | public virtual void Shutdown() |
||
935 | { |
||
936 | m_serverStatsCollector.Close(); |
||
937 | ShutdownSpecific(); |
||
938 | } |
||
939 | |||
940 | /// <summary> |
||
941 | /// Should be overriden and referenced by descendents if they need to perform extra shutdown processing |
||
942 | /// </summary> |
||
943 | protected virtual void ShutdownSpecific() {} |
||
944 | } |
||
945 | } |