aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Monitoring
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Framework/Monitoring/AssetStatsCollector.cs130
-rw-r--r--OpenSim/Framework/Monitoring/BaseStatsCollector.cs78
-rw-r--r--OpenSim/Framework/Monitoring/Checks/Check.cs118
-rw-r--r--OpenSim/Framework/Monitoring/ChecksManager.cs262
-rw-r--r--OpenSim/Framework/Monitoring/Interfaces/IPullStatsProvider.cs41
-rw-r--r--OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs58
-rw-r--r--OpenSim/Framework/Monitoring/JobEngine.cs341
-rw-r--r--OpenSim/Framework/Monitoring/MemoryWatchdog.cs137
-rw-r--r--OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs33
-rw-r--r--OpenSim/Framework/Monitoring/ServerStatsCollector.cs346
-rwxr-xr-xOpenSim/Framework/Monitoring/SimExtraStatsCollector.cs536
-rwxr-xr-xOpenSim/Framework/Monitoring/Stats/CounterStat.cs119
-rwxr-xr-xOpenSim/Framework/Monitoring/Stats/EventHistogram.cs173
-rw-r--r--OpenSim/Framework/Monitoring/Stats/PercentageStat.cs104
-rw-r--r--OpenSim/Framework/Monitoring/Stats/Stat.cs326
-rw-r--r--OpenSim/Framework/Monitoring/StatsLogger.cs151
-rw-r--r--OpenSim/Framework/Monitoring/StatsManager.cs557
-rw-r--r--OpenSim/Framework/Monitoring/UserStatsCollector.cs110
-rw-r--r--OpenSim/Framework/Monitoring/Watchdog.cs380
-rw-r--r--OpenSim/Framework/Monitoring/WorkManager.cs290
20 files changed, 4290 insertions, 0 deletions
diff --git a/OpenSim/Framework/Monitoring/AssetStatsCollector.cs b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs
new file mode 100644
index 0000000..6a0f676
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs
@@ -0,0 +1,130 @@
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.Timers;
30
31using OpenMetaverse.StructuredData;
32
33namespace OpenSim.Framework.Monitoring
34{
35 /// <summary>
36 /// Asset service statistics collection
37 /// </summary>
38 public class AssetStatsCollector : BaseStatsCollector
39 {
40 private Timer ageStatsTimer = new Timer(24 * 60 * 60 * 1000);
41 private DateTime startTime = DateTime.Now;
42
43 private long assetRequestsToday;
44 private long assetRequestsNotFoundToday;
45 private long assetRequestsYesterday;
46 private long assetRequestsNotFoundYesterday;
47
48 public long AssetRequestsToday { get { return assetRequestsToday; } }
49 public long AssetRequestsNotFoundToday { get { return assetRequestsNotFoundToday; } }
50 public long AssetRequestsYesterday { get { return assetRequestsYesterday; } }
51 public long AssetRequestsNotFoundYesterday { get { return assetRequestsNotFoundYesterday; } }
52
53 public AssetStatsCollector()
54 {
55 ageStatsTimer.Elapsed += new ElapsedEventHandler(OnAgeing);
56 ageStatsTimer.Enabled = true;
57 }
58
59 private void OnAgeing(object source, ElapsedEventArgs e)
60 {
61 assetRequestsYesterday = assetRequestsToday;
62
63 // There is a possibility that an asset request could occur between the execution of these
64 // two statements. But we're better off without the synchronization overhead.
65 assetRequestsToday = 0;
66
67 assetRequestsNotFoundYesterday = assetRequestsNotFoundToday;
68 assetRequestsNotFoundToday = 0;
69 }
70
71 /// <summary>
72 /// Record that an asset request failed to find an asset
73 /// </summary>
74 public void AddNotFoundRequest()
75 {
76 assetRequestsNotFoundToday++;
77 }
78
79 /// <summary>
80 /// Record that a request was made to the asset server
81 /// </summary>
82 public void AddRequest()
83 {
84 assetRequestsToday++;
85 }
86
87 /// <summary>
88 /// Report back collected statistical information.
89 /// </summary>
90 /// <returns></returns>
91 override public string Report()
92 {
93 double elapsedHours = (DateTime.Now - startTime).TotalHours;
94 if (elapsedHours <= 0) { elapsedHours = 1; } // prevent divide by zero
95
96 long assetRequestsTodayPerHour = (long)Math.Round(AssetRequestsToday / elapsedHours);
97 long assetRequestsYesterdayPerHour = (long)Math.Round(AssetRequestsYesterday / 24.0);
98
99 return string.Format(
100@"Asset requests today : {0} ({1} per hour) of which {2} were not found
101Asset requests yesterday : {3} ({4} per hour) of which {5} were not found",
102 AssetRequestsToday, assetRequestsTodayPerHour, AssetRequestsNotFoundToday,
103 AssetRequestsYesterday, assetRequestsYesterdayPerHour, AssetRequestsNotFoundYesterday);
104 }
105
106 public override string XReport(string uptime, string version)
107 {
108 return OSDParser.SerializeJsonString(OReport(uptime, version));
109 }
110
111 public override OSDMap OReport(string uptime, string version)
112 {
113 double elapsedHours = (DateTime.Now - startTime).TotalHours;
114 if (elapsedHours <= 0) { elapsedHours = 1; } // prevent divide by zero
115
116 long assetRequestsTodayPerHour = (long)Math.Round(AssetRequestsToday / elapsedHours);
117 long assetRequestsYesterdayPerHour = (long)Math.Round(AssetRequestsYesterday / 24.0);
118
119 OSDMap ret = new OSDMap();
120 ret.Add("AssetRequestsToday", OSD.FromLong(AssetRequestsToday));
121 ret.Add("AssetRequestsTodayPerHour", OSD.FromLong(assetRequestsTodayPerHour));
122 ret.Add("AssetRequestsNotFoundToday", OSD.FromLong(AssetRequestsNotFoundToday));
123 ret.Add("AssetRequestsYesterday", OSD.FromLong(AssetRequestsYesterday));
124 ret.Add("AssetRequestsYesterdayPerHour", OSD.FromLong(assetRequestsYesterdayPerHour));
125 ret.Add("AssetRequestsNotFoundYesterday", OSD.FromLong(assetRequestsNotFoundYesterday));
126
127 return ret;
128 }
129 }
130}
diff --git a/OpenSim/Framework/Monitoring/BaseStatsCollector.cs b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs
new file mode 100644
index 0000000..20495f6
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs
@@ -0,0 +1,78 @@
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.Diagnostics;
30using System.Text;
31using OpenMetaverse;
32using OpenMetaverse.StructuredData;
33
34namespace OpenSim.Framework.Monitoring
35{
36 /// <summary>
37 /// Statistics which all collectors are interested in reporting
38 /// </summary>
39 public class BaseStatsCollector : IStatsCollector
40 {
41 public virtual string Report()
42 {
43 StringBuilder sb = new StringBuilder(Environment.NewLine);
44 sb.Append("MEMORY STATISTICS");
45 sb.Append(Environment.NewLine);
46
47 sb.AppendFormat(
48 "Heap allocated to OpenSim : {0} MB\n",
49 Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0));
50
51 sb.AppendFormat(
52 "Last heap allocation rate : {0} MB/s\n",
53 Math.Round((MemoryWatchdog.LastHeapAllocationRate * 1000) / 1024.0 / 1024, 3));
54
55 sb.AppendFormat(
56 "Average heap allocation rate: {0} MB/s\n",
57 Math.Round((MemoryWatchdog.AverageHeapAllocationRate * 1000) / 1024.0 / 1024, 3));
58
59 sb.AppendFormat(
60 "Process memory : {0} MB\n",
61 Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024.0 / 1024.0));
62
63 return sb.ToString();
64 }
65
66 public virtual string XReport(string uptime, string version)
67 {
68 return (string) Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0).ToString() ;
69 }
70
71 public virtual OSDMap OReport(string uptime, string version)
72 {
73 OSDMap ret = new OSDMap();
74 ret.Add("TotalMemory", new OSDReal(Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0)));
75 return ret;
76 }
77 }
78}
diff --git a/OpenSim/Framework/Monitoring/Checks/Check.cs b/OpenSim/Framework/Monitoring/Checks/Check.cs
new file mode 100644
index 0000000..594386a
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Checks/Check.cs
@@ -0,0 +1,118 @@
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.Text;
30
31namespace OpenSim.Framework.Monitoring
32{
33 public class Check
34 {
35// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
36
37 public static readonly char[] DisallowedShortNameCharacters = { '.' };
38
39 /// <summary>
40 /// Category of this stat (e.g. cache, scene, etc).
41 /// </summary>
42 public string Category { get; private set; }
43
44 /// <summary>
45 /// Containing name for this stat.
46 /// FIXME: In the case of a scene, this is currently the scene name (though this leaves
47 /// us with a to-be-resolved problem of non-unique region names).
48 /// </summary>
49 /// <value>
50 /// The container.
51 /// </value>
52 public string Container { get; private set; }
53
54 /// <summary>
55 /// Action used to check whether alert should go off.
56 /// </summary>
57 /// <remarks>
58 /// Should return true if check passes. False otherwise.
59 /// </remarks>
60 public Func<Check, bool> CheckFunc { get; private set; }
61
62 /// <summary>
63 /// Message from the last failure, if any. If there is no message or no failure then will be null.
64 /// </summary>
65 /// <remarks>
66 /// Should be set by the CheckFunc when applicable.
67 /// </remarks>
68 public string LastFailureMessage { get; set; }
69
70 public StatVerbosity Verbosity { get; private set; }
71 public string ShortName { get; private set; }
72 public string Name { get; private set; }
73 public string Description { get; private set; }
74
75 public Check(
76 string shortName,
77 string name,
78 string description,
79 string category,
80 string container,
81 Func<Check, bool> checkFunc,
82 StatVerbosity verbosity)
83 {
84 if (ChecksManager.SubCommands.Contains(category))
85 throw new Exception(
86 string.Format("Alert cannot be in category '{0}' since this is reserved for a subcommand", category));
87
88 foreach (char c in DisallowedShortNameCharacters)
89 {
90 if (shortName.IndexOf(c) != -1)
91 throw new Exception(string.Format("Alert name {0} cannot contain character {1}", shortName, c));
92 }
93
94 ShortName = shortName;
95 Name = name;
96 Description = description;
97 Category = category;
98 Container = container;
99 CheckFunc = checkFunc;
100 Verbosity = verbosity;
101 }
102
103 public bool CheckIt()
104 {
105 return CheckFunc(this);
106 }
107
108 public virtual string ToConsoleString()
109 {
110 return string.Format(
111 "{0}.{1}.{2} - {3}",
112 Category,
113 Container,
114 ShortName,
115 Description);
116 }
117 }
118} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/ChecksManager.cs b/OpenSim/Framework/Monitoring/ChecksManager.cs
new file mode 100644
index 0000000..e4a7f8c
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/ChecksManager.cs
@@ -0,0 +1,262 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34
35namespace OpenSim.Framework.Monitoring
36{
37 /// <summary>
38 /// Static class used to register/deregister checks on runtime conditions.
39 /// </summary>
40 public static class ChecksManager
41 {
42 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
43
44 // Subcommand used to list other stats.
45 public const string ListSubCommand = "list";
46
47 // All subcommands
48 public static HashSet<string> SubCommands = new HashSet<string> { ListSubCommand };
49
50 /// <summary>
51 /// Checks categorized by category/container/shortname
52 /// </summary>
53 /// <remarks>
54 /// Do not add or remove directly from this dictionary.
55 /// </remarks>
56 public static SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Check>>> RegisteredChecks
57 = new SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Check>>>();
58
59 public static void RegisterConsoleCommands(ICommandConsole console)
60 {
61 console.Commands.AddCommand(
62 "General",
63 false,
64 "show checks",
65 "show checks",
66 "Show checks configured for this server",
67 "If no argument is specified then info on all checks will be shown.\n"
68 + "'list' argument will show check categories.\n"
69 + "THIS FACILITY IS EXPERIMENTAL",
70 HandleShowchecksCommand);
71 }
72
73 public static void HandleShowchecksCommand(string module, string[] cmd)
74 {
75 ICommandConsole con = MainConsole.Instance;
76
77 if (cmd.Length > 2)
78 {
79 foreach (string name in cmd.Skip(2))
80 {
81 string[] components = name.Split('.');
82
83 string categoryName = components[0];
84// string containerName = components.Length > 1 ? components[1] : null;
85
86 if (categoryName == ListSubCommand)
87 {
88 con.Output("check categories available are:");
89
90 foreach (string category in RegisteredChecks.Keys)
91 con.OutputFormat(" {0}", category);
92 }
93// else
94// {
95// SortedDictionary<string, SortedDictionary<string, Check>> category;
96// if (!Registeredchecks.TryGetValue(categoryName, out category))
97// {
98// con.OutputFormat("No such category as {0}", categoryName);
99// }
100// else
101// {
102// if (String.IsNullOrEmpty(containerName))
103// {
104// OutputConfiguredToConsole(con, category);
105// }
106// else
107// {
108// SortedDictionary<string, Check> container;
109// if (category.TryGetValue(containerName, out container))
110// {
111// OutputContainerChecksToConsole(con, container);
112// }
113// else
114// {
115// con.OutputFormat("No such container {0} in category {1}", containerName, categoryName);
116// }
117// }
118// }
119// }
120 }
121 }
122 else
123 {
124 OutputAllChecksToConsole(con);
125 }
126 }
127
128 /// <summary>
129 /// Registers a statistic.
130 /// </summary>
131 /// <param name='stat'></param>
132 /// <returns></returns>
133 public static bool RegisterCheck(Check check)
134 {
135 SortedDictionary<string, SortedDictionary<string, Check>> category = null, newCategory;
136 SortedDictionary<string, Check> container = null, newContainer;
137
138 lock (RegisteredChecks)
139 {
140 // Check name is not unique across category/container/shortname key.
141 // XXX: For now just return false. This is to avoid problems in regression tests where all tests
142 // in a class are run in the same instance of the VM.
143 if (TryGetCheckParents(check, out category, out container))
144 return false;
145
146 // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
147 // This means that we don't need to lock or copy them on iteration, which will be a much more
148 // common operation after startup.
149 if (container != null)
150 newContainer = new SortedDictionary<string, Check>(container);
151 else
152 newContainer = new SortedDictionary<string, Check>();
153
154 if (category != null)
155 newCategory = new SortedDictionary<string, SortedDictionary<string, Check>>(category);
156 else
157 newCategory = new SortedDictionary<string, SortedDictionary<string, Check>>();
158
159 newContainer[check.ShortName] = check;
160 newCategory[check.Container] = newContainer;
161 RegisteredChecks[check.Category] = newCategory;
162 }
163
164 return true;
165 }
166
167 /// <summary>
168 /// Deregister an check
169 /// </summary>>
170 /// <param name='stat'></param>
171 /// <returns></returns>
172 public static bool DeregisterCheck(Check check)
173 {
174 SortedDictionary<string, SortedDictionary<string, Check>> category = null, newCategory;
175 SortedDictionary<string, Check> container = null, newContainer;
176
177 lock (RegisteredChecks)
178 {
179 if (!TryGetCheckParents(check, out category, out container))
180 return false;
181
182 newContainer = new SortedDictionary<string, Check>(container);
183 newContainer.Remove(check.ShortName);
184
185 newCategory = new SortedDictionary<string, SortedDictionary<string, Check>>(category);
186 newCategory.Remove(check.Container);
187
188 newCategory[check.Container] = newContainer;
189 RegisteredChecks[check.Category] = newCategory;
190
191 return true;
192 }
193 }
194
195 public static bool TryGetCheckParents(
196 Check check,
197 out SortedDictionary<string, SortedDictionary<string, Check>> category,
198 out SortedDictionary<string, Check> container)
199 {
200 category = null;
201 container = null;
202
203 lock (RegisteredChecks)
204 {
205 if (RegisteredChecks.TryGetValue(check.Category, out category))
206 {
207 if (category.TryGetValue(check.Container, out container))
208 {
209 if (container.ContainsKey(check.ShortName))
210 return true;
211 }
212 }
213 }
214
215 return false;
216 }
217
218 public static void CheckChecks()
219 {
220 lock (RegisteredChecks)
221 {
222 foreach (SortedDictionary<string, SortedDictionary<string, Check>> category in RegisteredChecks.Values)
223 {
224 foreach (SortedDictionary<string, Check> container in category.Values)
225 {
226 foreach (Check check in container.Values)
227 {
228 if (!check.CheckIt())
229 m_log.WarnFormat(
230 "[CHECKS MANAGER]: Check {0}.{1}.{2} failed with message {3}", check.Category, check.Container, check.ShortName, check.LastFailureMessage);
231 }
232 }
233 }
234 }
235 }
236
237 private static void OutputAllChecksToConsole(ICommandConsole con)
238 {
239 foreach (var category in RegisteredChecks.Values)
240 {
241 OutputCategoryChecksToConsole(con, category);
242 }
243 }
244
245 private static void OutputCategoryChecksToConsole(
246 ICommandConsole con, SortedDictionary<string, SortedDictionary<string, Check>> category)
247 {
248 foreach (var container in category.Values)
249 {
250 OutputContainerChecksToConsole(con, container);
251 }
252 }
253
254 private static void OutputContainerChecksToConsole(ICommandConsole con, SortedDictionary<string, Check> container)
255 {
256 foreach (Check check in container.Values)
257 {
258 con.Output(check.ToConsoleString());
259 }
260 }
261 }
262} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/Interfaces/IPullStatsProvider.cs b/OpenSim/Framework/Monitoring/Interfaces/IPullStatsProvider.cs
new file mode 100644
index 0000000..86a6620
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Interfaces/IPullStatsProvider.cs
@@ -0,0 +1,41 @@
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
28namespace OpenSim.Framework.Monitoring.Interfaces
29{
30 /// <summary>
31 /// Implemented by objects which allow statistical information to be pulled from them.
32 /// </summary>
33 public interface IPullStatsProvider
34 {
35 /// <summary>
36 /// Provide statistical information. Only temporary one long string.
37 /// </summary>
38 /// <returns></returns>
39 string GetStats();
40 }
41}
diff --git a/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs b/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs
new file mode 100644
index 0000000..40df562
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Interfaces/IStatsCollector.cs
@@ -0,0 +1,58 @@
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 OpenMetaverse.StructuredData;
29
30namespace OpenSim.Framework.Monitoring
31{
32 /// <summary>
33 /// Implemented by classes which collect up non-viewer statistical information
34 /// </summary>
35 public interface IStatsCollector
36 {
37 /// <summary>
38 /// Report back collected statistical information.
39 /// </summary>
40 /// <returns></returns>
41 string Report();
42
43 /// <summary>
44 /// Report back collected statistical information in json
45 /// </summary>
46 /// <returns>
47 /// A <see cref="System.String"/>
48 /// </returns>
49 string XReport(string uptime, string version);
50
51 /// <summary>
52 /// Report back collected statistical information as an OSDMap of key/values
53 /// </summary>
54 /// <returns>
55 /// </returns>
56 OSDMap OReport(string uptime, string version);
57 }
58}
diff --git a/OpenSim/Framework/Monitoring/JobEngine.cs b/OpenSim/Framework/Monitoring/JobEngine.cs
new file mode 100644
index 0000000..6db9a67
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/JobEngine.cs
@@ -0,0 +1,341 @@
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.Concurrent;
30using System.Reflection;
31using System.Threading;
32using log4net;
33using OpenSim.Framework;
34
35namespace OpenSim.Framework.Monitoring
36{
37 public class JobEngine
38 {
39 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
40
41 public int LogLevel { get; set; }
42
43 public string Name { get; private set; }
44
45 public string LoggingName { get; private set; }
46
47 /// <summary>
48 /// Is this engine running?
49 /// </summary>
50 public bool IsRunning { get; private set; }
51
52 /// <summary>
53 /// The current job that the engine is running.
54 /// </summary>
55 /// <remarks>
56 /// Will be null if no job is currently running.
57 /// </remarks>
58 public Job CurrentJob { get; private set; }
59
60 /// <summary>
61 /// Number of jobs waiting to be processed.
62 /// </summary>
63 public int JobsWaiting { get { return m_jobQueue.Count; } }
64
65 /// <summary>
66 /// The timeout in milliseconds to wait for at least one event to be written when the recorder is stopping.
67 /// </summary>
68 public int RequestProcessTimeoutOnStop { get; set; }
69
70 /// <summary>
71 /// Controls whether we need to warn in the log about exceeding the max queue size.
72 /// </summary>
73 /// <remarks>
74 /// This is flipped to false once queue max has been exceeded and back to true when it falls below max, in
75 /// order to avoid spamming the log with lots of warnings.
76 /// </remarks>
77 private bool m_warnOverMaxQueue = true;
78
79 private BlockingCollection<Job> m_jobQueue;
80
81 private CancellationTokenSource m_cancelSource;
82
83 /// <summary>
84 /// Used to signal that we are ready to complete stop.
85 /// </summary>
86 private ManualResetEvent m_finishedProcessingAfterStop = new ManualResetEvent(false);
87
88 public JobEngine(string name, string loggingName)
89 {
90 Name = name;
91 LoggingName = loggingName;
92
93 RequestProcessTimeoutOnStop = 5000;
94 }
95
96 public void Start()
97 {
98 lock (this)
99 {
100 if (IsRunning)
101 return;
102
103 IsRunning = true;
104
105 m_finishedProcessingAfterStop.Reset();
106
107 m_jobQueue = new BlockingCollection<Job>(new ConcurrentQueue<Job>(), 5000);
108 m_cancelSource = new CancellationTokenSource();
109
110 WorkManager.StartThread(
111 ProcessRequests,
112 Name,
113 ThreadPriority.Normal,
114 false,
115 true,
116 null,
117 int.MaxValue);
118 }
119 }
120
121 public void Stop()
122 {
123 lock (this)
124 {
125 try
126 {
127 if (!IsRunning)
128 return;
129
130 IsRunning = false;
131
132 int requestsLeft = m_jobQueue.Count;
133
134 if (requestsLeft <= 0)
135 {
136 m_cancelSource.Cancel();
137 }
138 else
139 {
140 m_log.InfoFormat("[{0}]: Waiting to write {1} events after stop.", LoggingName, requestsLeft);
141
142 while (requestsLeft > 0)
143 {
144 if (!m_finishedProcessingAfterStop.WaitOne(RequestProcessTimeoutOnStop))
145 {
146 // After timeout no events have been written
147 if (requestsLeft == m_jobQueue.Count)
148 {
149 m_log.WarnFormat(
150 "[{0}]: No requests processed after {1} ms wait. Discarding remaining {2} requests",
151 LoggingName, RequestProcessTimeoutOnStop, requestsLeft);
152
153 break;
154 }
155 }
156
157 requestsLeft = m_jobQueue.Count;
158 }
159 }
160 }
161 finally
162 {
163 m_cancelSource.Dispose();
164 }
165 }
166 }
167
168 /// <summary>
169 /// Make a job.
170 /// </summary>
171 /// <remarks>
172 /// We provide this method to replace the constructor so that we can later pool job objects if necessary to
173 /// reduce memory churn. Normally one would directly call QueueJob() with parameters anyway.
174 /// </remarks>
175 /// <returns></returns>
176 /// <param name="name">Name.</param>
177 /// <param name="action">Action.</param>
178 /// <param name="commonId">Common identifier.</param>
179 public static Job MakeJob(string name, Action action, string commonId = null)
180 {
181 return Job.MakeJob(name, action, commonId);
182 }
183
184 /// <summary>
185 /// Remove the next job queued for processing.
186 /// </summary>
187 /// <remarks>
188 /// Returns null if there is no next job.
189 /// Will not remove a job currently being performed.
190 /// </remarks>
191 public Job RemoveNextJob()
192 {
193 Job nextJob;
194 m_jobQueue.TryTake(out nextJob);
195
196 return nextJob;
197 }
198
199 /// <summary>
200 /// Queue the job for processing.
201 /// </summary>
202 /// <returns><c>true</c>, if job was queued, <c>false</c> otherwise.</returns>
203 /// <param name="name">Name of job. This appears on the console and in logging.</param>
204 /// <param name="action">Action to perform.</param>
205 /// <param name="commonId">
206 /// Common identifier for a set of jobs. This is allows a set of jobs to be removed
207 /// if required (e.g. all jobs for a given agent. Optional.
208 /// </param>
209 public bool QueueJob(string name, Action action, string commonId = null)
210 {
211 return QueueJob(MakeJob(name, action, commonId));
212 }
213
214 /// <summary>
215 /// Queue the job for processing.
216 /// </summary>
217 /// <returns><c>true</c>, if job was queued, <c>false</c> otherwise.</returns>
218 /// <param name="job">The job</param>
219 /// </param>
220 public bool QueueJob(Job job)
221 {
222 if (m_jobQueue.Count < m_jobQueue.BoundedCapacity)
223 {
224 m_jobQueue.Add(job);
225
226 if (!m_warnOverMaxQueue)
227 m_warnOverMaxQueue = true;
228
229 return true;
230 }
231 else
232 {
233 if (m_warnOverMaxQueue)
234 {
235 m_log.WarnFormat(
236 "[{0}]: Job queue at maximum capacity, not recording job from {1} in {2}",
237 LoggingName, job.Name, Name);
238
239 m_warnOverMaxQueue = false;
240 }
241
242 return false;
243 }
244 }
245
246 private void ProcessRequests()
247 {
248 try
249 {
250 while (IsRunning || m_jobQueue.Count > 0)
251 {
252 try
253 {
254 CurrentJob = m_jobQueue.Take(m_cancelSource.Token);
255 }
256 catch (ObjectDisposedException e)
257 {
258 // If we see this whilst not running then it may be due to a race where this thread checks
259 // IsRunning after the stopping thread sets it to false and disposes of the cancellation source.
260 if (IsRunning)
261 throw e;
262 else
263 break;
264 }
265
266 if (LogLevel >= 1)
267 m_log.DebugFormat("[{0}]: Processing job {1}", LoggingName, CurrentJob.Name);
268
269 try
270 {
271 CurrentJob.Action();
272 }
273 catch (Exception e)
274 {
275 m_log.Error(
276 string.Format(
277 "[{0}]: Job {1} failed, continuing. Exception ", LoggingName, CurrentJob.Name), e);
278 }
279
280 if (LogLevel >= 1)
281 m_log.DebugFormat("[{0}]: Processed job {1}", LoggingName, CurrentJob.Name);
282
283 CurrentJob = null;
284 }
285 }
286 catch (OperationCanceledException)
287 {
288 }
289
290 m_finishedProcessingAfterStop.Set();
291 }
292
293 public class Job
294 {
295 /// <summary>
296 /// Name of the job.
297 /// </summary>
298 /// <remarks>
299 /// This appears on console and debug output.
300 /// </remarks>
301 public string Name { get; private set; }
302
303 /// <summary>
304 /// Common ID for this job.
305 /// </summary>
306 /// <remarks>
307 /// This allows all jobs with a certain common ID (e.g. a client UUID) to be removed en-masse if required.
308 /// Can be null if this is not required.
309 /// </remarks>
310 public string CommonId { get; private set; }
311
312 /// <summary>
313 /// Action to perform when this job is processed.
314 /// </summary>
315 public Action Action { get; private set; }
316
317 private Job(string name, string commonId, Action action)
318 {
319 Name = name;
320 CommonId = commonId;
321 Action = action;
322 }
323
324 /// <summary>
325 /// Make a job. It needs to be separately queued.
326 /// </summary>
327 /// <remarks>
328 /// We provide this method to replace the constructor so that we can pool job objects if necessary to
329 /// to reduce memory churn. Normally one would directly call JobEngine.QueueJob() with parameters anyway.
330 /// </remarks>
331 /// <returns></returns>
332 /// <param name="name">Name.</param>
333 /// <param name="action">Action.</param>
334 /// <param name="commonId">Common identifier.</param>
335 public static Job MakeJob(string name, Action action, string commonId = null)
336 {
337 return new Job(name, commonId, action);
338 }
339 }
340 }
341} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/MemoryWatchdog.cs b/OpenSim/Framework/Monitoring/MemoryWatchdog.cs
new file mode 100644
index 0000000..c474622
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/MemoryWatchdog.cs
@@ -0,0 +1,137 @@
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.Linq;
31using System.Reflection;
32using System.Threading;
33using log4net;
34
35namespace OpenSim.Framework.Monitoring
36{
37 /// <summary>
38 /// Experimental watchdog for memory usage.
39 /// </summary>
40 public static class MemoryWatchdog
41 {
42// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
43
44 /// <summary>
45 /// Is this watchdog active?
46 /// </summary>
47 public static bool Enabled
48 {
49 get { return m_enabled; }
50 set
51 {
52// m_log.DebugFormat("[MEMORY WATCHDOG]: Setting MemoryWatchdog.Enabled to {0}", value);
53
54 if (value && !m_enabled)
55 UpdateLastRecord(GC.GetTotalMemory(false), Util.EnvironmentTickCount());
56
57 m_enabled = value;
58 }
59 }
60 private static bool m_enabled;
61
62 /// <summary>
63 /// Average heap allocation rate in bytes per millisecond.
64 /// </summary>
65 public static double AverageHeapAllocationRate
66 {
67 get { if (m_samples.Count > 0) return m_samples.Average(); else return 0; }
68 }
69
70 /// <summary>
71 /// Last heap allocation in bytes
72 /// </summary>
73 public static double LastHeapAllocationRate
74 {
75 get { if (m_samples.Count > 0) return m_samples.Last(); else return 0; }
76 }
77
78 /// <summary>
79 /// Maximum number of statistical samples.
80 /// </summary>
81 /// <remarks>
82 /// At the moment this corresponds to 1 minute since the sampling rate is every 2.5 seconds as triggered from
83 /// the main Watchdog.
84 /// </remarks>
85 private static int m_maxSamples = 24;
86
87 /// <summary>
88 /// Time when the watchdog was last updated.
89 /// </summary>
90 private static int m_lastUpdateTick;
91
92 /// <summary>
93 /// Memory used at time of last watchdog update.
94 /// </summary>
95 private static long m_lastUpdateMemory;
96
97 /// <summary>
98 /// Memory churn rate per millisecond.
99 /// </summary>
100// private static double m_churnRatePerMillisecond;
101
102 /// <summary>
103 /// Historical samples for calculating moving average.
104 /// </summary>
105 private static Queue<double> m_samples = new Queue<double>(m_maxSamples);
106
107 public static void Update()
108 {
109 int now = Util.EnvironmentTickCount();
110 long memoryNow = GC.GetTotalMemory(false);
111 long memoryDiff = memoryNow - m_lastUpdateMemory;
112
113 if (memoryDiff >= 0)
114 {
115 if (m_samples.Count >= m_maxSamples)
116 m_samples.Dequeue();
117
118 double elapsed = Util.EnvironmentTickCountSubtract(now, m_lastUpdateTick);
119
120 // This should never happen since it's not useful for updates to occur with no time elapsed, but
121 // protect ourselves from a divide-by-zero just in case.
122 if (elapsed == 0)
123 return;
124
125 m_samples.Enqueue(memoryDiff / (double)elapsed);
126 }
127
128 UpdateLastRecord(memoryNow, now);
129 }
130
131 private static void UpdateLastRecord(long memoryNow, int timeNow)
132 {
133 m_lastUpdateMemory = memoryNow;
134 m_lastUpdateTick = timeNow;
135 }
136 }
137} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs b/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b08e4f7
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
1using System.Reflection;
2using System.Runtime.CompilerServices;
3using System.Runtime.InteropServices;
4
5// General Information about an assembly is controlled through the following
6// set of attributes. Change these attribute values to modify the information
7// associated with an assembly.
8[assembly: AssemblyTitle("OpenSim.Framework.Monitoring")]
9[assembly: AssemblyDescription("")]
10[assembly: AssemblyConfiguration("")]
11[assembly: AssemblyCompany("http://opensimulator.org")]
12[assembly: AssemblyProduct("OpenSim")]
13[assembly: AssemblyCopyright("OpenSimulator developers")]
14[assembly: AssemblyTrademark("")]
15[assembly: AssemblyCulture("")]
16
17// Setting ComVisible to false makes the types in this assembly not visible
18// to COM components. If you need to access a type in this assembly from
19// COM, set the ComVisible attribute to true on that type.
20[assembly: ComVisible(false)]
21
22// The following GUID is for the ID of the typelib if this project is exposed to COM
23[assembly: Guid("74506fe3-2f9d-44c1-94c9-a30f79d9e0cb")]
24
25// Version information for an assembly consists of the following four values:
26//
27// Major Version
28// Minor Version
29// Build Number
30// Revision
31//
32[assembly: AssemblyVersion("0.8.2.*")]
33
diff --git a/OpenSim/Framework/Monitoring/ServerStatsCollector.cs b/OpenSim/Framework/Monitoring/ServerStatsCollector.cs
new file mode 100644
index 0000000..77315bb
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/ServerStatsCollector.cs
@@ -0,0 +1,346 @@
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.Linq;
32using System.Net.NetworkInformation;
33using System.Text;
34using System.Threading;
35using log4net;
36using Nini.Config;
37using OpenMetaverse.StructuredData;
38using OpenSim.Framework;
39
40namespace OpenSim.Framework.Monitoring
41{
42 public class ServerStatsCollector
43 {
44 private readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
45 private readonly string LogHeader = "[SERVER STATS]";
46
47 public bool Enabled = false;
48 private static Dictionary<string, Stat> RegisteredStats = new Dictionary<string, Stat>();
49
50 public readonly string CategoryServer = "server";
51
52 public readonly string ContainerThreadpool = "threadpool";
53 public readonly string ContainerProcessor = "processor";
54 public readonly string ContainerMemory = "memory";
55 public readonly string ContainerNetwork = "network";
56 public readonly string ContainerProcess = "process";
57
58 public string NetworkInterfaceTypes = "Ethernet";
59
60 readonly int performanceCounterSampleInterval = 500;
61// int lastperformanceCounterSampleTime = 0;
62
63 private class PerfCounterControl
64 {
65 public PerformanceCounter perfCounter;
66 public int lastFetch;
67 public string name;
68 public PerfCounterControl(PerformanceCounter pPc)
69 : this(pPc, String.Empty)
70 {
71 }
72 public PerfCounterControl(PerformanceCounter pPc, string pName)
73 {
74 perfCounter = pPc;
75 lastFetch = 0;
76 name = pName;
77 }
78 }
79
80 PerfCounterControl processorPercentPerfCounter = null;
81
82 // IRegionModuleBase.Initialize
83 public void Initialise(IConfigSource source)
84 {
85 if (source == null)
86 return;
87
88 IConfig cfg = source.Configs["Monitoring"];
89
90 if (cfg != null)
91 Enabled = cfg.GetBoolean("ServerStatsEnabled", true);
92
93 if (Enabled)
94 {
95 NetworkInterfaceTypes = cfg.GetString("NetworkInterfaceTypes", "Ethernet");
96 }
97 }
98
99 public void Start()
100 {
101 if (RegisteredStats.Count == 0)
102 RegisterServerStats();
103 }
104
105 public void Close()
106 {
107 if (RegisteredStats.Count > 0)
108 {
109 foreach (Stat stat in RegisteredStats.Values)
110 {
111 StatsManager.DeregisterStat(stat);
112 stat.Dispose();
113 }
114 RegisteredStats.Clear();
115 }
116 }
117
118 private void MakeStat(string pName, string pDesc, string pUnit, string pContainer, Action<Stat> act)
119 {
120 MakeStat(pName, pDesc, pUnit, pContainer, act, MeasuresOfInterest.None);
121 }
122
123 private void MakeStat(string pName, string pDesc, string pUnit, string pContainer, Action<Stat> act, MeasuresOfInterest moi)
124 {
125 string desc = pDesc;
126 if (desc == null)
127 desc = pName;
128 Stat stat = new Stat(pName, pName, desc, pUnit, CategoryServer, pContainer, StatType.Pull, moi, act, StatVerbosity.Debug);
129 StatsManager.RegisterStat(stat);
130 RegisteredStats.Add(pName, stat);
131 }
132
133 public void RegisterServerStats()
134 {
135// lastperformanceCounterSampleTime = Util.EnvironmentTickCount();
136 PerformanceCounter tempPC;
137 Stat tempStat;
138 string tempName;
139
140 try
141 {
142 tempName = "CPUPercent";
143 tempPC = new PerformanceCounter("Processor", "% Processor Time", "_Total");
144 processorPercentPerfCounter = new PerfCounterControl(tempPC);
145 // A long time bug in mono is that CPU percent is reported as CPU percent idle. Windows reports CPU percent busy.
146 tempStat = new Stat(tempName, tempName, "", "percent", CategoryServer, ContainerProcessor,
147 StatType.Pull, (s) => { GetNextValue(s, processorPercentPerfCounter); },
148 StatVerbosity.Info);
149 StatsManager.RegisterStat(tempStat);
150 RegisteredStats.Add(tempName, tempStat);
151
152 MakeStat("TotalProcessorTime", null, "sec", ContainerProcessor,
153 (s) => { s.Value = Math.Round(Process.GetCurrentProcess().TotalProcessorTime.TotalSeconds, 3); });
154
155 MakeStat("UserProcessorTime", null, "sec", ContainerProcessor,
156 (s) => { s.Value = Math.Round(Process.GetCurrentProcess().UserProcessorTime.TotalSeconds, 3); });
157
158 MakeStat("PrivilegedProcessorTime", null, "sec", ContainerProcessor,
159 (s) => { s.Value = Math.Round(Process.GetCurrentProcess().PrivilegedProcessorTime.TotalSeconds, 3); });
160
161 MakeStat("Threads", null, "threads", ContainerProcessor,
162 (s) => { s.Value = Process.GetCurrentProcess().Threads.Count; });
163 }
164 catch (Exception e)
165 {
166 m_log.ErrorFormat("{0} Exception creating 'Process': {1}", LogHeader, e);
167 }
168
169 MakeStat("BuiltinThreadpoolWorkerThreadsAvailable", null, "threads", ContainerThreadpool,
170 s =>
171 {
172 int workerThreads, iocpThreads;
173 ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
174 s.Value = workerThreads;
175 });
176
177 MakeStat("BuiltinThreadpoolIOCPThreadsAvailable", null, "threads", ContainerThreadpool,
178 s =>
179 {
180 int workerThreads, iocpThreads;
181 ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
182 s.Value = iocpThreads;
183 });
184
185 if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool && Util.GetSmartThreadPoolInfo() != null)
186 {
187 MakeStat("STPMaxThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MaxThreads);
188 MakeStat("STPMinThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MinThreads);
189 MakeStat("STPConcurrency", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MaxConcurrentWorkItems);
190 MakeStat("STPActiveThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().ActiveThreads);
191 MakeStat("STPInUseThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().InUseThreads);
192 MakeStat("STPWorkItemsWaiting", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().WaitingCallbacks);
193 }
194
195 MakeStat(
196 "HTTPRequestsMade",
197 "Number of outbound HTTP requests made",
198 "requests",
199 ContainerNetwork,
200 s => s.Value = WebUtil.RequestNumber,
201 MeasuresOfInterest.AverageChangeOverTime);
202
203 try
204 {
205 List<string> okInterfaceTypes = new List<string>(NetworkInterfaceTypes.Split(','));
206
207 IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces();
208 foreach (NetworkInterface nic in nics)
209 {
210 if (nic.OperationalStatus != OperationalStatus.Up)
211 continue;
212
213 string nicInterfaceType = nic.NetworkInterfaceType.ToString();
214 if (!okInterfaceTypes.Contains(nicInterfaceType))
215 {
216 m_log.DebugFormat("{0} Not including stats for network interface '{1}' of type '{2}'.",
217 LogHeader, nic.Name, nicInterfaceType);
218 m_log.DebugFormat("{0} To include, add to comma separated list in [Monitoring]NetworkInterfaceTypes={1}",
219 LogHeader, NetworkInterfaceTypes);
220 continue;
221 }
222
223 if (nic.Supports(NetworkInterfaceComponent.IPv4))
224 {
225 IPv4InterfaceStatistics nicStats = nic.GetIPv4Statistics();
226 if (nicStats != null)
227 {
228 MakeStat("BytesRcvd/" + nic.Name, nic.Name, "KB", ContainerNetwork,
229 (s) => { LookupNic(s, (ns) => { return ns.BytesReceived; }, 1024.0); });
230 MakeStat("BytesSent/" + nic.Name, nic.Name, "KB", ContainerNetwork,
231 (s) => { LookupNic(s, (ns) => { return ns.BytesSent; }, 1024.0); });
232 MakeStat("TotalBytes/" + nic.Name, nic.Name, "KB", ContainerNetwork,
233 (s) => { LookupNic(s, (ns) => { return ns.BytesSent + ns.BytesReceived; }, 1024.0); });
234 }
235 }
236 // TODO: add IPv6 (it may actually happen someday)
237 }
238 }
239 catch (Exception e)
240 {
241 m_log.ErrorFormat("{0} Exception creating 'Network Interface': {1}", LogHeader, e);
242 }
243
244 MakeStat("ProcessMemory", null, "MB", ContainerMemory,
245 (s) => { s.Value = Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024d / 1024d, 3); });
246 MakeStat("HeapMemory", null, "MB", ContainerMemory,
247 (s) => { s.Value = Math.Round(GC.GetTotalMemory(false) / 1024d / 1024d, 3); });
248 MakeStat("LastHeapAllocationRate", null, "MB/sec", ContainerMemory,
249 (s) => { s.Value = Math.Round(MemoryWatchdog.LastHeapAllocationRate * 1000d / 1024d / 1024d, 3); });
250 MakeStat("AverageHeapAllocationRate", null, "MB/sec", ContainerMemory,
251 (s) => { s.Value = Math.Round(MemoryWatchdog.AverageHeapAllocationRate * 1000d / 1024d / 1024d, 3); });
252 }
253
254 // Notes on performance counters:
255 // "How To Read Performance Counters": http://blogs.msdn.com/b/bclteam/archive/2006/06/02/618156.aspx
256 // "How to get the CPU Usage in C#": http://stackoverflow.com/questions/278071/how-to-get-the-cpu-usage-in-c
257 // "Mono Performance Counters": http://www.mono-project.com/Mono_Performance_Counters
258 private delegate double PerfCounterNextValue();
259
260 private void GetNextValue(Stat stat, PerfCounterControl perfControl)
261 {
262 if (Util.EnvironmentTickCountSubtract(perfControl.lastFetch) > performanceCounterSampleInterval)
263 {
264 if (perfControl != null && perfControl.perfCounter != null)
265 {
266 try
267 {
268 stat.Value = Math.Round(perfControl.perfCounter.NextValue(), 3);
269 }
270 catch (Exception e)
271 {
272 m_log.ErrorFormat("{0} Exception on NextValue fetching {1}: {2}", LogHeader, stat.Name, e);
273 }
274
275 perfControl.lastFetch = Util.EnvironmentTickCount();
276 }
277 }
278 }
279
280 // Lookup the nic that goes with this stat and set the value by using a fetch action.
281 // Not sure about closure with delegates inside delegates.
282 private delegate double GetIPv4StatValue(IPv4InterfaceStatistics interfaceStat);
283 private void LookupNic(Stat stat, GetIPv4StatValue getter, double factor)
284 {
285 // Get the one nic that has the name of this stat
286 IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces().Where(
287 (network) => network.Name == stat.Description);
288 try
289 {
290 foreach (NetworkInterface nic in nics)
291 {
292 IPv4InterfaceStatistics intrStats = nic.GetIPv4Statistics();
293 if (intrStats != null)
294 {
295 double newVal = Math.Round(getter(intrStats) / factor, 3);
296 stat.Value = newVal;
297 }
298 break;
299 }
300 }
301 catch
302 {
303 // There are times interfaces go away so we just won't update the stat for this
304 m_log.ErrorFormat("{0} Exception fetching stat on interface '{1}'", LogHeader, stat.Description);
305 }
306 }
307 }
308
309 public class ServerStatsAggregator : Stat
310 {
311 public ServerStatsAggregator(
312 string shortName,
313 string name,
314 string description,
315 string unitName,
316 string category,
317 string container
318 )
319 : base(
320 shortName,
321 name,
322 description,
323 unitName,
324 category,
325 container,
326 StatType.Push,
327 MeasuresOfInterest.None,
328 null,
329 StatVerbosity.Info)
330 {
331 }
332 public override string ToConsoleString()
333 {
334 StringBuilder sb = new StringBuilder();
335
336 return sb.ToString();
337 }
338
339 public override OSDMap ToOSDMap()
340 {
341 OSDMap ret = new OSDMap();
342
343 return ret;
344 }
345 }
346}
diff --git a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs
new file mode 100755
index 0000000..e4df7ee
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs
@@ -0,0 +1,536 @@
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.Linq;
32using System.Text;
33using OpenMetaverse;
34using OpenMetaverse.StructuredData;
35using OpenSim.Framework.Monitoring.Interfaces;
36
37namespace OpenSim.Framework.Monitoring
38{
39 /// <summary>
40 /// Collects sim statistics which aren't already being collected for the linden viewer's statistics pane
41 /// </summary>
42 public class SimExtraStatsCollector : BaseStatsCollector
43 {
44// private long assetsInCache;
45// private long texturesInCache;
46// private long assetCacheMemoryUsage;
47// private long textureCacheMemoryUsage;
48// private TimeSpan assetRequestTimeAfterCacheMiss;
49// private long blockedMissingTextureRequests;
50
51// private long assetServiceRequestFailures;
52// private long inventoryServiceRetrievalFailures;
53
54 private volatile float timeDilation;
55 private volatile float simFps;
56 private volatile float physicsFps;
57 private volatile float agentUpdates;
58 private volatile float rootAgents;
59 private volatile float childAgents;
60 private volatile float totalPrims;
61 private volatile float activePrims;
62 private volatile float totalFrameTime;
63 private volatile float netFrameTime;
64 private volatile float physicsFrameTime;
65 private volatile float otherFrameTime;
66 private volatile float imageFrameTime;
67 private volatile float inPacketsPerSecond;
68 private volatile float outPacketsPerSecond;
69 private volatile float unackedBytes;
70 private volatile float agentFrameTime;
71 private volatile float pendingDownloads;
72 private volatile float pendingUploads;
73 private volatile float activeScripts;
74 private volatile float scriptLinesPerSecond;
75 private volatile float m_frameDilation;
76 private volatile float m_usersLoggingIn;
77 private volatile float m_totalGeoPrims;
78 private volatile float m_totalMeshes;
79 private volatile float m_inUseThreads;
80
81// /// <summary>
82// /// These statistics are being collected by push rather than pull. Pull would be simpler, but I had the
83// /// notion of providing some flow statistics (which pull wouldn't give us). Though admittedly these
84// /// haven't yet been implemented...
85// /// </summary>
86// public long AssetsInCache { get { return assetsInCache; } }
87//
88// /// <value>
89// /// Currently unused
90// /// </value>
91// public long TexturesInCache { get { return texturesInCache; } }
92//
93// /// <value>
94// /// Currently misleading since we can't currently subtract removed asset memory usage without a performance hit
95// /// </value>
96// public long AssetCacheMemoryUsage { get { return assetCacheMemoryUsage; } }
97//
98// /// <value>
99// /// Currently unused
100// /// </value>
101// public long TextureCacheMemoryUsage { get { return textureCacheMemoryUsage; } }
102
103 public float TimeDilation { get { return timeDilation; } }
104 public float SimFps { get { return simFps; } }
105 public float PhysicsFps { get { return physicsFps; } }
106 public float AgentUpdates { get { return agentUpdates; } }
107 public float RootAgents { get { return rootAgents; } }
108 public float ChildAgents { get { return childAgents; } }
109 public float TotalPrims { get { return totalPrims; } }
110 public float ActivePrims { get { return activePrims; } }
111 public float TotalFrameTime { get { return totalFrameTime; } }
112 public float NetFrameTime { get { return netFrameTime; } }
113 public float PhysicsFrameTime { get { return physicsFrameTime; } }
114 public float OtherFrameTime { get { return otherFrameTime; } }
115 public float ImageFrameTime { get { return imageFrameTime; } }
116 public float InPacketsPerSecond { get { return inPacketsPerSecond; } }
117 public float OutPacketsPerSecond { get { return outPacketsPerSecond; } }
118 public float UnackedBytes { get { return unackedBytes; } }
119 public float AgentFrameTime { get { return agentFrameTime; } }
120 public float PendingDownloads { get { return pendingDownloads; } }
121 public float PendingUploads { get { return pendingUploads; } }
122 public float ActiveScripts { get { return activeScripts; } }
123 public float ScriptLinesPerSecond { get { return scriptLinesPerSecond; } }
124
125// /// <summary>
126// /// This is the time it took for the last asset request made in response to a cache miss.
127// /// </summary>
128// public TimeSpan AssetRequestTimeAfterCacheMiss { get { return assetRequestTimeAfterCacheMiss; } }
129//
130// /// <summary>
131// /// Number of persistent requests for missing textures we have started blocking from clients. To some extent
132// /// this is just a temporary statistic to keep this problem in view - the root cause of this lies either
133// /// in a mishandling of the reply protocol, related to avatar appearance or may even originate in graphics
134// /// driver bugs on clients (though this seems less likely).
135// /// </summary>
136// public long BlockedMissingTextureRequests { get { return blockedMissingTextureRequests; } }
137//
138// /// <summary>
139// /// Record the number of times that an asset request has failed. Failures are effectively exceptions, such as
140// /// request timeouts. If an asset service replies that a particular asset cannot be found, this is not counted
141// /// as a failure
142// /// </summary>
143// public long AssetServiceRequestFailures { get { return assetServiceRequestFailures; } }
144
145 /// <summary>
146 /// Number of known failures to retrieve avatar inventory from the inventory service. This does not
147 /// cover situations where the inventory service accepts the request but never returns any data, since
148 /// we do not yet timeout this situation.
149 /// </summary>
150 /// <remarks>Commented out because we do not cache inventory at this point</remarks>
151// public long InventoryServiceRetrievalFailures { get { return inventoryServiceRetrievalFailures; } }
152
153 /// <summary>
154 /// Retrieve the total frame time (in ms) of the last frame
155 /// </summary>
156 //public float TotalFrameTime { get { return totalFrameTime; } }
157
158 /// <summary>
159 /// Retrieve the physics update component (in ms) of the last frame
160 /// </summary>
161 //public float PhysicsFrameTime { get { return physicsFrameTime; } }
162
163 /// <summary>
164 /// Retain a dictionary of all packet queues stats reporters
165 /// </summary>
166 private IDictionary<UUID, PacketQueueStatsCollector> packetQueueStatsCollectors
167 = new Dictionary<UUID, PacketQueueStatsCollector>();
168
169// public void AddAsset(AssetBase asset)
170// {
171// assetsInCache++;
172// //assetCacheMemoryUsage += asset.Data.Length;
173// }
174//
175// public void RemoveAsset(UUID uuid)
176// {
177// assetsInCache--;
178// }
179//
180// public void AddTexture(AssetBase image)
181// {
182// if (image.Data != null)
183// {
184// texturesInCache++;
185//
186// // This could have been a pull stat, though there was originally a nebulous idea to measure flow rates
187// textureCacheMemoryUsage += image.Data.Length;
188// }
189// }
190//
191// /// <summary>
192// /// Signal that the asset cache has been cleared.
193// /// </summary>
194// public void ClearAssetCacheStatistics()
195// {
196// assetsInCache = 0;
197// assetCacheMemoryUsage = 0;
198// texturesInCache = 0;
199// textureCacheMemoryUsage = 0;
200// }
201//
202// public void AddAssetRequestTimeAfterCacheMiss(TimeSpan ts)
203// {
204// assetRequestTimeAfterCacheMiss = ts;
205// }
206//
207// public void AddBlockedMissingTextureRequest()
208// {
209// blockedMissingTextureRequests++;
210// }
211//
212// public void AddAssetServiceRequestFailure()
213// {
214// assetServiceRequestFailures++;
215// }
216
217// public void AddInventoryServiceRetrievalFailure()
218// {
219// inventoryServiceRetrievalFailures++;
220// }
221
222 /// <summary>
223 /// Register as a packet queue stats provider
224 /// </summary>
225 /// <param name="uuid">An agent UUID</param>
226 /// <param name="provider"></param>
227 public void RegisterPacketQueueStatsProvider(UUID uuid, IPullStatsProvider provider)
228 {
229 lock (packetQueueStatsCollectors)
230 {
231 // FIXME: If the region service is providing more than one region, then the child and root agent
232 // queues are wrongly replacing each other here.
233 packetQueueStatsCollectors[uuid] = new PacketQueueStatsCollector(provider);
234 }
235 }
236
237 /// <summary>
238 /// Deregister a packet queue stats provider
239 /// </summary>
240 /// <param name="uuid">An agent UUID</param>
241 public void DeregisterPacketQueueStatsProvider(UUID uuid)
242 {
243 lock (packetQueueStatsCollectors)
244 {
245 packetQueueStatsCollectors.Remove(uuid);
246 }
247 }
248
249 /// <summary>
250 /// This is the method on which the classic sim stats reporter (which collects stats for
251 /// client purposes) sends information to listeners.
252 /// </summary>
253 /// <param name="pack"></param>
254 public void ReceiveClassicSimStatsPacket(SimStats stats)
255 {
256 // FIXME: SimStats shouldn't allow an arbitrary stat packing order (which is inherited from the original
257 // SimStatsPacket that was being used).
258
259 // For an unknown reason the original designers decided not to
260 // include the spare MS statistic inside of this class, this is
261 // located inside the StatsBlock at location 21, thus it is skipped
262 timeDilation = stats.StatsBlock[0].StatValue;
263 simFps = stats.StatsBlock[1].StatValue;
264 physicsFps = stats.StatsBlock[2].StatValue;
265 agentUpdates = stats.StatsBlock[3].StatValue;
266 rootAgents = stats.StatsBlock[4].StatValue;
267 childAgents = stats.StatsBlock[5].StatValue;
268 totalPrims = stats.StatsBlock[6].StatValue;
269 activePrims = stats.StatsBlock[7].StatValue;
270 totalFrameTime = stats.StatsBlock[8].StatValue;
271 netFrameTime = stats.StatsBlock[9].StatValue;
272 physicsFrameTime = stats.StatsBlock[10].StatValue;
273 otherFrameTime = stats.StatsBlock[11].StatValue;
274 imageFrameTime = stats.StatsBlock[12].StatValue;
275 inPacketsPerSecond = stats.StatsBlock[13].StatValue;
276 outPacketsPerSecond = stats.StatsBlock[14].StatValue;
277 unackedBytes = stats.StatsBlock[15].StatValue;
278 agentFrameTime = stats.StatsBlock[16].StatValue;
279 pendingDownloads = stats.StatsBlock[17].StatValue;
280 pendingUploads = stats.StatsBlock[18].StatValue;
281 activeScripts = stats.StatsBlock[19].StatValue;
282 scriptLinesPerSecond = stats.StatsBlock[20].StatValue;
283 m_frameDilation = stats.StatsBlock[22].StatValue;
284 m_usersLoggingIn = stats.StatsBlock[23].StatValue;
285 m_totalGeoPrims = stats.StatsBlock[24].StatValue;
286 m_totalMeshes = stats.StatsBlock[25].StatValue;
287 m_inUseThreads = stats.StatsBlock[26].StatValue;
288 }
289
290 /// <summary>
291 /// Report back collected statistical information.
292 /// </summary>
293 /// <returns></returns>
294 public override string Report()
295 {
296 StringBuilder sb = new StringBuilder(Environment.NewLine);
297// sb.Append("ASSET STATISTICS");
298// sb.Append(Environment.NewLine);
299
300 /*
301 sb.Append(
302 string.Format(
303@"Asset cache contains {0,6} non-texture assets using {1,10} K
304Texture cache contains {2,6} texture assets using {3,10} K
305Latest asset request time after cache miss: {4}s
306Blocked client requests for missing textures: {5}
307Asset service request failures: {6}"+ Environment.NewLine,
308 AssetsInCache, Math.Round(AssetCacheMemoryUsage / 1024.0),
309 TexturesInCache, Math.Round(TextureCacheMemoryUsage / 1024.0),
310 assetRequestTimeAfterCacheMiss.Milliseconds / 1000.0,
311 BlockedMissingTextureRequests,
312 AssetServiceRequestFailures));
313 */
314
315 /*
316 sb.Append(
317 string.Format(
318@"Asset cache contains {0,6} assets
319Latest asset request time after cache miss: {1}s
320Blocked client requests for missing textures: {2}
321Asset service request failures: {3}" + Environment.NewLine,
322 AssetsInCache,
323 assetRequestTimeAfterCacheMiss.Milliseconds / 1000.0,
324 BlockedMissingTextureRequests,
325 AssetServiceRequestFailures));
326 */
327
328 sb.Append(Environment.NewLine);
329 sb.Append("CONNECTION STATISTICS");
330 sb.Append(Environment.NewLine);
331
332 List<Stat> stats = StatsManager.GetStatsFromEachContainer("clientstack", "ClientLogoutsDueToNoReceives");
333
334 sb.AppendFormat(
335 "Client logouts due to no data receive timeout: {0}\n\n",
336 stats != null ? stats.Sum(s => s.Value).ToString() : "unknown");
337
338// sb.Append(Environment.NewLine);
339// sb.Append("INVENTORY STATISTICS");
340// sb.Append(Environment.NewLine);
341// sb.Append(
342// string.Format(
343// "Initial inventory caching failures: {0}" + Environment.NewLine,
344// InventoryServiceRetrievalFailures));
345
346 sb.Append(Environment.NewLine);
347 sb.Append("SAMPLE FRAME STATISTICS");
348 sb.Append(Environment.NewLine);
349 sb.Append("Dilatn SimFPS PhyFPS AgntUp RootAg ChldAg Prims AtvPrm AtvScr ScrLPS");
350 sb.Append(Environment.NewLine);
351 sb.Append(
352 string.Format(
353 "{0,6:0.00} {1,6:0} {2,6:0.0} {3,6:0.0} {4,6:0} {5,6:0} {6,6:0} {7,6:0} {8,6:0} {9,6:0}",
354 timeDilation, simFps, physicsFps, agentUpdates, rootAgents,
355 childAgents, totalPrims, activePrims, activeScripts, scriptLinesPerSecond));
356
357 sb.Append(Environment.NewLine);
358 sb.Append(Environment.NewLine);
359 // There is no script frame time currently because we don't yet collect it
360 sb.Append("PktsIn PktOut PendDl PendUl UnackB TotlFt NetFt PhysFt OthrFt AgntFt ImgsFt");
361 sb.Append(Environment.NewLine);
362 sb.Append(
363 string.Format(
364 "{0,6:0} {1,6:0} {2,6:0} {3,6:0} {4,6:0} {5,6:0.0} {6,6:0.0} {7,6:0.0} {8,6:0.0} {9,6:0.0} {10,6:0.0}\n\n",
365 inPacketsPerSecond, outPacketsPerSecond, pendingDownloads, pendingUploads, unackedBytes, totalFrameTime,
366 netFrameTime, physicsFrameTime, otherFrameTime, agentFrameTime, imageFrameTime));
367
368 /* 20130319 RA: For the moment, disable the dump of 'scene' catagory as they are mostly output by
369 * the two formatted printouts above.
370 SortedDictionary<string, SortedDictionary<string, Stat>> sceneStats;
371 if (StatsManager.TryGetStats("scene", out sceneStats))
372 {
373 foreach (KeyValuePair<string, SortedDictionary<string, Stat>> kvp in sceneStats)
374 {
375 foreach (Stat stat in kvp.Value.Values)
376 {
377 if (stat.Verbosity == StatVerbosity.Info)
378 {
379 sb.AppendFormat("{0} ({1}): {2}{3}\n", stat.Name, stat.Container, stat.Value, stat.UnitName);
380 }
381 }
382 }
383 }
384 */
385
386 /*
387 sb.Append(Environment.NewLine);
388 sb.Append("PACKET QUEUE STATISTICS");
389 sb.Append(Environment.NewLine);
390 sb.Append("Agent UUID ");
391 sb.Append(
392 string.Format(
393 " {0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}",
394 "Send", "In", "Out", "Resend", "Land", "Wind", "Cloud", "Task", "Texture", "Asset"));
395 sb.Append(Environment.NewLine);
396
397 foreach (UUID key in packetQueueStatsCollectors.Keys)
398 {
399 sb.Append(string.Format("{0}: ", key));
400 sb.Append(packetQueueStatsCollectors[key].Report());
401 sb.Append(Environment.NewLine);
402 }
403 */
404
405 sb.Append(base.Report());
406
407 return sb.ToString();
408 }
409
410 /// <summary>
411 /// Report back collected statistical information as json serialization.
412 /// </summary>
413 /// <returns></returns>
414 public override string XReport(string uptime, string version)
415 {
416 return OSDParser.SerializeJsonString(OReport(uptime, version));
417 }
418
419 /// <summary>
420 /// Report back collected statistical information as an OSDMap
421 /// </summary>
422 /// <returns></returns>
423 public override OSDMap OReport(string uptime, string version)
424 {
425 // Get the amount of physical memory, allocated with the instance of this program, in kilobytes;
426 // the working set is the set of memory pages currently visible to this program in physical RAM
427 // memory and includes both shared (e.g. system libraries) and private data
428 double memUsage = Process.GetCurrentProcess().WorkingSet64 / 1024.0;
429
430 // Get the number of threads from the system that are currently
431 // running
432 int numberThreadsRunning = 0;
433 foreach (ProcessThread currentThread in
434 Process.GetCurrentProcess().Threads)
435 {
436 // A known issue with the current process .Threads property is
437 // that it can return null threads, thus don't count those as
438 // running threads and prevent the program function from failing
439 if (currentThread != null &&
440 currentThread.ThreadState == ThreadState.Running)
441 {
442 numberThreadsRunning++;
443 }
444 }
445
446 OSDMap args = new OSDMap(30);
447// args["AssetsInCache"] = OSD.FromString (String.Format ("{0:0.##}", AssetsInCache));
448// args["TimeAfterCacheMiss"] = OSD.FromString (String.Format ("{0:0.##}",
449// assetRequestTimeAfterCacheMiss.Milliseconds / 1000.0));
450// args["BlockedMissingTextureRequests"] = OSD.FromString (String.Format ("{0:0.##}",
451// BlockedMissingTextureRequests));
452// args["AssetServiceRequestFailures"] = OSD.FromString (String.Format ("{0:0.##}",
453// AssetServiceRequestFailures));
454// args["abnormalClientThreadTerminations"] = OSD.FromString (String.Format ("{0:0.##}",
455// abnormalClientThreadTerminations));
456// args["InventoryServiceRetrievalFailures"] = OSD.FromString (String.Format ("{0:0.##}",
457// InventoryServiceRetrievalFailures));
458 args["Dilatn"] = OSD.FromString (String.Format ("{0:0.##}", timeDilation));
459 args["SimFPS"] = OSD.FromString (String.Format ("{0:0.##}", simFps));
460 args["PhyFPS"] = OSD.FromString (String.Format ("{0:0.##}", physicsFps));
461 args["AgntUp"] = OSD.FromString (String.Format ("{0:0.##}", agentUpdates));
462 args["RootAg"] = OSD.FromString (String.Format ("{0:0.##}", rootAgents));
463 args["ChldAg"] = OSD.FromString (String.Format ("{0:0.##}", childAgents));
464 args["Prims"] = OSD.FromString (String.Format ("{0:0.##}", totalPrims));
465 args["AtvPrm"] = OSD.FromString (String.Format ("{0:0.##}", activePrims));
466 args["AtvScr"] = OSD.FromString (String.Format ("{0:0.##}", activeScripts));
467 args["ScrLPS"] = OSD.FromString (String.Format ("{0:0.##}", scriptLinesPerSecond));
468 args["PktsIn"] = OSD.FromString (String.Format ("{0:0.##}", inPacketsPerSecond));
469 args["PktOut"] = OSD.FromString (String.Format ("{0:0.##}", outPacketsPerSecond));
470 args["PendDl"] = OSD.FromString (String.Format ("{0:0.##}", pendingDownloads));
471 args["PendUl"] = OSD.FromString (String.Format ("{0:0.##}", pendingUploads));
472 args["UnackB"] = OSD.FromString (String.Format ("{0:0.##}", unackedBytes));
473 args["TotlFt"] = OSD.FromString (String.Format ("{0:0.##}", totalFrameTime));
474 args["NetFt"] = OSD.FromString (String.Format ("{0:0.##}", netFrameTime));
475 args["PhysFt"] = OSD.FromString (String.Format ("{0:0.##}", physicsFrameTime));
476 args["OthrFt"] = OSD.FromString (String.Format ("{0:0.##}", otherFrameTime));
477 args["AgntFt"] = OSD.FromString (String.Format ("{0:0.##}", agentFrameTime));
478 args["ImgsFt"] = OSD.FromString (String.Format ("{0:0.##}", imageFrameTime));
479 args["Memory"] = OSD.FromString (base.XReport (uptime, version));
480 args["Uptime"] = OSD.FromString (uptime);
481 args["Version"] = OSD.FromString (version);
482
483 args["FrameDilatn"] = OSD.FromString(String.Format("{0:0.##}", m_frameDilation));
484 args["Logging in Users"] = OSD.FromString(String.Format("{0:0.##}",
485 m_usersLoggingIn));
486 args["GeoPrims"] = OSD.FromString(String.Format("{0:0.##}",
487 m_totalGeoPrims));
488 args["Mesh Objects"] = OSD.FromString(String.Format("{0:0.##}",
489 m_totalMeshes));
490 args["XEngine Thread Count"] = OSD.FromString(String.Format("{0:0.##}",
491 m_inUseThreads));
492 args["Util Thread Count"] = OSD.FromString(String.Format("{0:0.##}",
493 Util.GetSmartThreadPoolInfo().InUseThreads));
494 args["System Thread Count"] = OSD.FromString(String.Format(
495 "{0:0.##}", numberThreadsRunning));
496 args["ProcMem"] = OSD.FromString(String.Format("{0:#,###,###.##}",
497 memUsage));
498
499 return args;
500 }
501 }
502
503
504 /// <summary>
505 /// Pull packet queue stats from packet queues and report
506 /// </summary>
507 public class PacketQueueStatsCollector : IStatsCollector
508 {
509 private IPullStatsProvider m_statsProvider;
510
511 public PacketQueueStatsCollector(IPullStatsProvider provider)
512 {
513 m_statsProvider = provider;
514 }
515
516 /// <summary>
517 /// Report back collected statistical information.
518 /// </summary>
519 /// <returns></returns>
520 public string Report()
521 {
522 return m_statsProvider.GetStats();
523 }
524
525 public string XReport(string uptime, string version)
526 {
527 return "";
528 }
529
530 public OSDMap OReport(string uptime, string version)
531 {
532 OSDMap ret = new OSDMap();
533 return ret;
534 }
535 }
536}
diff --git a/OpenSim/Framework/Monitoring/Stats/CounterStat.cs b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs
new file mode 100755
index 0000000..318cf1c
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs
@@ -0,0 +1,119 @@
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.Linq;
31using System.Text;
32
33using OpenMetaverse.StructuredData;
34
35namespace OpenSim.Framework.Monitoring
36{
37// A statistic that wraps a counter.
38// Built this way mostly so histograms and history can be created.
39public class CounterStat : Stat
40{
41 private SortedDictionary<string, EventHistogram> m_histograms;
42 private object counterLock = new object();
43
44 public CounterStat(
45 string shortName,
46 string name,
47 string description,
48 string unitName,
49 string category,
50 string container,
51 StatVerbosity verbosity)
52 : base(shortName, name, description, unitName, category, container, StatType.Push, null, verbosity)
53 {
54 m_histograms = new SortedDictionary<string, EventHistogram>();
55 }
56
57 // Histograms are presumably added at intialization time and the list does not change thereafter.
58 // Thus no locking of the histogram list.
59 public void AddHistogram(string histoName, EventHistogram histo)
60 {
61 m_histograms.Add(histoName, histo);
62 }
63
64 public delegate void ProcessHistogram(string name, EventHistogram histo);
65 public void ForEachHistogram(ProcessHistogram process)
66 {
67 foreach (KeyValuePair<string, EventHistogram> kvp in m_histograms)
68 {
69 process(kvp.Key, kvp.Value);
70 }
71 }
72
73 public void Event()
74 {
75 this.Event(1);
76 }
77
78 // Count the underlying counter.
79 public void Event(int cnt)
80 {
81 lock (counterLock)
82 {
83 base.Value += cnt;
84
85 foreach (EventHistogram histo in m_histograms.Values)
86 {
87 histo.Event(cnt);
88 }
89 }
90 }
91
92 // CounterStat is a basic stat plus histograms
93 public override OSDMap ToOSDMap()
94 {
95 // Get the foundational instance
96 OSDMap map = base.ToOSDMap();
97
98 map["StatType"] = "CounterStat";
99
100 // If there are any histograms, add a new field that is an array of histograms as OSDMaps
101 if (m_histograms.Count > 0)
102 {
103 lock (counterLock)
104 {
105 if (m_histograms.Count > 0)
106 {
107 OSDArray histos = new OSDArray();
108 foreach (EventHistogram histo in m_histograms.Values)
109 {
110 histos.Add(histo.GetHistogramAsOSDMap());
111 }
112 map.Add("Histograms", histos);
113 }
114 }
115 }
116 return map;
117 }
118}
119}
diff --git a/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs b/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs
new file mode 100755
index 0000000..f51f322
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs
@@ -0,0 +1,173 @@
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.Linq;
31using System.Text;
32
33using OpenMetaverse.StructuredData;
34
35namespace OpenSim.Framework.Monitoring
36{
37// Create a time histogram of events. The histogram is built in a wrap-around
38// array of equally distributed buckets.
39// For instance, a minute long histogram of second sized buckets would be:
40// new EventHistogram(60, 1000)
41public class EventHistogram
42{
43 private int m_timeBase;
44 private int m_numBuckets;
45 private int m_bucketMilliseconds;
46 private int m_lastBucket;
47 private int m_totalHistogramMilliseconds;
48 private long[] m_histogram;
49 private object histoLock = new object();
50
51 public EventHistogram(int numberOfBuckets, int millisecondsPerBucket)
52 {
53 m_numBuckets = numberOfBuckets;
54 m_bucketMilliseconds = millisecondsPerBucket;
55 m_totalHistogramMilliseconds = m_numBuckets * m_bucketMilliseconds;
56
57 m_histogram = new long[m_numBuckets];
58 Zero();
59 m_lastBucket = 0;
60 m_timeBase = Util.EnvironmentTickCount();
61 }
62
63 public void Event()
64 {
65 this.Event(1);
66 }
67
68 // Record an event at time 'now' in the histogram.
69 public void Event(int cnt)
70 {
71 lock (histoLock)
72 {
73 // The time as displaced from the base of the histogram
74 int bucketTime = Util.EnvironmentTickCountSubtract(m_timeBase);
75
76 // If more than the total time of the histogram, we just start over
77 if (bucketTime > m_totalHistogramMilliseconds)
78 {
79 Zero();
80 m_lastBucket = 0;
81 m_timeBase = Util.EnvironmentTickCount();
82 }
83 else
84 {
85 // To which bucket should we add this event?
86 int bucket = bucketTime / m_bucketMilliseconds;
87
88 // Advance m_lastBucket to the new bucket. Zero any buckets skipped over.
89 while (bucket != m_lastBucket)
90 {
91 // Zero from just after the last bucket to the new bucket or the end
92 for (int jj = m_lastBucket + 1; jj <= Math.Min(bucket, m_numBuckets - 1); jj++)
93 {
94 m_histogram[jj] = 0;
95 }
96 m_lastBucket = bucket;
97 // If the new bucket is off the end, wrap around to the beginning
98 if (bucket > m_numBuckets)
99 {
100 bucket -= m_numBuckets;
101 m_lastBucket = 0;
102 m_histogram[m_lastBucket] = 0;
103 m_timeBase += m_totalHistogramMilliseconds;
104 }
105 }
106 }
107 m_histogram[m_lastBucket] += cnt;
108 }
109 }
110
111 // Get a copy of the current histogram
112 public long[] GetHistogram()
113 {
114 long[] ret = new long[m_numBuckets];
115 lock (histoLock)
116 {
117 int indx = m_lastBucket + 1;
118 for (int ii = 0; ii < m_numBuckets; ii++, indx++)
119 {
120 if (indx >= m_numBuckets)
121 indx = 0;
122 ret[ii] = m_histogram[indx];
123 }
124 }
125 return ret;
126 }
127
128 public OSDMap GetHistogramAsOSDMap()
129 {
130 OSDMap ret = new OSDMap();
131
132 ret.Add("Buckets", OSD.FromInteger(m_numBuckets));
133 ret.Add("BucketMilliseconds", OSD.FromInteger(m_bucketMilliseconds));
134 ret.Add("TotalMilliseconds", OSD.FromInteger(m_totalHistogramMilliseconds));
135
136 // Compute a number for the first bucket in the histogram.
137 // This will allow readers to know how this histogram relates to any previously read histogram.
138 int baseBucketNum = (m_timeBase / m_bucketMilliseconds) + m_lastBucket + 1;
139 ret.Add("BaseNumber", OSD.FromInteger(baseBucketNum));
140
141 ret.Add("Values", GetHistogramAsOSDArray());
142
143 return ret;
144 }
145 // Get a copy of the current histogram
146 public OSDArray GetHistogramAsOSDArray()
147 {
148 OSDArray ret = new OSDArray(m_numBuckets);
149 lock (histoLock)
150 {
151 int indx = m_lastBucket + 1;
152 for (int ii = 0; ii < m_numBuckets; ii++, indx++)
153 {
154 if (indx >= m_numBuckets)
155 indx = 0;
156 ret[ii] = OSD.FromLong(m_histogram[indx]);
157 }
158 }
159 return ret;
160 }
161
162 // Zero out the histogram
163 public void Zero()
164 {
165 lock (histoLock)
166 {
167 for (int ii = 0; ii < m_numBuckets; ii++)
168 m_histogram[ii] = 0;
169 }
170 }
171}
172
173}
diff --git a/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs b/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs
new file mode 100644
index 0000000..55ddf06
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs
@@ -0,0 +1,104 @@
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.Text;
31
32using OpenMetaverse.StructuredData;
33
34namespace OpenSim.Framework.Monitoring
35{
36 public class PercentageStat : Stat
37 {
38 public long Antecedent { get; set; }
39 public long Consequent { get; set; }
40
41 public override double Value
42 {
43 get
44 {
45 // Asking for an update here means that the updater cannot access this value without infinite recursion.
46 // XXX: A slightly messy but simple solution may be to flick a flag so we can tell if this is being
47 // called by the pull action and just return the value.
48 if (StatType == StatType.Pull)
49 PullAction(this);
50
51 long c = Consequent;
52
53 // Avoid any chance of a multi-threaded divide-by-zero
54 if (c == 0)
55 return 0;
56
57 return (double)Antecedent / c * 100;
58 }
59
60 set
61 {
62 throw new InvalidOperationException("Cannot set value on a PercentageStat");
63 }
64 }
65
66 public PercentageStat(
67 string shortName,
68 string name,
69 string description,
70 string category,
71 string container,
72 StatType type,
73 Action<Stat> pullAction,
74 StatVerbosity verbosity)
75 : base(shortName, name, description, "%", category, container, type, pullAction, verbosity) {}
76
77 public override string ToConsoleString()
78 {
79 StringBuilder sb = new StringBuilder();
80
81 sb.AppendFormat(
82 "{0}.{1}.{2} : {3:0.##}{4} ({5}/{6})",
83 Category, Container, ShortName, Value, UnitName, Antecedent, Consequent);
84
85 AppendMeasuresOfInterest(sb);
86
87 return sb.ToString();
88 }
89
90 // PercentageStat is a basic stat plus percent calc
91 public override OSDMap ToOSDMap()
92 {
93 // Get the foundational instance
94 OSDMap map = base.ToOSDMap();
95
96 map["StatType"] = "PercentageStat";
97
98 map.Add("Antecedent", OSD.FromLong(Antecedent));
99 map.Add("Consequent", OSD.FromLong(Consequent));
100
101 return map;
102 }
103 }
104} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/Stats/Stat.cs b/OpenSim/Framework/Monitoring/Stats/Stat.cs
new file mode 100644
index 0000000..a7cb2a6
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Stats/Stat.cs
@@ -0,0 +1,326 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using OpenMetaverse.StructuredData;
35
36namespace OpenSim.Framework.Monitoring
37{
38 /// <summary>
39 /// Holds individual statistic details
40 /// </summary>
41 public class Stat : IDisposable
42 {
43// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
44
45 public static readonly char[] DisallowedShortNameCharacters = { '.' };
46
47 /// <summary>
48 /// Category of this stat (e.g. cache, scene, etc).
49 /// </summary>
50 public string Category { get; private set; }
51
52 /// <summary>
53 /// Containing name for this stat.
54 /// FIXME: In the case of a scene, this is currently the scene name (though this leaves
55 /// us with a to-be-resolved problem of non-unique region names).
56 /// </summary>
57 /// <value>
58 /// The container.
59 /// </value>
60 public string Container { get; private set; }
61
62 public StatType StatType { get; private set; }
63
64 public MeasuresOfInterest MeasuresOfInterest { get; private set; }
65
66 /// <summary>
67 /// Action used to update this stat when the value is requested if it's a pull type.
68 /// </summary>
69 public Action<Stat> PullAction { get; private set; }
70
71 public StatVerbosity Verbosity { get; private set; }
72 public string ShortName { get; private set; }
73 public string Name { get; private set; }
74 public string Description { get; private set; }
75 public virtual string UnitName { get; private set; }
76
77 public virtual double Value
78 {
79 get
80 {
81 // Asking for an update here means that the updater cannot access this value without infinite recursion.
82 // XXX: A slightly messy but simple solution may be to flick a flag so we can tell if this is being
83 // called by the pull action and just return the value.
84 if (StatType == StatType.Pull)
85 PullAction(this);
86
87 return m_value;
88 }
89
90 set
91 {
92 m_value = value;
93 }
94 }
95
96 private double m_value;
97
98 /// <summary>
99 /// Historical samples for calculating measures of interest average.
100 /// </summary>
101 /// <remarks>
102 /// Will be null if no measures of interest require samples.
103 /// </remarks>
104 private Queue<double> m_samples;
105
106 /// <summary>
107 /// Maximum number of statistical samples.
108 /// </summary>
109 /// <remarks>
110 /// At the moment this corresponds to 1 minute since the sampling rate is every 2.5 seconds as triggered from
111 /// the main Watchdog.
112 /// </remarks>
113 private static int m_maxSamples = 24;
114
115 public Stat(
116 string shortName,
117 string name,
118 string description,
119 string unitName,
120 string category,
121 string container,
122 StatType type,
123 Action<Stat> pullAction,
124 StatVerbosity verbosity)
125 : this(
126 shortName,
127 name,
128 description,
129 unitName,
130 category,
131 container,
132 type,
133 MeasuresOfInterest.None,
134 pullAction,
135 verbosity)
136 {
137 }
138
139 /// <summary>
140 /// Constructor
141 /// </summary>
142 /// <param name='shortName'>Short name for the stat. Must not contain spaces. e.g. "LongFrames"</param>
143 /// <param name='name'>Human readable name for the stat. e.g. "Long frames"</param>
144 /// <param name='description'>Description of stat</param>
145 /// <param name='unitName'>
146 /// Unit name for the stat. Should be preceeded by a space if the unit name isn't normally appeneded immediately to the value.
147 /// e.g. " frames"
148 /// </param>
149 /// <param name='category'>Category under which this stat should appear, e.g. "scene". Do not capitalize.</param>
150 /// <param name='container'>Entity to which this stat relates. e.g. scene name if this is a per scene stat.</param>
151 /// <param name='type'>Push or pull</param>
152 /// <param name='pullAction'>Pull stats need an action to update the stat on request. Push stats should set null here.</param>
153 /// <param name='moi'>Measures of interest</param>
154 /// <param name='verbosity'>Verbosity of stat. Controls whether it will appear in short stat display or only full display.</param>
155 public Stat(
156 string shortName,
157 string name,
158 string description,
159 string unitName,
160 string category,
161 string container,
162 StatType type,
163 MeasuresOfInterest moi,
164 Action<Stat> pullAction,
165 StatVerbosity verbosity)
166 {
167 if (StatsManager.SubCommands.Contains(category))
168 throw new Exception(
169 string.Format("Stat cannot be in category '{0}' since this is reserved for a subcommand", category));
170
171 foreach (char c in DisallowedShortNameCharacters)
172 {
173 if (shortName.IndexOf(c) != -1)
174 shortName = shortName.Replace(c, '#');
175// throw new Exception(string.Format("Stat name {0} cannot contain character {1}", shortName, c));
176 }
177
178 ShortName = shortName;
179 Name = name;
180 Description = description;
181 UnitName = unitName;
182 Category = category;
183 Container = container;
184 StatType = type;
185
186 if (StatType == StatType.Push && pullAction != null)
187 throw new Exception("A push stat cannot have a pull action");
188 else
189 PullAction = pullAction;
190
191 MeasuresOfInterest = moi;
192
193 if ((moi & MeasuresOfInterest.AverageChangeOverTime) == MeasuresOfInterest.AverageChangeOverTime)
194 m_samples = new Queue<double>(m_maxSamples);
195
196 Verbosity = verbosity;
197 }
198
199 // IDisposable.Dispose()
200 public virtual void Dispose()
201 {
202 return;
203 }
204
205 /// <summary>
206 /// Record a value in the sample set.
207 /// </summary>
208 /// <remarks>
209 /// Do not call this if MeasuresOfInterest.None
210 /// </remarks>
211 public void RecordValue()
212 {
213 double newValue = Value;
214
215 lock (m_samples)
216 {
217 if (m_samples.Count >= m_maxSamples)
218 m_samples.Dequeue();
219
220// m_log.DebugFormat("[STAT]: Recording value {0} for {1}", newValue, Name);
221
222 m_samples.Enqueue(newValue);
223 }
224 }
225
226 public virtual string ToConsoleString()
227 {
228 StringBuilder sb = new StringBuilder();
229 sb.AppendFormat(
230 "{0}.{1}.{2} : {3}{4}",
231 Category,
232 Container,
233 ShortName,
234 Value,
235 string.IsNullOrEmpty(UnitName) ? "" : string.Format(" {0}", UnitName));
236
237 AppendMeasuresOfInterest(sb);
238
239 return sb.ToString();
240 }
241
242 public virtual OSDMap ToOSDMap()
243 {
244 OSDMap ret = new OSDMap();
245 ret.Add("StatType", "Stat"); // used by overloading classes to denote type of stat
246
247 ret.Add("Category", OSD.FromString(Category));
248 ret.Add("Container", OSD.FromString(Container));
249 ret.Add("ShortName", OSD.FromString(ShortName));
250 ret.Add("Name", OSD.FromString(Name));
251 ret.Add("Description", OSD.FromString(Description));
252 ret.Add("UnitName", OSD.FromString(UnitName));
253 ret.Add("Value", OSD.FromReal(Value));
254
255 double lastChangeOverTime, averageChangeOverTime;
256 if (ComputeMeasuresOfInterest(out lastChangeOverTime, out averageChangeOverTime))
257 {
258 ret.Add("LastChangeOverTime", OSD.FromReal(lastChangeOverTime));
259 ret.Add("AverageChangeOverTime", OSD.FromReal(averageChangeOverTime));
260 }
261
262 return ret;
263 }
264
265 // Compute the averages over time and return same.
266 // Return 'true' if averages were actually computed. 'false' if no average info.
267 public bool ComputeMeasuresOfInterest(out double lastChangeOverTime, out double averageChangeOverTime)
268 {
269 bool ret = false;
270 lastChangeOverTime = 0;
271 averageChangeOverTime = 0;
272
273 if ((MeasuresOfInterest & MeasuresOfInterest.AverageChangeOverTime) == MeasuresOfInterest.AverageChangeOverTime)
274 {
275 double totalChange = 0;
276 double? penultimateSample = null;
277 double? lastSample = null;
278
279 lock (m_samples)
280 {
281 // m_log.DebugFormat(
282 // "[STAT]: Samples for {0} are {1}",
283 // Name, string.Join(",", m_samples.Select(s => s.ToString()).ToArray()));
284
285 foreach (double s in m_samples)
286 {
287 if (lastSample != null)
288 totalChange += s - (double)lastSample;
289
290 penultimateSample = lastSample;
291 lastSample = s;
292 }
293 }
294
295 if (lastSample != null && penultimateSample != null)
296 {
297 lastChangeOverTime
298 = ((double)lastSample - (double)penultimateSample) / (Watchdog.WATCHDOG_INTERVAL_MS / 1000);
299 }
300
301 int divisor = m_samples.Count <= 1 ? 1 : m_samples.Count - 1;
302
303 averageChangeOverTime = totalChange / divisor / (Watchdog.WATCHDOG_INTERVAL_MS / 1000);
304 ret = true;
305 }
306
307 return ret;
308 }
309
310 protected void AppendMeasuresOfInterest(StringBuilder sb)
311 {
312 double lastChangeOverTime = 0;
313 double averageChangeOverTime = 0;
314
315 if (ComputeMeasuresOfInterest(out lastChangeOverTime, out averageChangeOverTime))
316 {
317 sb.AppendFormat(
318 ", {0:0.##}{1}/s, {2:0.##}{3}/s",
319 lastChangeOverTime,
320 string.IsNullOrEmpty(UnitName) ? "" : string.Format(" {0}", UnitName),
321 averageChangeOverTime,
322 string.IsNullOrEmpty(UnitName) ? "" : string.Format(" {0}", UnitName));
323 }
324 }
325 }
326} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/StatsLogger.cs b/OpenSim/Framework/Monitoring/StatsLogger.cs
new file mode 100644
index 0000000..15a37aa
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/StatsLogger.cs
@@ -0,0 +1,151 @@
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.IO;
31using System.Reflection;
32using System.Text;
33using System.Timers;
34using log4net;
35
36namespace OpenSim.Framework.Monitoring
37{
38 /// <summary>
39 /// Provides a means to continuously log stats for debugging purposes.
40 /// </summary>
41 public static class StatsLogger
42 {
43 private static readonly ILog m_statsLog = LogManager.GetLogger("special.StatsLogger");
44
45 private static Timer m_loggingTimer;
46 private static int m_statsLogIntervalMs = 5000;
47
48 public static void RegisterConsoleCommands(ICommandConsole console)
49 {
50 console.Commands.AddCommand(
51 "General",
52 false,
53 "stats record",
54 "stats record start|stop",
55 "Control whether stats are being regularly recorded to a separate file.",
56 "For debug purposes. Experimental.",
57 HandleStatsRecordCommand);
58
59 console.Commands.AddCommand(
60 "General",
61 false,
62 "stats save",
63 "stats save <path>",
64 "Save stats snapshot to a file. If the file already exists, then the report is appended.",
65 "For debug purposes. Experimental.",
66 HandleStatsSaveCommand);
67 }
68
69 public static void HandleStatsRecordCommand(string module, string[] cmd)
70 {
71 ICommandConsole con = MainConsole.Instance;
72
73 if (cmd.Length != 3)
74 {
75 con.Output("Usage: stats record start|stop");
76 return;
77 }
78
79 if (cmd[2] == "start")
80 {
81 Start();
82 con.OutputFormat("Now recording all stats to file every {0}ms", m_statsLogIntervalMs);
83 }
84 else if (cmd[2] == "stop")
85 {
86 Stop();
87 con.Output("Stopped recording stats to file.");
88 }
89 }
90
91 public static void HandleStatsSaveCommand(string module, string[] cmd)
92 {
93 ICommandConsole con = MainConsole.Instance;
94
95 if (cmd.Length != 3)
96 {
97 con.Output("Usage: stats save <path>");
98 return;
99 }
100
101 string path = cmd[2];
102
103 using (StreamWriter sw = new StreamWriter(path, true))
104 {
105 foreach (string line in GetReport())
106 sw.WriteLine(line);
107 }
108
109 MainConsole.Instance.OutputFormat("Stats saved to file {0}", path);
110 }
111
112 public static void Start()
113 {
114 if (m_loggingTimer != null)
115 Stop();
116
117 m_loggingTimer = new Timer(m_statsLogIntervalMs);
118 m_loggingTimer.AutoReset = false;
119 m_loggingTimer.Elapsed += Log;
120 m_loggingTimer.Start();
121 }
122
123 public static void Stop()
124 {
125 if (m_loggingTimer != null)
126 {
127 m_loggingTimer.Stop();
128 }
129 }
130
131 private static void Log(object sender, ElapsedEventArgs e)
132 {
133 foreach (string line in GetReport())
134 m_statsLog.Info(line);
135
136 m_loggingTimer.Start();
137 }
138
139 private static List<string> GetReport()
140 {
141 List<string> lines = new List<string>();
142
143 lines.Add(string.Format("*** STATS REPORT AT {0} ***", DateTime.Now));
144
145 foreach (string report in StatsManager.GetAllStatsReports())
146 lines.Add(report);
147
148 return lines;
149 }
150 }
151} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/StatsManager.cs b/OpenSim/Framework/Monitoring/StatsManager.cs
new file mode 100644
index 0000000..3136ee8
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/StatsManager.cs
@@ -0,0 +1,557 @@
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;
30using System.Collections.Generic;
31using System.Linq;
32using System.Text;
33
34using OpenSim.Framework;
35using OpenMetaverse.StructuredData;
36
37namespace OpenSim.Framework.Monitoring
38{
39 /// <summary>
40 /// Static class used to register/deregister/fetch statistics
41 /// </summary>
42 public static class StatsManager
43 {
44 // Subcommand used to list other stats.
45 public const string AllSubCommand = "all";
46
47 // Subcommand used to list other stats.
48 public const string ListSubCommand = "list";
49
50 // All subcommands
51 public static HashSet<string> SubCommands = new HashSet<string> { AllSubCommand, ListSubCommand };
52
53 /// <summary>
54 /// Registered stats categorized by category/container/shortname
55 /// </summary>
56 /// <remarks>
57 /// Do not add or remove directly from this dictionary.
58 /// </remarks>
59 public static SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>> RegisteredStats
60 = new SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>>();
61
62// private static AssetStatsCollector assetStats;
63// private static UserStatsCollector userStats;
64// private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector();
65
66// public static AssetStatsCollector AssetStats { get { return assetStats; } }
67// public static UserStatsCollector UserStats { get { return userStats; } }
68 public static SimExtraStatsCollector SimExtraStats { get; set; }
69
70 public static void RegisterConsoleCommands(ICommandConsole console)
71 {
72 console.Commands.AddCommand(
73 "General",
74 false,
75 "stats show",
76 "stats show [list|all|(<category>[.<container>])+",
77 "Show statistical information for this server",
78 "If no final argument is specified then legacy statistics information is currently shown.\n"
79 + "'list' argument will show statistic categories.\n"
80 + "'all' will show all statistics.\n"
81 + "A <category> name will show statistics from that category.\n"
82 + "A <category>.<container> name will show statistics from that category in that container.\n"
83 + "More than one name can be given separated by spaces.\n"
84 + "THIS STATS FACILITY IS EXPERIMENTAL AND DOES NOT YET CONTAIN ALL STATS",
85 HandleShowStatsCommand);
86
87 console.Commands.AddCommand(
88 "General",
89 false,
90 "show stats",
91 "show stats [list|all|(<category>[.<container>])+",
92 "Alias for 'stats show' command",
93 HandleShowStatsCommand);
94
95 StatsLogger.RegisterConsoleCommands(console);
96 }
97
98 public static void HandleShowStatsCommand(string module, string[] cmd)
99 {
100 ICommandConsole con = MainConsole.Instance;
101
102 if (cmd.Length > 2)
103 {
104 foreach (string name in cmd.Skip(2))
105 {
106 string[] components = name.Split('.');
107
108 string categoryName = components[0];
109 string containerName = components.Length > 1 ? components[1] : null;
110 string statName = components.Length > 2 ? components[2] : null;
111
112 if (categoryName == AllSubCommand)
113 {
114 OutputAllStatsToConsole(con);
115 }
116 else if (categoryName == ListSubCommand)
117 {
118 con.Output("Statistic categories available are:");
119 foreach (string category in RegisteredStats.Keys)
120 con.OutputFormat(" {0}", category);
121 }
122 else
123 {
124 SortedDictionary<string, SortedDictionary<string, Stat>> category;
125 if (!RegisteredStats.TryGetValue(categoryName, out category))
126 {
127 con.OutputFormat("No such category as {0}", categoryName);
128 }
129 else
130 {
131 if (String.IsNullOrEmpty(containerName))
132 {
133 OutputCategoryStatsToConsole(con, category);
134 }
135 else
136 {
137 SortedDictionary<string, Stat> container;
138 if (category.TryGetValue(containerName, out container))
139 {
140 if (String.IsNullOrEmpty(statName))
141 {
142 OutputContainerStatsToConsole(con, container);
143 }
144 else
145 {
146 Stat stat;
147 if (container.TryGetValue(statName, out stat))
148 {
149 OutputStatToConsole(con, stat);
150 }
151 else
152 {
153 con.OutputFormat(
154 "No such stat {0} in {1}.{2}", statName, categoryName, containerName);
155 }
156 }
157 }
158 else
159 {
160 con.OutputFormat("No such container {0} in category {1}", containerName, categoryName);
161 }
162 }
163 }
164 }
165 }
166 }
167 else
168 {
169 // Legacy
170 if (SimExtraStats != null)
171 con.Output(SimExtraStats.Report());
172 else
173 OutputAllStatsToConsole(con);
174 }
175 }
176
177 public static List<string> GetAllStatsReports()
178 {
179 List<string> reports = new List<string>();
180
181 foreach (var category in RegisteredStats.Values)
182 reports.AddRange(GetCategoryStatsReports(category));
183
184 return reports;
185 }
186
187 private static void OutputAllStatsToConsole(ICommandConsole con)
188 {
189 foreach (string report in GetAllStatsReports())
190 con.Output(report);
191 }
192
193 private static List<string> GetCategoryStatsReports(
194 SortedDictionary<string, SortedDictionary<string, Stat>> category)
195 {
196 List<string> reports = new List<string>();
197
198 foreach (var container in category.Values)
199 reports.AddRange(GetContainerStatsReports(container));
200
201 return reports;
202 }
203
204 private static void OutputCategoryStatsToConsole(
205 ICommandConsole con, SortedDictionary<string, SortedDictionary<string, Stat>> category)
206 {
207 foreach (string report in GetCategoryStatsReports(category))
208 con.Output(report);
209 }
210
211 private static List<string> GetContainerStatsReports(SortedDictionary<string, Stat> container)
212 {
213 List<string> reports = new List<string>();
214
215 foreach (Stat stat in container.Values)
216 reports.Add(stat.ToConsoleString());
217
218 return reports;
219 }
220
221 private static void OutputContainerStatsToConsole(
222 ICommandConsole con, SortedDictionary<string, Stat> container)
223 {
224 foreach (string report in GetContainerStatsReports(container))
225 con.Output(report);
226 }
227
228 private static void OutputStatToConsole(ICommandConsole con, Stat stat)
229 {
230 con.Output(stat.ToConsoleString());
231 }
232
233 // Creates an OSDMap of the format:
234 // { categoryName: {
235 // containerName: {
236 // statName: {
237 // "Name": name,
238 // "ShortName": shortName,
239 // ...
240 // },
241 // statName: {
242 // "Name": name,
243 // "ShortName": shortName,
244 // ...
245 // },
246 // ...
247 // },
248 // containerName: {
249 // ...
250 // },
251 // ...
252 // },
253 // categoryName: {
254 // ...
255 // },
256 // ...
257 // }
258 // The passed in parameters will filter the categories, containers and stats returned. If any of the
259 // parameters are either EmptyOrNull or the AllSubCommand value, all of that type will be returned.
260 // Case matters.
261 public static OSDMap GetStatsAsOSDMap(string pCategoryName, string pContainerName, string pStatName)
262 {
263 OSDMap map = new OSDMap();
264
265 foreach (string catName in RegisteredStats.Keys)
266 {
267 // Do this category if null spec, "all" subcommand or category name matches passed parameter.
268 // Skip category if none of the above.
269 if (!(String.IsNullOrEmpty(pCategoryName) || pCategoryName == AllSubCommand || pCategoryName == catName))
270 continue;
271
272 OSDMap contMap = new OSDMap();
273 foreach (string contName in RegisteredStats[catName].Keys)
274 {
275 if (!(string.IsNullOrEmpty(pContainerName) || pContainerName == AllSubCommand || pContainerName == contName))
276 continue;
277
278 OSDMap statMap = new OSDMap();
279
280 SortedDictionary<string, Stat> theStats = RegisteredStats[catName][contName];
281 foreach (string statName in theStats.Keys)
282 {
283 if (!(String.IsNullOrEmpty(pStatName) || pStatName == AllSubCommand || pStatName == statName))
284 continue;
285
286 statMap.Add(statName, theStats[statName].ToOSDMap());
287 }
288
289 contMap.Add(contName, statMap);
290 }
291 map.Add(catName, contMap);
292 }
293
294 return map;
295 }
296
297 public static Hashtable HandleStatsRequest(Hashtable request)
298 {
299 Hashtable responsedata = new Hashtable();
300// string regpath = request["uri"].ToString();
301 int response_code = 200;
302 string contenttype = "text/json";
303
304 string pCategoryName = StatsManager.AllSubCommand;
305 string pContainerName = StatsManager.AllSubCommand;
306 string pStatName = StatsManager.AllSubCommand;
307
308 if (request.ContainsKey("cat")) pCategoryName = request["cat"].ToString();
309 if (request.ContainsKey("cont")) pContainerName = request["cat"].ToString();
310 if (request.ContainsKey("stat")) pStatName = request["stat"].ToString();
311
312 string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString();
313
314 // If requestor wants it as a callback function, build response as a function rather than just the JSON string.
315 if (request.ContainsKey("callback"))
316 {
317 strOut = request["callback"].ToString() + "(" + strOut + ");";
318 }
319
320 // m_log.DebugFormat("{0} StatFetch: uri={1}, cat={2}, cont={3}, stat={4}, resp={5}",
321 // LogHeader, regpath, pCategoryName, pContainerName, pStatName, strOut);
322
323 responsedata["int_response_code"] = response_code;
324 responsedata["content_type"] = contenttype;
325 responsedata["keepalive"] = false;
326 responsedata["str_response_string"] = strOut;
327 responsedata["access_control_allow_origin"] = "*";
328
329 return responsedata;
330 }
331
332// /// <summary>
333// /// Start collecting statistics related to assets.
334// /// Should only be called once.
335// /// </summary>
336// public static AssetStatsCollector StartCollectingAssetStats()
337// {
338// assetStats = new AssetStatsCollector();
339//
340// return assetStats;
341// }
342//
343// /// <summary>
344// /// Start collecting statistics related to users.
345// /// Should only be called once.
346// /// </summary>
347// public static UserStatsCollector StartCollectingUserStats()
348// {
349// userStats = new UserStatsCollector();
350//
351// return userStats;
352// }
353
354 /// <summary>
355 /// Register a statistic.
356 /// </summary>
357 /// <param name='stat'></param>
358 /// <returns></returns>
359 public static bool RegisterStat(Stat stat)
360 {
361 SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
362 SortedDictionary<string, Stat> container = null, newContainer;
363
364 lock (RegisteredStats)
365 {
366 // Stat name is not unique across category/container/shortname key.
367 // XXX: For now just return false. This is to avoid problems in regression tests where all tests
368 // in a class are run in the same instance of the VM.
369 if (TryGetStatParents(stat, out category, out container))
370 return false;
371
372 // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
373 // This means that we don't need to lock or copy them on iteration, which will be a much more
374 // common operation after startup.
375 if (container != null)
376 newContainer = new SortedDictionary<string, Stat>(container);
377 else
378 newContainer = new SortedDictionary<string, Stat>();
379
380 if (category != null)
381 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
382 else
383 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>();
384
385 newContainer[stat.ShortName] = stat;
386 newCategory[stat.Container] = newContainer;
387 RegisteredStats[stat.Category] = newCategory;
388 }
389
390 return true;
391 }
392
393 /// <summary>
394 /// Deregister a statistic
395 /// </summary>>
396 /// <param name='stat'></param>
397 /// <returns></returns>
398 public static bool DeregisterStat(Stat stat)
399 {
400 SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
401 SortedDictionary<string, Stat> container = null, newContainer;
402
403 lock (RegisteredStats)
404 {
405 if (!TryGetStatParents(stat, out category, out container))
406 return false;
407
408 newContainer = new SortedDictionary<string, Stat>(container);
409 newContainer.Remove(stat.ShortName);
410
411 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
412 newCategory.Remove(stat.Container);
413
414 newCategory[stat.Container] = newContainer;
415 RegisteredStats[stat.Category] = newCategory;
416
417 return true;
418 }
419 }
420
421 public static bool TryGetStat(string category, string container, string statShortName, out Stat stat)
422 {
423 stat = null;
424 SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
425
426 lock (RegisteredStats)
427 {
428 if (!TryGetStatsForCategory(category, out categoryStats))
429 return false;
430
431 SortedDictionary<string, Stat> containerStats;
432
433 if (!categoryStats.TryGetValue(container, out containerStats))
434 return false;
435
436 return containerStats.TryGetValue(statShortName, out stat);
437 }
438 }
439
440 public static bool TryGetStatsForCategory(
441 string category, out SortedDictionary<string, SortedDictionary<string, Stat>> stats)
442 {
443 lock (RegisteredStats)
444 return RegisteredStats.TryGetValue(category, out stats);
445 }
446
447 /// <summary>
448 /// Get the same stat for each container in a given category.
449 /// </summary>
450 /// <returns>
451 /// The stats if there were any to fetch. Otherwise null.
452 /// </returns>
453 /// <param name='category'></param>
454 /// <param name='statShortName'></param>
455 public static List<Stat> GetStatsFromEachContainer(string category, string statShortName)
456 {
457 SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
458
459 lock (RegisteredStats)
460 {
461 if (!RegisteredStats.TryGetValue(category, out categoryStats))
462 return null;
463
464 List<Stat> stats = null;
465
466 foreach (SortedDictionary<string, Stat> containerStats in categoryStats.Values)
467 {
468 if (containerStats.ContainsKey(statShortName))
469 {
470 if (stats == null)
471 stats = new List<Stat>();
472
473 stats.Add(containerStats[statShortName]);
474 }
475 }
476
477 return stats;
478 }
479 }
480
481 public static bool TryGetStatParents(
482 Stat stat,
483 out SortedDictionary<string, SortedDictionary<string, Stat>> category,
484 out SortedDictionary<string, Stat> container)
485 {
486 category = null;
487 container = null;
488
489 lock (RegisteredStats)
490 {
491 if (RegisteredStats.TryGetValue(stat.Category, out category))
492 {
493 if (category.TryGetValue(stat.Container, out container))
494 {
495 if (container.ContainsKey(stat.ShortName))
496 return true;
497 }
498 }
499 }
500
501 return false;
502 }
503
504 public static void RecordStats()
505 {
506 lock (RegisteredStats)
507 {
508 foreach (SortedDictionary<string, SortedDictionary<string, Stat>> category in RegisteredStats.Values)
509 {
510 foreach (SortedDictionary<string, Stat> container in category.Values)
511 {
512 foreach (Stat stat in container.Values)
513 {
514 if (stat.MeasuresOfInterest != MeasuresOfInterest.None)
515 stat.RecordValue();
516 }
517 }
518 }
519 }
520 }
521 }
522
523 /// <summary>
524 /// Stat type.
525 /// </summary>
526 /// <remarks>
527 /// A push stat is one which is continually updated and so it's value can simply by read.
528 /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
529 /// </remarks>
530 public enum StatType
531 {
532 Push,
533 Pull
534 }
535
536 /// <summary>
537 /// Measures of interest for this stat.
538 /// </summary>
539 [Flags]
540 public enum MeasuresOfInterest
541 {
542 None,
543 AverageChangeOverTime
544 }
545
546 /// <summary>
547 /// Verbosity of stat.
548 /// </summary>
549 /// <remarks>
550 /// Info will always be displayed.
551 /// </remarks>
552 public enum StatVerbosity
553 {
554 Debug,
555 Info
556 }
557} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/UserStatsCollector.cs b/OpenSim/Framework/Monitoring/UserStatsCollector.cs
new file mode 100644
index 0000000..81e0fa4
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/UserStatsCollector.cs
@@ -0,0 +1,110 @@
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.Timers;
29
30using OpenMetaverse.StructuredData;
31
32namespace OpenSim.Framework.Monitoring
33{
34 /// <summary>
35 /// Collects user service statistics
36 /// </summary>
37 public class UserStatsCollector : BaseStatsCollector
38 {
39 private Timer ageStatsTimer = new Timer(24 * 60 * 60 * 1000);
40
41 private int successfulLoginsToday;
42 public int SuccessfulLoginsToday { get { return successfulLoginsToday; } }
43
44 private int successfulLoginsYesterday;
45 public int SuccessfulLoginsYesterday { get { return successfulLoginsYesterday; } }
46
47 private int successfulLogins;
48 public int SuccessfulLogins { get { return successfulLogins; } }
49
50 private int logouts;
51 public int Logouts { get { return logouts; } }
52
53 public UserStatsCollector()
54 {
55 ageStatsTimer.Elapsed += new ElapsedEventHandler(OnAgeing);
56 ageStatsTimer.Enabled = true;
57 }
58
59 private void OnAgeing(object source, ElapsedEventArgs e)
60 {
61 successfulLoginsYesterday = successfulLoginsToday;
62
63 // There is a possibility that an asset request could occur between the execution of these
64 // two statements. But we're better off without the synchronization overhead.
65 successfulLoginsToday = 0;
66 }
67
68 /// <summary>
69 /// Record a successful login
70 /// </summary>
71 public void AddSuccessfulLogin()
72 {
73 successfulLogins++;
74 successfulLoginsToday++;
75 }
76
77 public void AddLogout()
78 {
79 logouts++;
80 }
81
82 /// <summary>
83 /// Report back collected statistical information.
84 /// </summary>
85 /// <returns></returns>
86 override public string Report()
87 {
88 return string.Format(
89@"Successful logins total : {0}, today : {1}, yesterday : {2}
90 Logouts total : {3}",
91 SuccessfulLogins, SuccessfulLoginsToday, SuccessfulLoginsYesterday, Logouts);
92 }
93
94 public override string XReport(string uptime, string version)
95 {
96 return OSDParser.SerializeJsonString(OReport(uptime, version));
97 }
98
99 public override OSDMap OReport(string uptime, string version)
100 {
101 OSDMap ret = new OSDMap();
102 ret.Add("SuccessfulLogins", OSD.FromInteger(SuccessfulLogins));
103 ret.Add("SuccessfulLoginsToday", OSD.FromInteger(SuccessfulLoginsToday));
104 ret.Add("SuccessfulLoginsYesterday", OSD.FromInteger(SuccessfulLoginsYesterday));
105 ret.Add("Logouts", OSD.FromInteger(Logouts));
106
107 return ret;
108 }
109 }
110}
diff --git a/OpenSim/Framework/Monitoring/Watchdog.cs b/OpenSim/Framework/Monitoring/Watchdog.cs
new file mode 100644
index 0000000..a644fa5
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/Watchdog.cs
@@ -0,0 +1,380 @@
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.Linq;
31using System.Threading;
32using log4net;
33
34namespace OpenSim.Framework.Monitoring
35{
36 /// <summary>
37 /// Manages launching threads and keeping watch over them for timeouts
38 /// </summary>
39 public static class Watchdog
40 {
41 private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
42
43 /// <summary>Timer interval in milliseconds for the watchdog timer</summary>
44 public const double WATCHDOG_INTERVAL_MS = 2500.0d;
45
46 /// <summary>Default timeout in milliseconds before a thread is considered dead</summary>
47 public const int DEFAULT_WATCHDOG_TIMEOUT_MS = 5000;
48
49 [System.Diagnostics.DebuggerDisplay("{Thread.Name}")]
50 public class ThreadWatchdogInfo
51 {
52 public Thread Thread { get; private set; }
53
54 /// <summary>
55 /// Approximate tick when this thread was started.
56 /// </summary>
57 /// <remarks>
58 /// Not terribly good since this quickly wraps around.
59 /// </remarks>
60 public int FirstTick { get; private set; }
61
62 /// <summary>
63 /// Last time this heartbeat update was invoked
64 /// </summary>
65 public int LastTick { get; set; }
66
67 /// <summary>
68 /// Number of milliseconds before we notify that the thread is having a problem.
69 /// </summary>
70 public int Timeout { get; set; }
71
72 /// <summary>
73 /// Is this thread considered timed out?
74 /// </summary>
75 public bool IsTimedOut { get; set; }
76
77 /// <summary>
78 /// Will this thread trigger the alarm function if it has timed out?
79 /// </summary>
80 public bool AlarmIfTimeout { get; set; }
81
82 /// <summary>
83 /// Method execute if alarm goes off. If null then no alarm method is fired.
84 /// </summary>
85 public Func<string> AlarmMethod { get; set; }
86
87 /// <summary>
88 /// Stat structure associated with this thread.
89 /// </summary>
90 public Stat Stat { get; set; }
91
92 public ThreadWatchdogInfo(Thread thread, int timeout, string name)
93 {
94 Thread = thread;
95 Timeout = timeout;
96 FirstTick = Environment.TickCount & Int32.MaxValue;
97 LastTick = FirstTick;
98
99 Stat
100 = new Stat(
101 name,
102 string.Format("Last update of thread {0}", name),
103 "",
104 "ms",
105 "server",
106 "thread",
107 StatType.Pull,
108 MeasuresOfInterest.None,
109 stat => stat.Value = Environment.TickCount & Int32.MaxValue - LastTick,
110 StatVerbosity.Debug);
111
112 StatsManager.RegisterStat(Stat);
113 }
114
115 public ThreadWatchdogInfo(ThreadWatchdogInfo previousTwi)
116 {
117 Thread = previousTwi.Thread;
118 FirstTick = previousTwi.FirstTick;
119 LastTick = previousTwi.LastTick;
120 Timeout = previousTwi.Timeout;
121 IsTimedOut = previousTwi.IsTimedOut;
122 AlarmIfTimeout = previousTwi.AlarmIfTimeout;
123 AlarmMethod = previousTwi.AlarmMethod;
124 }
125
126 public void Cleanup()
127 {
128 StatsManager.DeregisterStat(Stat);
129 }
130 }
131
132 /// <summary>
133 /// This event is called whenever a tracked thread is
134 /// stopped or has not called UpdateThread() in time<
135 /// /summary>
136 public static event Action<ThreadWatchdogInfo> OnWatchdogTimeout;
137
138 /// <summary>
139 /// Is this watchdog active?
140 /// </summary>
141 public static bool Enabled
142 {
143 get { return m_enabled; }
144 set
145 {
146 // m_log.DebugFormat("[MEMORY WATCHDOG]: Setting MemoryWatchdog.Enabled to {0}", value);
147
148 if (value == m_enabled)
149 return;
150
151 m_enabled = value;
152
153 if (m_enabled)
154 {
155 // Set now so we don't get alerted on the first run
156 LastWatchdogThreadTick = Environment.TickCount & Int32.MaxValue;
157 }
158
159 m_watchdogTimer.Enabled = m_enabled;
160 }
161 }
162
163 private static bool m_enabled;
164 private static Dictionary<int, ThreadWatchdogInfo> m_threads;
165 private static System.Timers.Timer m_watchdogTimer;
166
167 /// <summary>
168 /// Last time the watchdog thread ran.
169 /// </summary>
170 /// <remarks>
171 /// Should run every WATCHDOG_INTERVAL_MS
172 /// </remarks>
173 public static int LastWatchdogThreadTick { get; private set; }
174
175 static Watchdog()
176 {
177 m_threads = new Dictionary<int, ThreadWatchdogInfo>();
178 m_watchdogTimer = new System.Timers.Timer(WATCHDOG_INTERVAL_MS);
179 m_watchdogTimer.AutoReset = false;
180 m_watchdogTimer.Elapsed += WatchdogTimerElapsed;
181 }
182
183 /// <summary>
184 /// Add a thread to the watchdog tracker.
185 /// </summary>
186 /// <param name="info">Information about the thread.</info>
187 /// <param name="info">Name of the thread.</info>
188 /// <param name="log">If true then creation of thread is logged.</param>
189 public static void AddThread(ThreadWatchdogInfo info, string name, bool log = true)
190 {
191 if (log)
192 m_log.DebugFormat(
193 "[WATCHDOG]: Started tracking thread {0}, ID {1}", name, info.Thread.ManagedThreadId);
194
195 lock (m_threads)
196 m_threads.Add(info.Thread.ManagedThreadId, info);
197 }
198
199 /// <summary>
200 /// Marks the current thread as alive
201 /// </summary>
202 public static void UpdateThread()
203 {
204 UpdateThread(Thread.CurrentThread.ManagedThreadId);
205 }
206
207 /// <summary>
208 /// Stops watchdog tracking on the current thread
209 /// </summary>
210 /// <param name="log">If true then normal events in thread removal are not logged.</param>
211 /// <returns>
212 /// True if the thread was removed from the list of tracked
213 /// threads, otherwise false
214 /// </returns>
215 public static bool RemoveThread(bool log = true)
216 {
217 return RemoveThread(Thread.CurrentThread.ManagedThreadId, log);
218 }
219
220 private static bool RemoveThread(int threadID, bool log = true)
221 {
222 lock (m_threads)
223 {
224 ThreadWatchdogInfo twi;
225 if (m_threads.TryGetValue(threadID, out twi))
226 {
227 if (log)
228 m_log.DebugFormat(
229 "[WATCHDOG]: Removing thread {0}, ID {1}", twi.Thread.Name, twi.Thread.ManagedThreadId);
230
231 twi.Cleanup();
232 m_threads.Remove(threadID);
233
234 return true;
235 }
236 else
237 {
238 m_log.WarnFormat(
239 "[WATCHDOG]: Requested to remove thread with ID {0} but this is not being monitored", threadID);
240
241 return false;
242 }
243 }
244 }
245
246 public static bool AbortThread(int threadID)
247 {
248 lock (m_threads)
249 {
250 if (m_threads.ContainsKey(threadID))
251 {
252 ThreadWatchdogInfo twi = m_threads[threadID];
253 twi.Thread.Abort();
254 RemoveThread(threadID);
255
256 return true;
257 }
258 else
259 {
260 return false;
261 }
262 }
263 }
264
265 private static void UpdateThread(int threadID)
266 {
267 ThreadWatchdogInfo threadInfo;
268
269 // Although TryGetValue is not a thread safe operation, we use a try/catch here instead
270 // of a lock for speed. Adding/removing threads is a very rare operation compared to
271 // UpdateThread(), and a single UpdateThread() failure here and there won't break
272 // anything
273 try
274 {
275 if (m_threads.TryGetValue(threadID, out threadInfo))
276 {
277 threadInfo.LastTick = Environment.TickCount & Int32.MaxValue;
278 threadInfo.IsTimedOut = false;
279 }
280 else
281 {
282 m_log.WarnFormat("[WATCHDOG]: Asked to update thread {0} which is not being monitored", threadID);
283 }
284 }
285 catch { }
286 }
287
288 /// <summary>
289 /// Get currently watched threads for diagnostic purposes
290 /// </summary>
291 /// <returns></returns>
292 public static ThreadWatchdogInfo[] GetThreadsInfo()
293 {
294 lock (m_threads)
295 return m_threads.Values.ToArray();
296 }
297
298 /// <summary>
299 /// Return the current thread's watchdog info.
300 /// </summary>
301 /// <returns>The watchdog info. null if the thread isn't being monitored.</returns>
302 public static ThreadWatchdogInfo GetCurrentThreadInfo()
303 {
304 lock (m_threads)
305 {
306 if (m_threads.ContainsKey(Thread.CurrentThread.ManagedThreadId))
307 return m_threads[Thread.CurrentThread.ManagedThreadId];
308 }
309
310 return null;
311 }
312
313 /// <summary>
314 /// Check watched threads. Fire alarm if appropriate.
315 /// </summary>
316 /// <param name="sender"></param>
317 /// <param name="e"></param>
318 private static void WatchdogTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
319 {
320 int now = Environment.TickCount & Int32.MaxValue;
321 int msElapsed = now - LastWatchdogThreadTick;
322
323 if (msElapsed > WATCHDOG_INTERVAL_MS * 2)
324 m_log.WarnFormat(
325 "[WATCHDOG]: {0} ms since Watchdog last ran. Interval should be approximately {1} ms",
326 msElapsed, WATCHDOG_INTERVAL_MS);
327
328 LastWatchdogThreadTick = Environment.TickCount & Int32.MaxValue;
329
330 Action<ThreadWatchdogInfo> callback = OnWatchdogTimeout;
331
332 if (callback != null)
333 {
334 List<ThreadWatchdogInfo> callbackInfos = null;
335
336 lock (m_threads)
337 {
338 foreach (ThreadWatchdogInfo threadInfo in m_threads.Values)
339 {
340 if (threadInfo.Thread.ThreadState == ThreadState.Stopped)
341 {
342 RemoveThread(threadInfo.Thread.ManagedThreadId);
343
344 if (callbackInfos == null)
345 callbackInfos = new List<ThreadWatchdogInfo>();
346
347 callbackInfos.Add(threadInfo);
348 }
349 else if (!threadInfo.IsTimedOut && now - threadInfo.LastTick >= threadInfo.Timeout)
350 {
351 threadInfo.IsTimedOut = true;
352
353 if (threadInfo.AlarmIfTimeout)
354 {
355 if (callbackInfos == null)
356 callbackInfos = new List<ThreadWatchdogInfo>();
357
358 // Send a copy of the watchdog info to prevent race conditions where the watchdog
359 // thread updates the monitoring info after an alarm has been sent out.
360 callbackInfos.Add(new ThreadWatchdogInfo(threadInfo));
361 }
362 }
363 }
364 }
365
366 if (callbackInfos != null)
367 foreach (ThreadWatchdogInfo callbackInfo in callbackInfos)
368 callback(callbackInfo);
369 }
370
371 if (MemoryWatchdog.Enabled)
372 MemoryWatchdog.Update();
373
374 ChecksManager.CheckChecks();
375 StatsManager.RecordStats();
376
377 m_watchdogTimer.Start();
378 }
379 }
380} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/WorkManager.cs b/OpenSim/Framework/Monitoring/WorkManager.cs
new file mode 100644
index 0000000..d1a74ce
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/WorkManager.cs
@@ -0,0 +1,290 @@
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.Reflection;
30using System.Threading;
31using log4net;
32
33namespace OpenSim.Framework.Monitoring
34{
35 /// <summary>
36 /// Manages various work items in the simulator.
37 /// </summary>
38 /// <remarks>
39 /// Currently, here work can be started
40 /// * As a long-running and monitored thread.
41 /// * In a thread that will never timeout but where the job is expected to eventually complete.
42 /// * In a threadpool thread that will timeout if it takes a very long time to complete (> 10 mins).
43 /// * As a job which will be run in a single-threaded job engine. Such jobs must not incorporate delays (sleeps,
44 /// network waits, etc.).
45 ///
46 /// This is an evolving approach to better manage the work that OpenSimulator is asked to do from a very diverse
47 /// range of sources (client actions, incoming network, outgoing network calls, etc.).
48 ///
49 /// Util.FireAndForget is still available to insert jobs in the threadpool, though this is equivalent to
50 /// WorkManager.RunInThreadPool().
51 /// </remarks>
52 public static class WorkManager
53 {
54 private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
55
56 public static JobEngine JobEngine { get; private set; }
57
58 static WorkManager()
59 {
60 JobEngine = new JobEngine("Non-blocking non-critical job engine", "JOB ENGINE");
61
62 StatsManager.RegisterStat(
63 new Stat(
64 "JobsWaiting",
65 "Number of jobs waiting for processing.",
66 "",
67 "",
68 "server",
69 "jobengine",
70 StatType.Pull,
71 MeasuresOfInterest.None,
72 stat => stat.Value = JobEngine.JobsWaiting,
73 StatVerbosity.Debug));
74
75 MainConsole.Instance.Commands.AddCommand(
76 "Debug",
77 false,
78 "debug jobengine",
79 "debug jobengine <start|stop|status|log>",
80 "Start, stop, get status or set logging level of the job engine.",
81 "If stopped then all outstanding jobs are processed immediately.",
82 HandleControlCommand);
83 }
84
85 /// <summary>
86 /// Start a new long-lived thread.
87 /// </summary>
88 /// <param name="start">The method that will be executed in a new thread</param>
89 /// <param name="name">A name to give to the new thread</param>
90 /// <param name="priority">Priority to run the thread at</param>
91 /// <param name="isBackground">True to run this thread as a background thread, otherwise false</param>
92 /// <param name="alarmIfTimeout">Trigger an alarm function is we have timed out</param>
93 /// <param name="log">If true then creation of thread is logged.</param>
94 /// <returns>The newly created Thread object</returns>
95 public static Thread StartThread(
96 ThreadStart start, string name, ThreadPriority priority, bool isBackground, bool alarmIfTimeout, bool log = true)
97 {
98 return StartThread(start, name, priority, isBackground, alarmIfTimeout, null, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS, log);
99 }
100
101 /// <summary>
102 /// Start a new thread that is tracked by the watchdog
103 /// </summary>
104 /// <param name="start">The method that will be executed in a new thread</param>
105 /// <param name="name">A name to give to the new thread</param>
106 /// <param name="priority">Priority to run the thread at</param>
107 /// <param name="isBackground">True to run this thread as a background
108 /// thread, otherwise false</param>
109 /// <param name="alarmIfTimeout">Trigger an alarm function is we have timed out</param>
110 /// <param name="alarmMethod">
111 /// Alarm method to call if alarmIfTimeout is true and there is a timeout.
112 /// Normally, this will just return some useful debugging information.
113 /// </param>
114 /// <param name="timeout">Number of milliseconds to wait until we issue a warning about timeout.</param>
115 /// <param name="log">If true then creation of thread is logged.</param>
116 /// <returns>The newly created Thread object</returns>
117 public static Thread StartThread(
118 ThreadStart start, string name, ThreadPriority priority, bool isBackground,
119 bool alarmIfTimeout, Func<string> alarmMethod, int timeout, bool log = true)
120 {
121 Thread thread = new Thread(start);
122 thread.Priority = priority;
123 thread.IsBackground = isBackground;
124
125 Watchdog.ThreadWatchdogInfo twi
126 = new Watchdog.ThreadWatchdogInfo(thread, timeout, name)
127 { AlarmIfTimeout = alarmIfTimeout, AlarmMethod = alarmMethod };
128
129 Watchdog.AddThread(twi, name, log:log);
130
131 thread.Start();
132 thread.Name = name;
133
134 return thread;
135 }
136
137 /// <summary>
138 /// Run the callback in a new thread immediately. If the thread exits with an exception log it but do
139 /// not propogate it.
140 /// </summary>
141 /// <param name="callback">Code for the thread to execute.</param>
142 /// <param name="obj">Object to pass to the thread.</param>
143 /// <param name="name">Name of the thread</param>
144 public static void RunInThread(WaitCallback callback, object obj, string name, bool log = false)
145 {
146 if (Util.FireAndForgetMethod == FireAndForgetMethod.RegressionTest)
147 {
148 Culture.SetCurrentCulture();
149 callback(obj);
150 return;
151 }
152
153 ThreadStart ts = new ThreadStart(delegate()
154 {
155 try
156 {
157 Culture.SetCurrentCulture();
158 callback(obj);
159 Watchdog.RemoveThread(log:false);
160 }
161 catch (Exception e)
162 {
163 m_log.Error(string.Format("[WATCHDOG]: Exception in thread {0}.", name), e);
164 }
165 });
166
167 StartThread(ts, name, ThreadPriority.Normal, true, false, log:log);
168 }
169
170 /// <summary>
171 /// Run the callback via a threadpool thread.
172 /// </summary>
173 /// <remarks>
174 /// Such jobs may run after some delay but must always complete.
175 /// </remarks>
176 /// <param name="callback"></param>
177 /// <param name="obj"></param>
178 /// <param name="name">The name of the job. This is used in monitoring and debugging.</param>
179 public static void RunInThreadPool(System.Threading.WaitCallback callback, object obj, string name)
180 {
181 Util.FireAndForget(callback, obj, name);
182 }
183
184 /// <summary>
185 /// Run a job.
186 /// </summary>
187 /// <remarks>
188 /// This differs from direct scheduling (e.g. Util.FireAndForget) in that a job can be run in the job
189 /// engine if it is running, where all jobs are currently performed in sequence on a single thread. This is
190 /// to prevent observed overload and server freeze problems when there are hundreds of connections which all attempt to
191 /// perform work at once (e.g. in conference situations). With lower numbers of connections, the small
192 /// delay in performing jobs in sequence rather than concurrently has not been notiecable in testing, though a future more
193 /// sophisticated implementation could perform jobs concurrently when the server is under low load.
194 ///
195 /// However, be advised that some callers of this function rely on all jobs being performed in sequence if any
196 /// jobs are performed in sequence (i.e. if jobengine is active or not). Therefore, expanding the jobengine
197 /// beyond a single thread will require considerable thought.
198 ///
199 /// Also, any jobs submitted must be guaranteed to complete within a reasonable timeframe (e.g. they cannot
200 /// incorporate a network delay with a long timeout). At the moment, work that could suffer such issues
201 /// should still be run directly with RunInThread(), Util.FireAndForget(), etc. This is another area where
202 /// the job engine could be improved and so CPU utilization improved by better management of concurrency within
203 /// OpenSimulator.
204 /// </remarks>
205 /// <param name="jobType">General classification for the job (e.g. "RezAttachments").</param>
206 /// <param name="callback">Callback for job.</param>
207 /// <param name="obj">Object to pass to callback when run</param>
208 /// <param name="name">Specific name of job (e.g. "RezAttachments for Joe Bloggs"</param>
209 /// <param name="canRunInThisThread">If set to true then the job may be run in ths calling thread.</param>
210 /// <param name="mustNotTimeout">If the true then the job must never timeout.</param>
211 /// <param name="log">If set to true then extra logging is performed.</param>
212 public static void RunJob(
213 string jobType, WaitCallback callback, object obj, string name,
214 bool canRunInThisThread = false, bool mustNotTimeout = false,
215 bool log = false)
216 {
217 if (Util.FireAndForgetMethod == FireAndForgetMethod.RegressionTest)
218 {
219 Culture.SetCurrentCulture();
220 callback(obj);
221 return;
222 }
223
224 if (JobEngine.IsRunning)
225 JobEngine.QueueJob(name, () => callback(obj));
226 else if (canRunInThisThread)
227 callback(obj);
228 else if (mustNotTimeout)
229 RunInThread(callback, obj, name, log);
230 else
231 Util.FireAndForget(callback, obj, name);
232 }
233
234 private static void HandleControlCommand(string module, string[] args)
235 {
236 // if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene)
237 // return;
238
239 if (args.Length < 3)
240 {
241 MainConsole.Instance.Output("Usage: debug jobengine <stop|start|status|log>");
242 return;
243 }
244
245 string subCommand = args[2];
246
247 if (subCommand == "stop")
248 {
249 JobEngine.Stop();
250 MainConsole.Instance.OutputFormat("Stopped job engine.");
251 }
252 else if (subCommand == "start")
253 {
254 JobEngine.Start();
255 MainConsole.Instance.OutputFormat("Started job engine.");
256 }
257 else if (subCommand == "status")
258 {
259 MainConsole.Instance.OutputFormat("Job engine running: {0}", JobEngine.IsRunning);
260
261 JobEngine.Job job = JobEngine.CurrentJob;
262 MainConsole.Instance.OutputFormat("Current job {0}", job != null ? job.Name : "none");
263
264 MainConsole.Instance.OutputFormat(
265 "Jobs waiting: {0}", JobEngine.IsRunning ? JobEngine.JobsWaiting.ToString() : "n/a");
266 MainConsole.Instance.OutputFormat("Log Level: {0}", JobEngine.LogLevel);
267 }
268 else if (subCommand == "log")
269 {
270 if (args.Length < 4)
271 {
272 MainConsole.Instance.Output("Usage: debug jobengine log <level>");
273 return;
274 }
275
276 // int logLevel;
277 int logLevel = int.Parse(args[3]);
278 // if (ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[4], out logLevel))
279 // {
280 JobEngine.LogLevel = logLevel;
281 MainConsole.Instance.OutputFormat("Set debug log level to {0}", JobEngine.LogLevel);
282 // }
283 }
284 else
285 {
286 MainConsole.Instance.OutputFormat("Unrecognized job engine subcommand {0}", subCommand);
287 }
288 }
289 }
290} \ No newline at end of file