aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Servers/ServerBase.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/Servers/ServerBase.cs')
-rw-r--r--OpenSim/Framework/Servers/ServerBase.cs677
1 files changed, 677 insertions, 0 deletions
diff --git a/OpenSim/Framework/Servers/ServerBase.cs b/OpenSim/Framework/Servers/ServerBase.cs
new file mode 100644
index 0000000..47baac8
--- /dev/null
+++ b/OpenSim/Framework/Servers/ServerBase.cs
@@ -0,0 +1,677 @@
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
28using System;
29using System.Collections.Generic;
30using System.Diagnostics;
31using System.IO;
32using System.Reflection;
33using System.Text;
34using System.Text.RegularExpressions;
35using System.Threading;
36using log4net;
37using log4net.Appender;
38using log4net.Core;
39using log4net.Repository;
40using Nini.Config;
41using OpenSim.Framework.Console;
42using OpenSim.Framework.Monitoring;
43
44namespace 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 /// <summary>
66 /// Server version information. Usually VersionInfo + information about git commit, operating system, etc.
67 /// </summary>
68 protected string m_version;
69
70 public ServerBase()
71 {
72 m_startuptime = DateTime.Now;
73 m_version = VersionInfo.Version;
74 EnhanceVersionInformation();
75 }
76
77 protected void CreatePIDFile(string path)
78 {
79 try
80 {
81 string pidstring = System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
82
83 using (FileStream fs = File.Create(path))
84 {
85 Byte[] buf = Encoding.ASCII.GetBytes(pidstring);
86 fs.Write(buf, 0, buf.Length);
87 }
88
89 m_pidFile = path;
90
91 m_log.InfoFormat("[SERVER BASE]: Created pid file {0}", m_pidFile);
92 }
93 catch (Exception e)
94 {
95 m_log.Warn(string.Format("[SERVER BASE]: Could not create PID file at {0} ", path), e);
96 }
97 }
98
99 protected void RemovePIDFile()
100 {
101 if (m_pidFile != String.Empty)
102 {
103 try
104 {
105 File.Delete(m_pidFile);
106 }
107 catch (Exception e)
108 {
109 m_log.Error(string.Format("[SERVER BASE]: Error whilst removing {0} ", m_pidFile), e);
110 }
111
112 m_pidFile = String.Empty;
113 }
114 }
115
116 public void RegisterCommonAppenders(IConfig startupConfig)
117 {
118 ILoggerRepository repository = LogManager.GetRepository();
119 IAppender[] appenders = repository.GetAppenders();
120
121 foreach (IAppender appender in appenders)
122 {
123 if (appender.Name == "Console")
124 {
125 m_consoleAppender = (OpenSimAppender)appender;
126 }
127 else if (appender.Name == "LogFileAppender")
128 {
129 m_logFileAppender = (FileAppender)appender;
130 }
131 }
132
133 if (null == m_consoleAppender)
134 {
135 Notice("No appender named Console found (see the log4net config file for this executable)!");
136 }
137 else
138 {
139 // FIXME: This should be done through an interface rather than casting.
140 m_consoleAppender.Console = (ConsoleBase)m_console;
141
142 // If there is no threshold set then the threshold is effectively everything.
143 if (null == m_consoleAppender.Threshold)
144 m_consoleAppender.Threshold = Level.All;
145
146 Notice(String.Format("Console log level is {0}", m_consoleAppender.Threshold));
147 }
148
149 if (m_logFileAppender != null && startupConfig != null)
150 {
151 string cfgFileName = startupConfig.GetString("LogFile", null);
152 if (cfgFileName != null)
153 {
154 m_logFileAppender.File = cfgFileName;
155 m_logFileAppender.ActivateOptions();
156 }
157
158 m_log.InfoFormat("[SERVER BASE]: Logging started to file {0}", m_logFileAppender.File);
159 }
160 }
161
162 /// <summary>
163 /// Register common commands once m_console has been set if it is going to be set
164 /// </summary>
165 public void RegisterCommonCommands()
166 {
167 if (m_console == null)
168 return;
169
170 m_console.Commands.AddCommand(
171 "General", false, "show info", "show info", "Show general information about the server", HandleShow);
172
173 m_console.Commands.AddCommand(
174 "General", false, "show version", "show version", "Show server version", HandleShow);
175
176 m_console.Commands.AddCommand(
177 "General", false, "show uptime", "show uptime", "Show server uptime", HandleShow);
178
179 m_console.Commands.AddCommand(
180 "General", false, "get log level", "get log level", "Get the current console logging level",
181 (mod, cmd) => ShowLogLevel());
182
183 m_console.Commands.AddCommand(
184 "General", false, "set log level", "set log level <level>",
185 "Set the console logging level for this session.", HandleSetLogLevel);
186
187 m_console.Commands.AddCommand(
188 "General", false, "config set",
189 "config set <section> <key> <value>",
190 "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);
191
192 m_console.Commands.AddCommand(
193 "General", false, "config get",
194 "config get [<section>] [<key>]",
195 "Synonym for config show",
196 HandleConfig);
197
198 m_console.Commands.AddCommand(
199 "General", false, "config show",
200 "config show [<section>] [<key>]",
201 "Show config information",
202 "If neither section nor field are specified, then the whole current configuration is printed." + Environment.NewLine
203 + "If a section is given but not a field, then all fields in that section are printed.",
204 HandleConfig);
205
206 m_console.Commands.AddCommand(
207 "General", false, "config save",
208 "config save <path>",
209 "Save current configuration to a file at the given path", HandleConfig);
210
211 m_console.Commands.AddCommand(
212 "General", false, "command-script",
213 "command-script <script>",
214 "Run a command script from file", HandleScript);
215
216 m_console.Commands.AddCommand(
217 "General", false, "show threads",
218 "show threads",
219 "Show thread status", HandleShow);
220
221 m_console.Commands.AddCommand(
222 "General", false, "threads abort",
223 "threads abort <thread-id>",
224 "Abort a managed thread. Use \"show threads\" to find possible threads.", HandleThreadsAbort);
225
226 m_console.Commands.AddCommand(
227 "General", false, "threads show",
228 "threads show",
229 "Show thread status. Synonym for \"show threads\"",
230 (string module, string[] args) => Notice(GetThreadsReport()));
231
232 m_console.Commands.AddCommand(
233 "General", false, "force gc",
234 "force gc",
235 "Manually invoke runtime garbage collection. For debugging purposes",
236 HandleForceGc);
237 }
238
239 private void HandleForceGc(string module, string[] args)
240 {
241 Notice("Manually invoking runtime garbage collection");
242 GC.Collect();
243 }
244
245 public virtual void HandleShow(string module, string[] cmd)
246 {
247 List<string> args = new List<string>(cmd);
248
249 args.RemoveAt(0);
250
251 string[] showParams = args.ToArray();
252
253 switch (showParams[0])
254 {
255 case "info":
256 ShowInfo();
257 break;
258
259 case "version":
260 Notice(GetVersionText());
261 break;
262
263 case "uptime":
264 Notice(GetUptimeReport());
265 break;
266
267 case "threads":
268 Notice(GetThreadsReport());
269 break;
270 }
271 }
272
273 /// <summary>
274 /// Change and load configuration file data.
275 /// </summary>
276 /// <param name="module"></param>
277 /// <param name="cmd"></param>
278 private void HandleConfig(string module, string[] cmd)
279 {
280 List<string> args = new List<string>(cmd);
281 args.RemoveAt(0);
282 string[] cmdparams = args.ToArray();
283
284 if (cmdparams.Length > 0)
285 {
286 string firstParam = cmdparams[0].ToLower();
287
288 switch (firstParam)
289 {
290 case "set":
291 if (cmdparams.Length < 4)
292 {
293 Notice("Syntax: config set <section> <key> <value>");
294 Notice("Example: config set ScriptEngine.DotNetEngine NumberOfScriptThreads 5");
295 }
296 else
297 {
298 IConfig c;
299 IConfigSource source = new IniConfigSource();
300 c = source.AddConfig(cmdparams[1]);
301 if (c != null)
302 {
303 string _value = String.Join(" ", cmdparams, 3, cmdparams.Length - 3);
304 c.Set(cmdparams[2], _value);
305 Config.Merge(source);
306
307 Notice("In section [{0}], set {1} = {2}", c.Name, cmdparams[2], _value);
308 }
309 }
310 break;
311
312 case "get":
313 case "show":
314 if (cmdparams.Length == 1)
315 {
316 foreach (IConfig config in Config.Configs)
317 {
318 Notice("[{0}]", config.Name);
319 string[] keys = config.GetKeys();
320 foreach (string key in keys)
321 Notice(" {0} = {1}", key, config.GetString(key));
322 }
323 }
324 else if (cmdparams.Length == 2 || cmdparams.Length == 3)
325 {
326 IConfig config = Config.Configs[cmdparams[1]];
327 if (config == null)
328 {
329 Notice("Section \"{0}\" does not exist.",cmdparams[1]);
330 break;
331 }
332 else
333 {
334 if (cmdparams.Length == 2)
335 {
336 Notice("[{0}]", config.Name);
337 foreach (string key in config.GetKeys())
338 Notice(" {0} = {1}", key, config.GetString(key));
339 }
340 else
341 {
342 Notice(
343 "config get {0} {1} : {2}",
344 cmdparams[1], cmdparams[2], config.GetString(cmdparams[2]));
345 }
346 }
347 }
348 else
349 {
350 Notice("Syntax: config {0} [<section>] [<key>]", firstParam);
351 Notice("Example: config {0} ScriptEngine.DotNetEngine NumberOfScriptThreads", firstParam);
352 }
353
354 break;
355
356 case "save":
357 if (cmdparams.Length < 2)
358 {
359 Notice("Syntax: config save <path>");
360 return;
361 }
362
363 string path = cmdparams[1];
364 Notice("Saving configuration file: {0}", path);
365
366 if (Config is IniConfigSource)
367 {
368 IniConfigSource iniCon = (IniConfigSource)Config;
369 iniCon.Save(path);
370 }
371 else if (Config is XmlConfigSource)
372 {
373 XmlConfigSource xmlCon = (XmlConfigSource)Config;
374 xmlCon.Save(path);
375 }
376
377 break;
378 }
379 }
380 }
381
382 private void HandleSetLogLevel(string module, string[] cmd)
383 {
384 if (cmd.Length != 4)
385 {
386 Notice("Usage: set log level <level>");
387 return;
388 }
389
390 if (null == m_consoleAppender)
391 {
392 Notice("No appender named Console found (see the log4net config file for this executable)!");
393 return;
394 }
395
396 string rawLevel = cmd[3];
397
398 ILoggerRepository repository = LogManager.GetRepository();
399 Level consoleLevel = repository.LevelMap[rawLevel];
400
401 if (consoleLevel != null)
402 m_consoleAppender.Threshold = consoleLevel;
403 else
404 Notice(
405 "{0} is not a valid logging level. Valid logging levels are ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF",
406 rawLevel);
407
408 ShowLogLevel();
409 }
410
411 private void ShowLogLevel()
412 {
413 Notice("Console log level is {0}", m_consoleAppender.Threshold);
414 }
415
416 protected virtual void HandleScript(string module, string[] parms)
417 {
418 if (parms.Length != 2)
419 {
420 Notice("Usage: command-script <path-to-script");
421 return;
422 }
423
424 RunCommandScript(parms[1]);
425 }
426
427 /// <summary>
428 /// Run an optional startup list of commands
429 /// </summary>
430 /// <param name="fileName"></param>
431 protected void RunCommandScript(string fileName)
432 {
433 if (m_console == null)
434 return;
435
436 if (File.Exists(fileName))
437 {
438 m_log.Info("[SERVER BASE]: Running " + fileName);
439
440 using (StreamReader readFile = File.OpenText(fileName))
441 {
442 string currentCommand;
443 while ((currentCommand = readFile.ReadLine()) != null)
444 {
445 currentCommand = currentCommand.Trim();
446 if (!(currentCommand == ""
447 || currentCommand.StartsWith(";")
448 || currentCommand.StartsWith("//")
449 || currentCommand.StartsWith("#")))
450 {
451 m_log.Info("[SERVER BASE]: Running '" + currentCommand + "'");
452 m_console.RunCommand(currentCommand);
453 }
454 }
455 }
456 }
457 }
458
459 /// <summary>
460 /// Return a report about the uptime of this server
461 /// </summary>
462 /// <returns></returns>
463 protected string GetUptimeReport()
464 {
465 StringBuilder sb = new StringBuilder(String.Format("Time now is {0}\n", DateTime.Now));
466 sb.Append(String.Format("Server has been running since {0}, {1}\n", m_startuptime.DayOfWeek, m_startuptime));
467 sb.Append(String.Format("That is an elapsed time of {0}\n", DateTime.Now - m_startuptime));
468
469 return sb.ToString();
470 }
471
472 protected void ShowInfo()
473 {
474 Notice(GetVersionText());
475 Notice("Startup directory: " + m_startupDirectory);
476 if (null != m_consoleAppender)
477 Notice(String.Format("Console log level: {0}", m_consoleAppender.Threshold));
478 }
479
480 /// <summary>
481 /// Enhance the version string with extra information if it's available.
482 /// </summary>
483 protected void EnhanceVersionInformation()
484 {
485 string buildVersion = string.Empty;
486
487 // The subversion information is deprecated and will be removed at a later date
488 // Add subversion revision information if available
489 // Try file "svn_revision" in the current directory first, then the .svn info.
490 // This allows to make the revision available in simulators not running from the source tree.
491 // FIXME: Making an assumption about the directory we're currently in - we do this all over the place
492 // elsewhere as well
493 string gitDir = "../.git/";
494 string gitRefPointerPath = gitDir + "HEAD";
495
496 string svnRevisionFileName = "svn_revision";
497 string svnFileName = ".svn/entries";
498 string manualVersionFileName = ".version";
499 string inputLine;
500 int strcmp;
501
502 if (File.Exists(manualVersionFileName))
503 {
504 using (StreamReader CommitFile = File.OpenText(manualVersionFileName))
505 buildVersion = CommitFile.ReadLine();
506
507 m_version += buildVersion ?? "";
508 }
509 else if (File.Exists(gitRefPointerPath))
510 {
511// m_log.DebugFormat("[SERVER BASE]: Found {0}", gitRefPointerPath);
512
513 string rawPointer = "";
514
515 using (StreamReader pointerFile = File.OpenText(gitRefPointerPath))
516 rawPointer = pointerFile.ReadLine();
517
518// m_log.DebugFormat("[SERVER BASE]: rawPointer [{0}]", rawPointer);
519
520 Match m = Regex.Match(rawPointer, "^ref: (.+)$");
521
522 if (m.Success)
523 {
524// m_log.DebugFormat("[SERVER BASE]: Matched [{0}]", m.Groups[1].Value);
525
526 string gitRef = m.Groups[1].Value;
527 string gitRefPath = gitDir + gitRef;
528 if (File.Exists(gitRefPath))
529 {
530// m_log.DebugFormat("[SERVER BASE]: Found gitRefPath [{0}]", gitRefPath);
531
532 using (StreamReader refFile = File.OpenText(gitRefPath))
533 {
534 string gitHash = refFile.ReadLine();
535 m_version += gitHash.Substring(0, 7);
536 }
537 }
538 }
539 }
540 else
541 {
542 // Remove the else logic when subversion mirror is no longer used
543 if (File.Exists(svnRevisionFileName))
544 {
545 StreamReader RevisionFile = File.OpenText(svnRevisionFileName);
546 buildVersion = RevisionFile.ReadLine();
547 buildVersion.Trim();
548 RevisionFile.Close();
549 }
550
551 if (string.IsNullOrEmpty(buildVersion) && File.Exists(svnFileName))
552 {
553 StreamReader EntriesFile = File.OpenText(svnFileName);
554 inputLine = EntriesFile.ReadLine();
555 while (inputLine != null)
556 {
557 // using the dir svn revision at the top of entries file
558 strcmp = String.Compare(inputLine, "dir");
559 if (strcmp == 0)
560 {
561 buildVersion = EntriesFile.ReadLine();
562 break;
563 }
564 else
565 {
566 inputLine = EntriesFile.ReadLine();
567 }
568 }
569 EntriesFile.Close();
570 }
571
572 m_version += string.IsNullOrEmpty(buildVersion) ? " " : ("." + buildVersion + " ").Substring(0, 6);
573 }
574 }
575
576 protected string GetVersionText()
577 {
578 return String.Format("Version: {0} (interface version {1})", m_version, VersionInfo.MajorInterfaceVersion);
579 }
580
581 /// <summary>
582 /// Get a report about the registered threads in this server.
583 /// </summary>
584 protected string GetThreadsReport()
585 {
586 // This should be a constant field.
587 string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}";
588
589 StringBuilder sb = new StringBuilder();
590 Watchdog.ThreadWatchdogInfo[] threads = Watchdog.GetThreadsInfo();
591
592 sb.Append(threads.Length + " threads are being tracked:" + Environment.NewLine);
593
594 int timeNow = Environment.TickCount & Int32.MaxValue;
595
596 sb.AppendFormat(reportFormat, "ID", "NAME", "LAST UPDATE (MS)", "LIFETIME (MS)", "PRIORITY", "STATE");
597 sb.Append(Environment.NewLine);
598
599 foreach (Watchdog.ThreadWatchdogInfo twi in threads)
600 {
601 Thread t = twi.Thread;
602
603 sb.AppendFormat(
604 reportFormat,
605 t.ManagedThreadId,
606 t.Name,
607 timeNow - twi.LastTick,
608 timeNow - twi.FirstTick,
609 t.Priority,
610 t.ThreadState);
611
612 sb.Append("\n");
613 }
614
615 sb.Append("\n");
616
617 // For some reason mono 2.6.7 returns an empty threads set! Not going to confuse people by reporting
618 // zero active threads.
619 int totalThreads = Process.GetCurrentProcess().Threads.Count;
620 if (totalThreads > 0)
621 sb.AppendFormat("Total threads active: {0}\n\n", totalThreads);
622
623 sb.Append("Main threadpool (excluding script engine pools)\n");
624 sb.Append(Util.GetThreadPoolReport());
625
626 return sb.ToString();
627 }
628
629 public virtual void HandleThreadsAbort(string module, string[] cmd)
630 {
631 if (cmd.Length != 3)
632 {
633 MainConsole.Instance.Output("Usage: threads abort <thread-id>");
634 return;
635 }
636
637 int threadId;
638 if (!int.TryParse(cmd[2], out threadId))
639 {
640 MainConsole.Instance.Output("ERROR: Thread id must be an integer");
641 return;
642 }
643
644 if (Watchdog.AbortThread(threadId))
645 MainConsole.Instance.OutputFormat("Aborted thread with id {0}", threadId);
646 else
647 MainConsole.Instance.OutputFormat("ERROR - Thread with id {0} not found in managed threads", threadId);
648 }
649
650 /// <summary>
651 /// Console output is only possible if a console has been established.
652 /// That is something that cannot be determined within this class. So
653 /// all attempts to use the console MUST be verified.
654 /// </summary>
655 /// <param name="msg"></param>
656 protected void Notice(string msg)
657 {
658 if (m_console != null)
659 {
660 m_console.Output(msg);
661 }
662 }
663
664 /// <summary>
665 /// Console output is only possible if a console has been established.
666 /// That is something that cannot be determined within this class. So
667 /// all attempts to use the console MUST be verified.
668 /// </summary>
669 /// <param name="format"></param>
670 /// <param name="components"></param>
671 protected void Notice(string format, params object[] components)
672 {
673 if (m_console != null)
674 m_console.OutputFormat(format, components);
675 }
676 }
677} \ No newline at end of file