aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Monitoring
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/Monitoring')
-rw-r--r--OpenSim/Framework/Monitoring/BaseStatsCollector.cs23
-rw-r--r--OpenSim/Framework/Monitoring/MemoryWatchdog.cs10
-rw-r--r--OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs19
-rw-r--r--OpenSim/Framework/Monitoring/StatsManager.cs360
-rw-r--r--OpenSim/Framework/Monitoring/Watchdog.cs35
5 files changed, 433 insertions, 14 deletions
diff --git a/OpenSim/Framework/Monitoring/BaseStatsCollector.cs b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs
index 9ee0876..446e3c0 100644
--- a/OpenSim/Framework/Monitoring/BaseStatsCollector.cs
+++ b/OpenSim/Framework/Monitoring/BaseStatsCollector.cs
@@ -43,27 +43,32 @@ namespace OpenSim.Framework.Monitoring
43 StringBuilder sb = new StringBuilder(Environment.NewLine); 43 StringBuilder sb = new StringBuilder(Environment.NewLine);
44 sb.Append("MEMORY STATISTICS"); 44 sb.Append("MEMORY STATISTICS");
45 sb.Append(Environment.NewLine); 45 sb.Append(Environment.NewLine);
46 sb.Append( 46 sb.AppendFormat(
47 string.Format(
48 "Allocated to OpenSim objects: {0} MB\n", 47 "Allocated to OpenSim objects: {0} MB\n",
49 Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0))); 48 Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0));
49
50 sb.AppendFormat(
51 "OpenSim last object memory churn : {0} MB/s\n",
52 Math.Round((MemoryWatchdog.LastMemoryChurn * 1000) / 1024.0 / 1024, 3));
53
54 sb.AppendFormat(
55 "OpenSim average object memory churn : {0} MB/s\n",
56 Math.Round((MemoryWatchdog.AverageMemoryChurn * 1000) / 1024.0 / 1024, 3));
50 57
51 Process myprocess = Process.GetCurrentProcess(); 58 Process myprocess = Process.GetCurrentProcess();
52 if (!myprocess.HasExited) 59 if (!myprocess.HasExited)
53 { 60 {
54 myprocess.Refresh(); 61 myprocess.Refresh();
55 sb.Append( 62 sb.AppendFormat(
56 string.Format(
57 "Process memory: Physical {0} MB \t Paged {1} MB \t Virtual {2} MB\n", 63 "Process memory: Physical {0} MB \t Paged {1} MB \t Virtual {2} MB\n",
58 Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024.0 / 1024.0), 64 Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024.0 / 1024.0),
59 Math.Round(Process.GetCurrentProcess().PagedMemorySize64 / 1024.0 / 1024.0), 65 Math.Round(Process.GetCurrentProcess().PagedMemorySize64 / 1024.0 / 1024.0),
60 Math.Round(Process.GetCurrentProcess().VirtualMemorySize64 / 1024.0 / 1024.0))); 66 Math.Round(Process.GetCurrentProcess().VirtualMemorySize64 / 1024.0 / 1024.0));
61 sb.Append( 67 sb.AppendFormat(
62 string.Format(
63 "Peak process memory: Physical {0} MB \t Paged {1} MB \t Virtual {2} MB\n", 68 "Peak process memory: Physical {0} MB \t Paged {1} MB \t Virtual {2} MB\n",
64 Math.Round(Process.GetCurrentProcess().PeakWorkingSet64 / 1024.0 / 1024.0), 69 Math.Round(Process.GetCurrentProcess().PeakWorkingSet64 / 1024.0 / 1024.0),
65 Math.Round(Process.GetCurrentProcess().PeakPagedMemorySize64 / 1024.0 / 1024.0), 70 Math.Round(Process.GetCurrentProcess().PeakPagedMemorySize64 / 1024.0 / 1024.0),
66 Math.Round(Process.GetCurrentProcess().PeakVirtualMemorySize64 / 1024.0 / 1024.0))); 71 Math.Round(Process.GetCurrentProcess().PeakVirtualMemorySize64 / 1024.0 / 1024.0));
67 } 72 }
68 else 73 else
69 sb.Append("Process reported as Exited \n"); 74 sb.Append("Process reported as Exited \n");
diff --git a/OpenSim/Framework/Monitoring/MemoryWatchdog.cs b/OpenSim/Framework/Monitoring/MemoryWatchdog.cs
index a23cf1f..c6010cd 100644
--- a/OpenSim/Framework/Monitoring/MemoryWatchdog.cs
+++ b/OpenSim/Framework/Monitoring/MemoryWatchdog.cs
@@ -60,7 +60,7 @@ namespace OpenSim.Framework.Monitoring
60 private static bool m_enabled; 60 private static bool m_enabled;
61 61
62 /// <summary> 62 /// <summary>
63 /// Average memory churn in bytes per millisecond. 63 /// Last memory churn in bytes per millisecond.
64 /// </summary> 64 /// </summary>
65 public static double AverageMemoryChurn 65 public static double AverageMemoryChurn
66 { 66 {
@@ -68,6 +68,14 @@ namespace OpenSim.Framework.Monitoring
68 } 68 }
69 69
70 /// <summary> 70 /// <summary>
71 /// Average memory churn in bytes per millisecond.
72 /// </summary>
73 public static double LastMemoryChurn
74 {
75 get { if (m_samples.Count > 0) return m_samples.Last(); else return 0; }
76 }
77
78 /// <summary>
71 /// Maximum number of statistical samples. 79 /// Maximum number of statistical samples.
72 /// </summary> 80 /// </summary>
73 /// <remarks> 81 /// <remarks>
diff --git a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs
index cdd7cc7..aa86202 100644
--- a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs
+++ b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs
@@ -355,10 +355,25 @@ Asset service request failures: {3}" + Environment.NewLine,
355 sb.Append(Environment.NewLine); 355 sb.Append(Environment.NewLine);
356 sb.Append( 356 sb.Append(
357 string.Format( 357 string.Format(
358 "{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}", 358 "{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",
359 inPacketsPerSecond, outPacketsPerSecond, pendingDownloads, pendingUploads, unackedBytes, totalFrameTime, 359 inPacketsPerSecond, outPacketsPerSecond, pendingDownloads, pendingUploads, unackedBytes, totalFrameTime,
360 netFrameTime, physicsFrameTime, otherFrameTime, agentFrameTime, imageFrameTime)); 360 netFrameTime, physicsFrameTime, otherFrameTime, agentFrameTime, imageFrameTime));
361 sb.Append(Environment.NewLine); 361
362 Dictionary<string, Dictionary<string, Stat>> sceneStats;
363
364 if (StatsManager.TryGetStats("scene", out sceneStats))
365 {
366 foreach (KeyValuePair<string, Dictionary<string, Stat>> kvp in sceneStats)
367 {
368 foreach (Stat stat in kvp.Value.Values)
369 {
370 if (stat.Verbosity == StatVerbosity.Info)
371 {
372 sb.AppendFormat("{0} ({1}): {2}{3}\n", stat.Name, stat.Container, stat.Value, stat.UnitName);
373 }
374 }
375 }
376 }
362 377
363 /* 378 /*
364 sb.Append(Environment.NewLine); 379 sb.Append(Environment.NewLine);
diff --git a/OpenSim/Framework/Monitoring/StatsManager.cs b/OpenSim/Framework/Monitoring/StatsManager.cs
index d78fa6a..4844336 100644
--- a/OpenSim/Framework/Monitoring/StatsManager.cs
+++ b/OpenSim/Framework/Monitoring/StatsManager.cs
@@ -25,6 +25,9 @@
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28using System;
29using System.Collections.Generic;
30
28namespace OpenSim.Framework.Monitoring 31namespace OpenSim.Framework.Monitoring
29{ 32{
30 /// <summary> 33 /// <summary>
@@ -32,6 +35,24 @@ namespace OpenSim.Framework.Monitoring
32 /// </summary> 35 /// </summary>
33 public class StatsManager 36 public class StatsManager
34 { 37 {
38 // Subcommand used to list other stats.
39 public const string AllSubCommand = "all";
40
41 // Subcommand used to list other stats.
42 public const string ListSubCommand = "list";
43
44 // All subcommands
45 public static HashSet<string> SubCommands = new HashSet<string> { AllSubCommand, ListSubCommand };
46
47 /// <summary>
48 /// Registered stats categorized by category/container/shortname
49 /// </summary>
50 /// <remarks>
51 /// Do not add or remove directly from this dictionary.
52 /// </remarks>
53 public static Dictionary<string, Dictionary<string, Dictionary<string, Stat>>> RegisteredStats
54 = new Dictionary<string, Dictionary<string, Dictionary<string, Stat>>>();
55
35 private static AssetStatsCollector assetStats; 56 private static AssetStatsCollector assetStats;
36 private static UserStatsCollector userStats; 57 private static UserStatsCollector userStats;
37 private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector(); 58 private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector();
@@ -40,6 +61,75 @@ namespace OpenSim.Framework.Monitoring
40 public static UserStatsCollector UserStats { get { return userStats; } } 61 public static UserStatsCollector UserStats { get { return userStats; } }
41 public static SimExtraStatsCollector SimExtraStats { get { return simExtraStats; } } 62 public static SimExtraStatsCollector SimExtraStats { get { return simExtraStats; } }
42 63
64 public static void RegisterConsoleCommands(ICommandConsole console)
65 {
66 console.Commands.AddCommand(
67 "General",
68 false,
69 "show stats",
70 "show stats [list|all|<category>]",
71 "Show statistical information for this server",
72 "If no final argument is specified then legacy statistics information is currently shown.\n"
73 + "If list is specified then statistic categories are shown.\n"
74 + "If all is specified then all registered statistics are shown.\n"
75 + "If a category name is specified then only statistics from that category are shown.\n"
76 + "THIS STATS FACILITY IS EXPERIMENTAL AND DOES NOT YET CONTAIN ALL STATS",
77 HandleShowStatsCommand);
78 }
79
80 public static void HandleShowStatsCommand(string module, string[] cmd)
81 {
82 ICommandConsole con = MainConsole.Instance;
83
84 if (cmd.Length > 2)
85 {
86 var categoryName = cmd[2];
87
88 if (categoryName == AllSubCommand)
89 {
90 foreach (var category in RegisteredStats.Values)
91 {
92 OutputCategoryStatsToConsole(con, category);
93 }
94 }
95 else if (categoryName == ListSubCommand)
96 {
97 con.Output("Statistic categories available are:");
98 foreach (string category in RegisteredStats.Keys)
99 con.OutputFormat(" {0}", category);
100 }
101 else
102 {
103 Dictionary<string, Dictionary<string, Stat>> category;
104 if (!RegisteredStats.TryGetValue(categoryName, out category))
105 {
106 con.OutputFormat("No such category as {0}", categoryName);
107 }
108 else
109 {
110 OutputCategoryStatsToConsole(con, category);
111 }
112 }
113 }
114 else
115 {
116 // Legacy
117 con.Output(SimExtraStats.Report());
118 }
119 }
120
121 private static void OutputCategoryStatsToConsole(
122 ICommandConsole con, Dictionary<string, Dictionary<string, Stat>> category)
123 {
124 foreach (var container in category.Values)
125 {
126 foreach (Stat stat in container.Values)
127 {
128 con.Output(stat.ToConsoleString());
129 }
130 }
131 }
132
43 /// <summary> 133 /// <summary>
44 /// Start collecting statistics related to assets. 134 /// Start collecting statistics related to assets.
45 /// Should only be called once. 135 /// Should only be called once.
@@ -61,5 +151,275 @@ namespace OpenSim.Framework.Monitoring
61 151
62 return userStats; 152 return userStats;
63 } 153 }
154
155 /// <summary>
156 /// Registers a statistic.
157 /// </summary>
158 /// <param name='stat'></param>
159 /// <returns></returns>
160 public static bool RegisterStat(Stat stat)
161 {
162 Dictionary<string, Dictionary<string, Stat>> category = null, newCategory;
163 Dictionary<string, Stat> container = null, newContainer;
164
165 lock (RegisteredStats)
166 {
167 // Stat name is not unique across category/container/shortname key.
168 // XXX: For now just return false. This is to avoid problems in regression tests where all tests
169 // in a class are run in the same instance of the VM.
170 if (TryGetStat(stat, out category, out container))
171 return false;
172
173 // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
174 // This means that we don't need to lock or copy them on iteration, which will be a much more
175 // common operation after startup.
176 if (container != null)
177 newContainer = new Dictionary<string, Stat>(container);
178 else
179 newContainer = new Dictionary<string, Stat>();
180
181 if (category != null)
182 newCategory = new Dictionary<string, Dictionary<string, Stat>>(category);
183 else
184 newCategory = new Dictionary<string, Dictionary<string, Stat>>();
185
186 newContainer[stat.ShortName] = stat;
187 newCategory[stat.Container] = newContainer;
188 RegisteredStats[stat.Category] = newCategory;
189 }
190
191 return true;
192 }
193
194 /// <summary>
195 /// Deregister a statistic
196 /// </summary>>
197 /// <param name='stat'></param>
198 /// <returns></returns
199 public static bool DeregisterStat(Stat stat)
200 {
201 Dictionary<string, Dictionary<string, Stat>> category = null, newCategory;
202 Dictionary<string, Stat> container = null, newContainer;
203
204 lock (RegisteredStats)
205 {
206 if (!TryGetStat(stat, out category, out container))
207 return false;
208
209 newContainer = new Dictionary<string, Stat>(container);
210 newContainer.Remove(stat.ShortName);
211
212 newCategory = new Dictionary<string, Dictionary<string, Stat>>(category);
213 newCategory.Remove(stat.Container);
214
215 newCategory[stat.Container] = newContainer;
216 RegisteredStats[stat.Category] = newCategory;
217
218 return true;
219 }
220 }
221
222 public static bool TryGetStats(string category, out Dictionary<string, Dictionary<string, Stat>> stats)
223 {
224 return RegisteredStats.TryGetValue(category, out stats);
225 }
226
227 public static bool TryGetStat(
228 Stat stat,
229 out Dictionary<string, Dictionary<string, Stat>> category,
230 out Dictionary<string, Stat> container)
231 {
232 category = null;
233 container = null;
234
235 lock (RegisteredStats)
236 {
237 if (RegisteredStats.TryGetValue(stat.Category, out category))
238 {
239 if (category.TryGetValue(stat.Container, out container))
240 {
241 if (container.ContainsKey(stat.ShortName))
242 return true;
243 }
244 }
245 }
246
247 return false;
248 }
249 }
250
251 /// <summary>
252 /// Stat type.
253 /// </summary>
254 /// <remarks>
255 /// A push stat is one which is continually updated and so it's value can simply by read.
256 /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
257 /// </remarks>
258 public enum StatType
259 {
260 Push,
261 Pull
262 }
263
264 /// <summary>
265 /// Verbosity of stat.
266 /// </summary>
267 /// <remarks>
268 /// Info will always be displayed.
269 /// </remarks>
270 public enum StatVerbosity
271 {
272 Debug,
273 Info
274 }
275
276 /// <summary>
277 /// Holds individual static details
278 /// </summary>
279 public class Stat
280 {
281 /// <summary>
282 /// Category of this stat (e.g. cache, scene, etc).
283 /// </summary>
284 public string Category { get; private set; }
285
286 /// <summary>
287 /// Containing name for this stat.
288 /// FIXME: In the case of a scene, this is currently the scene name (though this leaves
289 /// us with a to-be-resolved problem of non-unique region names).
290 /// </summary>
291 /// <value>
292 /// The container.
293 /// </value>
294 public string Container { get; private set; }
295
296 public StatType StatType { get; private set; }
297
298 /// <summary>
299 /// Action used to update this stat when the value is requested if it's a pull type.
300 /// </summary>
301 public Action<Stat> PullAction { get; private set; }
302
303 public StatVerbosity Verbosity { get; private set; }
304 public string ShortName { get; private set; }
305 public string Name { get; private set; }
306 public string Description { get; private set; }
307 public virtual string UnitName { get; private set; }
308
309 public virtual double Value
310 {
311 get
312 {
313 // Asking for an update here means that the updater cannot access this value without infinite recursion.
314 // XXX: A slightly messy but simple solution may be to flick a flag so we can tell if this is being
315 // called by the pull action and just return the value.
316 if (StatType == StatType.Pull)
317 PullAction(this);
318
319 return m_value;
320 }
321
322 set
323 {
324 m_value = value;
325 }
326 }
327
328 private double m_value;
329
330 /// <summary>
331 /// Constructor
332 /// </summary>
333 /// <param name='shortName'>Short name for the stat. Must not contain spaces. e.g. "LongFrames"</param>
334 /// <param name='name'>Human readable name for the stat. e.g. "Long frames"</param>
335 /// <param name='description'>Description of stat</param>
336 /// <param name='unitName'>
337 /// Unit name for the stat. Should be preceeded by a space if the unit name isn't normally appeneded immediately to the value.
338 /// e.g. " frames"
339 /// </param>
340 /// <param name='category'>Category under which this stat should appear, e.g. "scene". Do not capitalize.</param>
341 /// <param name='container'>Entity to which this stat relates. e.g. scene name if this is a per scene stat.</param>
342 /// <param name='type'>Push or pull</param>
343 /// <param name='pullAction'>Pull stats need an action to update the stat on request. Push stats should set null here.</param>
344 /// <param name='verbosity'>Verbosity of stat. Controls whether it will appear in short stat display or only full display.</param>
345 public Stat(
346 string shortName,
347 string name,
348 string description,
349 string unitName,
350 string category,
351 string container,
352 StatType type,
353 Action<Stat> pullAction,
354 StatVerbosity verbosity)
355 {
356 if (StatsManager.SubCommands.Contains(category))
357 throw new Exception(
358 string.Format("Stat cannot be in category '{0}' since this is reserved for a subcommand", category));
359
360 ShortName = shortName;
361 Name = name;
362 Description = description;
363 UnitName = unitName;
364 Category = category;
365 Container = container;
366 StatType = type;
367
368 if (StatType == StatType.Push && pullAction != null)
369 throw new Exception("A push stat cannot have a pull action");
370 else
371 PullAction = pullAction;
372
373 Verbosity = verbosity;
374 }
375
376 public virtual string ToConsoleString()
377 {
378 return string.Format(
379 "{0}.{1}.{2} : {3}{4}", Category, Container, ShortName, Value, UnitName);
380 }
381 }
382
383 public class PercentageStat : Stat
384 {
385 public int Antecedent { get; set; }
386 public int Consequent { get; set; }
387
388 public override double Value
389 {
390 get
391 {
392 int c = Consequent;
393
394 // Avoid any chance of a multi-threaded divide-by-zero
395 if (c == 0)
396 return 0;
397
398 return (double)Antecedent / c * 100;
399 }
400
401 set
402 {
403 throw new Exception("Cannot set value on a PercentageStat");
404 }
405 }
406
407 public PercentageStat(
408 string shortName,
409 string name,
410 string description,
411 string category,
412 string container,
413 StatType type,
414 Action<Stat> pullAction,
415 StatVerbosity verbosity)
416 : base(shortName, name, description, "%", category, container, type, pullAction, verbosity) {}
417
418 public override string ToConsoleString()
419 {
420 return string.Format(
421 "{0}.{1}.{2} : {3:0.##}{4} ({5}/{6})",
422 Category, Container, ShortName, Value, UnitName, Antecedent, Consequent);
423 }
64 } 424 }
65} \ No newline at end of file 425} \ No newline at end of file
diff --git a/OpenSim/Framework/Monitoring/Watchdog.cs b/OpenSim/Framework/Monitoring/Watchdog.cs
index b709baa..28d6d5c 100644
--- a/OpenSim/Framework/Monitoring/Watchdog.cs
+++ b/OpenSim/Framework/Monitoring/Watchdog.cs
@@ -89,6 +89,17 @@ namespace OpenSim.Framework.Monitoring
89 FirstTick = Environment.TickCount & Int32.MaxValue; 89 FirstTick = Environment.TickCount & Int32.MaxValue;
90 LastTick = FirstTick; 90 LastTick = FirstTick;
91 } 91 }
92
93 public ThreadWatchdogInfo(ThreadWatchdogInfo previousTwi)
94 {
95 Thread = previousTwi.Thread;
96 FirstTick = previousTwi.FirstTick;
97 LastTick = previousTwi.LastTick;
98 Timeout = previousTwi.Timeout;
99 IsTimedOut = previousTwi.IsTimedOut;
100 AlarmIfTimeout = previousTwi.AlarmIfTimeout;
101 AlarmMethod = previousTwi.AlarmMethod;
102 }
92 } 103 }
93 104
94 /// <summary> 105 /// <summary>
@@ -220,7 +231,25 @@ namespace OpenSim.Framework.Monitoring
220 private static bool RemoveThread(int threadID) 231 private static bool RemoveThread(int threadID)
221 { 232 {
222 lock (m_threads) 233 lock (m_threads)
223 return m_threads.Remove(threadID); 234 {
235 ThreadWatchdogInfo twi;
236 if (m_threads.TryGetValue(threadID, out twi))
237 {
238 m_log.DebugFormat(
239 "[WATCHDOG]: Removing thread {0}, ID {1}", twi.Thread.Name, twi.Thread.ManagedThreadId);
240
241 m_threads.Remove(threadID);
242
243 return true;
244 }
245 else
246 {
247 m_log.WarnFormat(
248 "[WATCHDOG]: Requested to remove thread with ID {0} but this is not being monitored", threadID);
249
250 return false;
251 }
252 }
224 } 253 }
225 254
226 public static bool AbortThread(int threadID) 255 public static bool AbortThread(int threadID)
@@ -335,7 +364,9 @@ namespace OpenSim.Framework.Monitoring
335 if (callbackInfos == null) 364 if (callbackInfos == null)
336 callbackInfos = new List<ThreadWatchdogInfo>(); 365 callbackInfos = new List<ThreadWatchdogInfo>();
337 366
338 callbackInfos.Add(threadInfo); 367 // Send a copy of the watchdog info to prevent race conditions where the watchdog
368 // thread updates the monitoring info after an alarm has been sent out.
369 callbackInfos.Add(new ThreadWatchdogInfo(threadInfo));
339 } 370 }
340 } 371 }
341 } 372 }