clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Linq;
32 using System.Text;
33  
34 using OpenSim.Framework;
35 using OpenMetaverse.StructuredData;
36  
37 namespace 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["cat"].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 }