StatsManager.cs 21 KB

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