opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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 "show stats",
76 "show stats [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 StatsLogger.RegisterConsoleCommands(console);
88 }
89  
90 public static void HandleShowStatsCommand(string module, string[] cmd)
91 {
92 ICommandConsole con = MainConsole.Instance;
93  
94 if (cmd.Length > 2)
95 {
96 foreach (string name in cmd.Skip(2))
97 {
98 string[] components = name.Split('.');
99  
100 string categoryName = components[0];
101 string containerName = components.Length > 1 ? components[1] : null;
102  
103 if (categoryName == AllSubCommand)
104 {
105 OutputAllStatsToConsole(con);
106 }
107 else if (categoryName == ListSubCommand)
108 {
109 con.Output("Statistic categories available are:");
110 foreach (string category in RegisteredStats.Keys)
111 con.OutputFormat(" {0}", category);
112 }
113 else
114 {
115 SortedDictionary<string, SortedDictionary<string, Stat>> category;
116 if (!RegisteredStats.TryGetValue(categoryName, out category))
117 {
118 con.OutputFormat("No such category as {0}", categoryName);
119 }
120 else
121 {
122 if (String.IsNullOrEmpty(containerName))
123 {
124 OutputCategoryStatsToConsole(con, category);
125 }
126 else
127 {
128 SortedDictionary<string, Stat> container;
129 if (category.TryGetValue(containerName, out container))
130 {
131 OutputContainerStatsToConsole(con, container);
132 }
133 else
134 {
135 con.OutputFormat("No such container {0} in category {1}", containerName, categoryName);
136 }
137 }
138 }
139 }
140 }
141 }
142 else
143 {
144 // Legacy
145 if (SimExtraStats != null)
146 con.Output(SimExtraStats.Report());
147 else
148 OutputAllStatsToConsole(con);
149 }
150 }
151  
152 public static List<string> GetAllStatsReports()
153 {
154 List<string> reports = new List<string>();
155  
156 foreach (var category in RegisteredStats.Values)
157 reports.AddRange(GetCategoryStatsReports(category));
158  
159 return reports;
160 }
161  
162 private static void OutputAllStatsToConsole(ICommandConsole con)
163 {
164 foreach (string report in GetAllStatsReports())
165 con.Output(report);
166 }
167  
168 private static List<string> GetCategoryStatsReports(
169 SortedDictionary<string, SortedDictionary<string, Stat>> category)
170 {
171 List<string> reports = new List<string>();
172  
173 foreach (var container in category.Values)
174 reports.AddRange(GetContainerStatsReports(container));
175  
176 return reports;
177 }
178  
179 private static void OutputCategoryStatsToConsole(
180 ICommandConsole con, SortedDictionary<string, SortedDictionary<string, Stat>> category)
181 {
182 foreach (string report in GetCategoryStatsReports(category))
183 con.Output(report);
184 }
185  
186 private static List<string> GetContainerStatsReports(SortedDictionary<string, Stat> container)
187 {
188 List<string> reports = new List<string>();
189  
190 foreach (Stat stat in container.Values)
191 reports.Add(stat.ToConsoleString());
192  
193 return reports;
194 }
195  
196 private static void OutputContainerStatsToConsole(
197 ICommandConsole con, SortedDictionary<string, Stat> container)
198 {
199 foreach (string report in GetContainerStatsReports(container))
200 con.Output(report);
201 }
202  
203 // Creates an OSDMap of the format:
204 // { categoryName: {
205 // containerName: {
206 // statName: {
207 // "Name": name,
208 // "ShortName": shortName,
209 // ...
210 // },
211 // statName: {
212 // "Name": name,
213 // "ShortName": shortName,
214 // ...
215 // },
216 // ...
217 // },
218 // containerName: {
219 // ...
220 // },
221 // ...
222 // },
223 // categoryName: {
224 // ...
225 // },
226 // ...
227 // }
228 // The passed in parameters will filter the categories, containers and stats returned. If any of the
229 // parameters are either EmptyOrNull or the AllSubCommand value, all of that type will be returned.
230 // Case matters.
231 public static OSDMap GetStatsAsOSDMap(string pCategoryName, string pContainerName, string pStatName)
232 {
233 OSDMap map = new OSDMap();
234  
235 foreach (string catName in RegisteredStats.Keys)
236 {
237 // Do this category if null spec, "all" subcommand or category name matches passed parameter.
238 // Skip category if none of the above.
239 if (!(String.IsNullOrEmpty(pCategoryName) || pCategoryName == AllSubCommand || pCategoryName == catName))
240 continue;
241  
242 OSDMap contMap = new OSDMap();
243 foreach (string contName in RegisteredStats[catName].Keys)
244 {
245 if (!(string.IsNullOrEmpty(pContainerName) || pContainerName == AllSubCommand || pContainerName == contName))
246 continue;
247  
248 OSDMap statMap = new OSDMap();
249  
250 SortedDictionary<string, Stat> theStats = RegisteredStats[catName][contName];
251 foreach (string statName in theStats.Keys)
252 {
253 if (!(String.IsNullOrEmpty(pStatName) || pStatName == AllSubCommand || pStatName == statName))
254 continue;
255  
256 statMap.Add(statName, theStats[statName].ToOSDMap());
257 }
258  
259 contMap.Add(contName, statMap);
260 }
261 map.Add(catName, contMap);
262 }
263  
264 return map;
265 }
266  
267 public static Hashtable HandleStatsRequest(Hashtable request)
268 {
269 Hashtable responsedata = new Hashtable();
270 // string regpath = request["uri"].ToString();
271 int response_code = 200;
272 string contenttype = "text/json";
273  
274 string pCategoryName = StatsManager.AllSubCommand;
275 string pContainerName = StatsManager.AllSubCommand;
276 string pStatName = StatsManager.AllSubCommand;
277  
278 if (request.ContainsKey("cat")) pCategoryName = request["cat"].ToString();
279 if (request.ContainsKey("cont")) pContainerName = request["cat"].ToString();
280 if (request.ContainsKey("stat")) pStatName = request["cat"].ToString();
281  
282 string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString();
283  
284 // If requestor wants it as a callback function, build response as a function rather than just the JSON string.
285 if (request.ContainsKey("callback"))
286 {
287 strOut = request["callback"].ToString() + "(" + strOut + ");";
288 }
289  
290 // m_log.DebugFormat("{0} StatFetch: uri={1}, cat={2}, cont={3}, stat={4}, resp={5}",
291 // LogHeader, regpath, pCategoryName, pContainerName, pStatName, strOut);
292  
293 responsedata["int_response_code"] = response_code;
294 responsedata["content_type"] = contenttype;
295 responsedata["keepalive"] = false;
296 responsedata["str_response_string"] = strOut;
297 responsedata["access_control_allow_origin"] = "*";
298  
299 return responsedata;
300 }
301  
302 // /// <summary>
303 // /// Start collecting statistics related to assets.
304 // /// Should only be called once.
305 // /// </summary>
306 // public static AssetStatsCollector StartCollectingAssetStats()
307 // {
308 // assetStats = new AssetStatsCollector();
309 //
310 // return assetStats;
311 // }
312 //
313 // /// <summary>
314 // /// Start collecting statistics related to users.
315 // /// Should only be called once.
316 // /// </summary>
317 // public static UserStatsCollector StartCollectingUserStats()
318 // {
319 // userStats = new UserStatsCollector();
320 //
321 // return userStats;
322 // }
323  
324 /// <summary>
325 /// Register a statistic.
326 /// </summary>
327 /// <param name='stat'></param>
328 /// <returns></returns>
329 public static bool RegisterStat(Stat stat)
330 {
331 SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
332 SortedDictionary<string, Stat> container = null, newContainer;
333  
334 lock (RegisteredStats)
335 {
336 // Stat name is not unique across category/container/shortname key.
337 // XXX: For now just return false. This is to avoid problems in regression tests where all tests
338 // in a class are run in the same instance of the VM.
339 if (TryGetStatParents(stat, out category, out container))
340 return false;
341  
342 // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
343 // This means that we don't need to lock or copy them on iteration, which will be a much more
344 // common operation after startup.
345 if (container != null)
346 newContainer = new SortedDictionary<string, Stat>(container);
347 else
348 newContainer = new SortedDictionary<string, Stat>();
349  
350 if (category != null)
351 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
352 else
353 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>();
354  
355 newContainer[stat.ShortName] = stat;
356 newCategory[stat.Container] = newContainer;
357 RegisteredStats[stat.Category] = newCategory;
358 }
359  
360 return true;
361 }
362  
363 /// <summary>
364 /// Deregister a statistic
365 /// </summary>>
366 /// <param name='stat'></param>
367 /// <returns></returns>
368 public static bool DeregisterStat(Stat stat)
369 {
370 SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
371 SortedDictionary<string, Stat> container = null, newContainer;
372  
373 lock (RegisteredStats)
374 {
375 if (!TryGetStatParents(stat, out category, out container))
376 return false;
377  
378 newContainer = new SortedDictionary<string, Stat>(container);
379 newContainer.Remove(stat.ShortName);
380  
381 newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
382 newCategory.Remove(stat.Container);
383  
384 newCategory[stat.Container] = newContainer;
385 RegisteredStats[stat.Category] = newCategory;
386  
387 return true;
388 }
389 }
390  
391 public static bool TryGetStat(string category, string container, string statShortName, out Stat stat)
392 {
393 stat = null;
394 SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
395  
396 lock (RegisteredStats)
397 {
398 if (!TryGetStatsForCategory(category, out categoryStats))
399 return false;
400  
401 SortedDictionary<string, Stat> containerStats;
402  
403 if (!categoryStats.TryGetValue(container, out containerStats))
404 return false;
405  
406 return containerStats.TryGetValue(statShortName, out stat);
407 }
408 }
409  
410 public static bool TryGetStatsForCategory(
411 string category, out SortedDictionary<string, SortedDictionary<string, Stat>> stats)
412 {
413 lock (RegisteredStats)
414 return RegisteredStats.TryGetValue(category, out stats);
415 }
416  
417 /// <summary>
418 /// Get the same stat for each container in a given category.
419 /// </summary>
420 /// <returns>
421 /// The stats if there were any to fetch. Otherwise null.
422 /// </returns>
423 /// <param name='category'></param>
424 /// <param name='statShortName'></param>
425 public static List<Stat> GetStatsFromEachContainer(string category, string statShortName)
426 {
427 SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
428  
429 lock (RegisteredStats)
430 {
431 if (!RegisteredStats.TryGetValue(category, out categoryStats))
432 return null;
433  
434 List<Stat> stats = null;
435  
436 foreach (SortedDictionary<string, Stat> containerStats in categoryStats.Values)
437 {
438 if (containerStats.ContainsKey(statShortName))
439 {
440 if (stats == null)
441 stats = new List<Stat>();
442  
443 stats.Add(containerStats[statShortName]);
444 }
445 }
446  
447 return stats;
448 }
449 }
450  
451 public static bool TryGetStatParents(
452 Stat stat,
453 out SortedDictionary<string, SortedDictionary<string, Stat>> category,
454 out SortedDictionary<string, Stat> container)
455 {
456 category = null;
457 container = null;
458  
459 lock (RegisteredStats)
460 {
461 if (RegisteredStats.TryGetValue(stat.Category, out category))
462 {
463 if (category.TryGetValue(stat.Container, out container))
464 {
465 if (container.ContainsKey(stat.ShortName))
466 return true;
467 }
468 }
469 }
470  
471 return false;
472 }
473  
474 public static void RecordStats()
475 {
476 lock (RegisteredStats)
477 {
478 foreach (SortedDictionary<string, SortedDictionary<string, Stat>> category in RegisteredStats.Values)
479 {
480 foreach (SortedDictionary<string, Stat> container in category.Values)
481 {
482 foreach (Stat stat in container.Values)
483 {
484 if (stat.MeasuresOfInterest != MeasuresOfInterest.None)
485 stat.RecordValue();
486 }
487 }
488 }
489 }
490 }
491 }
492  
493 /// <summary>
494 /// Stat type.
495 /// </summary>
496 /// <remarks>
497 /// A push stat is one which is continually updated and so it's value can simply by read.
498 /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
499 /// </remarks>
500 public enum StatType
501 {
502 Push,
503 Pull
504 }
505  
506 /// <summary>
507 /// Measures of interest for this stat.
508 /// </summary>
509 [Flags]
510 public enum MeasuresOfInterest
511 {
512 None,
513 AverageChangeOverTime
514 }
515  
516 /// <summary>
517 /// Verbosity of stat.
518 /// </summary>
519 /// <remarks>
520 /// Info will always be displayed.
521 /// </remarks>
522 public enum StatVerbosity
523 {
524 Debug,
525 Info
526 }
527 }