diff options
Diffstat (limited to 'OpenSim/Framework/Monitoring/StatsManager.cs')
-rw-r--r-- | OpenSim/Framework/Monitoring/StatsManager.cs | 360 |
1 files changed, 360 insertions, 0 deletions
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 | ||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | |||
28 | namespace OpenSim.Framework.Monitoring | 31 | namespace 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 |