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