clockwerk-opensim – Blame information for rev 1
?pathlinks?
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 | } |