StatsManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Linq;
  31. using System.Text;
  32. using OpenSim.Framework;
  33. using OpenMetaverse.StructuredData;
  34. namespace OpenSim.Framework.Monitoring
  35. {
  36. /// <summary>
  37. /// Static class used to register/deregister/fetch statistics
  38. /// </summary>
  39. public static class StatsManager
  40. {
  41. // Subcommand used to list other stats.
  42. public const string AllSubCommand = "all";
  43. // Subcommand used to list other stats.
  44. public const string ListSubCommand = "list";
  45. // All subcommands
  46. public static HashSet<string> SubCommands = new HashSet<string> { AllSubCommand, ListSubCommand };
  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 SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>> RegisteredStats
  54. = new SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>>();
  55. // private static AssetStatsCollector assetStats;
  56. // private static UserStatsCollector userStats;
  57. // private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector();
  58. // public static AssetStatsCollector AssetStats { get { return assetStats; } }
  59. // public static UserStatsCollector UserStats { get { return userStats; } }
  60. public static SimExtraStatsCollector SimExtraStats { get; set; }
  61. public static void RegisterConsoleCommands(ICommandConsole console)
  62. {
  63. console.Commands.AddCommand(
  64. "General",
  65. false,
  66. "show stats",
  67. "show stats [list|all|(<category>[.<container>])+",
  68. "Show statistical information for this server",
  69. "If no final argument is specified then legacy statistics information is currently shown.\n"
  70. + "'list' argument will show statistic categories.\n"
  71. + "'all' will show all statistics.\n"
  72. + "A <category> name will show statistics from that category.\n"
  73. + "A <category>.<container> name will show statistics from that category in that container.\n"
  74. + "More than one name can be given separated by spaces.\n"
  75. + "THIS STATS FACILITY IS EXPERIMENTAL AND DOES NOT YET CONTAIN ALL STATS",
  76. HandleShowStatsCommand);
  77. StatsLogger.RegisterConsoleCommands(console);
  78. }
  79. public static void HandleShowStatsCommand(string module, string[] cmd)
  80. {
  81. ICommandConsole con = MainConsole.Instance;
  82. if (cmd.Length > 2)
  83. {
  84. foreach (string name in cmd.Skip(2))
  85. {
  86. string[] components = name.Split('.');
  87. string categoryName = components[0];
  88. string containerName = components.Length > 1 ? components[1] : null;
  89. if (categoryName == AllSubCommand)
  90. {
  91. OutputAllStatsToConsole(con);
  92. }
  93. else if (categoryName == ListSubCommand)
  94. {
  95. con.Output("Statistic categories available are:");
  96. foreach (string category in RegisteredStats.Keys)
  97. con.OutputFormat(" {0}", category);
  98. }
  99. else
  100. {
  101. SortedDictionary<string, SortedDictionary<string, Stat>> category;
  102. if (!RegisteredStats.TryGetValue(categoryName, out category))
  103. {
  104. con.OutputFormat("No such category as {0}", categoryName);
  105. }
  106. else
  107. {
  108. if (String.IsNullOrEmpty(containerName))
  109. {
  110. OutputCategoryStatsToConsole(con, category);
  111. }
  112. else
  113. {
  114. SortedDictionary<string, Stat> container;
  115. if (category.TryGetValue(containerName, out container))
  116. {
  117. OutputContainerStatsToConsole(con, container);
  118. }
  119. else
  120. {
  121. con.OutputFormat("No such container {0} in category {1}", containerName, categoryName);
  122. }
  123. }
  124. }
  125. }
  126. }
  127. }
  128. else
  129. {
  130. // Legacy
  131. if (SimExtraStats != null)
  132. con.Output(SimExtraStats.Report());
  133. else
  134. OutputAllStatsToConsole(con);
  135. }
  136. }
  137. public static List<string> GetAllStatsReports()
  138. {
  139. List<string> reports = new List<string>();
  140. foreach (var category in RegisteredStats.Values)
  141. reports.AddRange(GetCategoryStatsReports(category));
  142. return reports;
  143. }
  144. private static void OutputAllStatsToConsole(ICommandConsole con)
  145. {
  146. foreach (string report in GetAllStatsReports())
  147. con.Output(report);
  148. }
  149. private static List<string> GetCategoryStatsReports(
  150. SortedDictionary<string, SortedDictionary<string, Stat>> category)
  151. {
  152. List<string> reports = new List<string>();
  153. foreach (var container in category.Values)
  154. reports.AddRange(GetContainerStatsReports(container));
  155. return reports;
  156. }
  157. private static void OutputCategoryStatsToConsole(
  158. ICommandConsole con, SortedDictionary<string, SortedDictionary<string, Stat>> category)
  159. {
  160. foreach (string report in GetCategoryStatsReports(category))
  161. con.Output(report);
  162. }
  163. private static List<string> GetContainerStatsReports(SortedDictionary<string, Stat> container)
  164. {
  165. List<string> reports = new List<string>();
  166. foreach (Stat stat in container.Values)
  167. reports.Add(stat.ToConsoleString());
  168. return reports;
  169. }
  170. private static void OutputContainerStatsToConsole(
  171. ICommandConsole con, SortedDictionary<string, Stat> container)
  172. {
  173. foreach (string report in GetContainerStatsReports(container))
  174. con.Output(report);
  175. }
  176. // Creates an OSDMap of the format:
  177. // { categoryName: {
  178. // containerName: {
  179. // statName: {
  180. // "Name": name,
  181. // "ShortName": shortName,
  182. // ...
  183. // },
  184. // statName: {
  185. // "Name": name,
  186. // "ShortName": shortName,
  187. // ...
  188. // },
  189. // ...
  190. // },
  191. // containerName: {
  192. // ...
  193. // },
  194. // ...
  195. // },
  196. // categoryName: {
  197. // ...
  198. // },
  199. // ...
  200. // }
  201. // The passed in parameters will filter the categories, containers and stats returned. If any of the
  202. // parameters are either EmptyOrNull or the AllSubCommand value, all of that type will be returned.
  203. // Case matters.
  204. public static OSDMap GetStatsAsOSDMap(string pCategoryName, string pContainerName, string pStatName)
  205. {
  206. OSDMap map = new OSDMap();
  207. foreach (string catName in RegisteredStats.Keys)
  208. {
  209. // Do this category if null spec, "all" subcommand or category name matches passed parameter.
  210. // Skip category if none of the above.
  211. if (!(String.IsNullOrEmpty(pCategoryName) || pCategoryName == AllSubCommand || pCategoryName == catName))
  212. continue;
  213. OSDMap contMap = new OSDMap();
  214. foreach (string contName in RegisteredStats[catName].Keys)
  215. {
  216. if (!(string.IsNullOrEmpty(pContainerName) || pContainerName == AllSubCommand || pContainerName == contName))
  217. continue;
  218. OSDMap statMap = new OSDMap();
  219. SortedDictionary<string, Stat> theStats = RegisteredStats[catName][contName];
  220. foreach (string statName in theStats.Keys)
  221. {
  222. if (!(String.IsNullOrEmpty(pStatName) || pStatName == AllSubCommand || pStatName == statName))
  223. continue;
  224. statMap.Add(statName, theStats[statName].ToOSDMap());
  225. }
  226. contMap.Add(contName, statMap);
  227. }
  228. map.Add(catName, contMap);
  229. }
  230. return map;
  231. }
  232. public static Hashtable HandleStatsRequest(Hashtable request)
  233. {
  234. Hashtable responsedata = new Hashtable();
  235. // string regpath = request["uri"].ToString();
  236. int response_code = 200;
  237. string contenttype = "text/json";
  238. string pCategoryName = StatsManager.AllSubCommand;
  239. string pContainerName = StatsManager.AllSubCommand;
  240. string pStatName = StatsManager.AllSubCommand;
  241. if (request.ContainsKey("cat")) pCategoryName = request["cat"].ToString();
  242. if (request.ContainsKey("cont")) pContainerName = request["cat"].ToString();
  243. if (request.ContainsKey("stat")) pStatName = request["cat"].ToString();
  244. string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString();
  245. // If requestor wants it as a callback function, build response as a function rather than just the JSON string.
  246. if (request.ContainsKey("callback"))
  247. {
  248. strOut = request["callback"].ToString() + "(" + strOut + ");";
  249. }
  250. // m_log.DebugFormat("{0} StatFetch: uri={1}, cat={2}, cont={3}, stat={4}, resp={5}",
  251. // LogHeader, regpath, pCategoryName, pContainerName, pStatName, strOut);
  252. responsedata["int_response_code"] = response_code;
  253. responsedata["content_type"] = contenttype;
  254. responsedata["keepalive"] = false;
  255. responsedata["str_response_string"] = strOut;
  256. responsedata["access_control_allow_origin"] = "*";
  257. return responsedata;
  258. }
  259. // /// <summary>
  260. // /// Start collecting statistics related to assets.
  261. // /// Should only be called once.
  262. // /// </summary>
  263. // public static AssetStatsCollector StartCollectingAssetStats()
  264. // {
  265. // assetStats = new AssetStatsCollector();
  266. //
  267. // return assetStats;
  268. // }
  269. //
  270. // /// <summary>
  271. // /// Start collecting statistics related to users.
  272. // /// Should only be called once.
  273. // /// </summary>
  274. // public static UserStatsCollector StartCollectingUserStats()
  275. // {
  276. // userStats = new UserStatsCollector();
  277. //
  278. // return userStats;
  279. // }
  280. /// <summary>
  281. /// Register a statistic.
  282. /// </summary>
  283. /// <param name='stat'></param>
  284. /// <returns></returns>
  285. public static bool RegisterStat(Stat stat)
  286. {
  287. SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
  288. SortedDictionary<string, Stat> container = null, newContainer;
  289. lock (RegisteredStats)
  290. {
  291. // Stat name is not unique across category/container/shortname key.
  292. // XXX: For now just return false. This is to avoid problems in regression tests where all tests
  293. // in a class are run in the same instance of the VM.
  294. if (TryGetStatParents(stat, out category, out container))
  295. return false;
  296. // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
  297. // This means that we don't need to lock or copy them on iteration, which will be a much more
  298. // common operation after startup.
  299. if (container != null)
  300. newContainer = new SortedDictionary<string, Stat>(container);
  301. else
  302. newContainer = new SortedDictionary<string, Stat>();
  303. if (category != null)
  304. newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
  305. else
  306. newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>();
  307. newContainer[stat.ShortName] = stat;
  308. newCategory[stat.Container] = newContainer;
  309. RegisteredStats[stat.Category] = newCategory;
  310. }
  311. return true;
  312. }
  313. /// <summary>
  314. /// Deregister a statistic
  315. /// </summary>>
  316. /// <param name='stat'></param>
  317. /// <returns></returns>
  318. public static bool DeregisterStat(Stat stat)
  319. {
  320. SortedDictionary<string, SortedDictionary<string, Stat>> category = null, newCategory;
  321. SortedDictionary<string, Stat> container = null, newContainer;
  322. lock (RegisteredStats)
  323. {
  324. if (!TryGetStatParents(stat, out category, out container))
  325. return false;
  326. newContainer = new SortedDictionary<string, Stat>(container);
  327. newContainer.Remove(stat.ShortName);
  328. newCategory = new SortedDictionary<string, SortedDictionary<string, Stat>>(category);
  329. newCategory.Remove(stat.Container);
  330. newCategory[stat.Container] = newContainer;
  331. RegisteredStats[stat.Category] = newCategory;
  332. return true;
  333. }
  334. }
  335. public static bool TryGetStat(string category, string container, string statShortName, out Stat stat)
  336. {
  337. stat = null;
  338. SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
  339. lock (RegisteredStats)
  340. {
  341. if (!TryGetStatsForCategory(category, out categoryStats))
  342. return false;
  343. SortedDictionary<string, Stat> containerStats;
  344. if (!categoryStats.TryGetValue(container, out containerStats))
  345. return false;
  346. return containerStats.TryGetValue(statShortName, out stat);
  347. }
  348. }
  349. public static bool TryGetStatsForCategory(
  350. string category, out SortedDictionary<string, SortedDictionary<string, Stat>> stats)
  351. {
  352. lock (RegisteredStats)
  353. return RegisteredStats.TryGetValue(category, out stats);
  354. }
  355. /// <summary>
  356. /// Get the same stat for each container in a given category.
  357. /// </summary>
  358. /// <returns>
  359. /// The stats if there were any to fetch. Otherwise null.
  360. /// </returns>
  361. /// <param name='category'></param>
  362. /// <param name='statShortName'></param>
  363. public static List<Stat> GetStatsFromEachContainer(string category, string statShortName)
  364. {
  365. SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
  366. lock (RegisteredStats)
  367. {
  368. if (!RegisteredStats.TryGetValue(category, out categoryStats))
  369. return null;
  370. List<Stat> stats = null;
  371. foreach (SortedDictionary<string, Stat> containerStats in categoryStats.Values)
  372. {
  373. if (containerStats.ContainsKey(statShortName))
  374. {
  375. if (stats == null)
  376. stats = new List<Stat>();
  377. stats.Add(containerStats[statShortName]);
  378. }
  379. }
  380. return stats;
  381. }
  382. }
  383. public static bool TryGetStatParents(
  384. Stat stat,
  385. out SortedDictionary<string, SortedDictionary<string, Stat>> category,
  386. out SortedDictionary<string, Stat> container)
  387. {
  388. category = null;
  389. container = null;
  390. lock (RegisteredStats)
  391. {
  392. if (RegisteredStats.TryGetValue(stat.Category, out category))
  393. {
  394. if (category.TryGetValue(stat.Container, out container))
  395. {
  396. if (container.ContainsKey(stat.ShortName))
  397. return true;
  398. }
  399. }
  400. }
  401. return false;
  402. }
  403. public static void RecordStats()
  404. {
  405. lock (RegisteredStats)
  406. {
  407. foreach (SortedDictionary<string, SortedDictionary<string, Stat>> category in RegisteredStats.Values)
  408. {
  409. foreach (SortedDictionary<string, Stat> container in category.Values)
  410. {
  411. foreach (Stat stat in container.Values)
  412. {
  413. if (stat.MeasuresOfInterest != MeasuresOfInterest.None)
  414. stat.RecordValue();
  415. }
  416. }
  417. }
  418. }
  419. }
  420. }
  421. /// <summary>
  422. /// Stat type.
  423. /// </summary>
  424. /// <remarks>
  425. /// A push stat is one which is continually updated and so it's value can simply by read.
  426. /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
  427. /// </remarks>
  428. public enum StatType
  429. {
  430. Push,
  431. Pull
  432. }
  433. /// <summary>
  434. /// Measures of interest for this stat.
  435. /// </summary>
  436. [Flags]
  437. public enum MeasuresOfInterest
  438. {
  439. None,
  440. AverageChangeOverTime
  441. }
  442. /// <summary>
  443. /// Verbosity of stat.
  444. /// </summary>
  445. /// <remarks>
  446. /// Info will always be displayed.
  447. /// </remarks>
  448. public enum StatVerbosity
  449. {
  450. Debug,
  451. Info
  452. }
  453. }