StatsManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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. public static string StatsPassword { get; set; }
  46. // All subcommands
  47. public static HashSet<string> SubCommands = new HashSet<string> { AllSubCommand, ListSubCommand };
  48. /// <summary>
  49. /// Registered stats categorized by category/container/shortname
  50. /// </summary>
  51. /// <remarks>
  52. /// Do not add or remove directly from this dictionary.
  53. /// </remarks>
  54. public static SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>> RegisteredStats
  55. = new SortedDictionary<string, SortedDictionary<string, SortedDictionary<string, Stat>>>();
  56. // private static AssetStatsCollector assetStats;
  57. // private static UserStatsCollector userStats;
  58. // private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector();
  59. // public static AssetStatsCollector AssetStats { get { return assetStats; } }
  60. // public static UserStatsCollector UserStats { get { return userStats; } }
  61. public static SimExtraStatsCollector SimExtraStats { get; set; }
  62. public static void RegisterConsoleCommands(ICommandConsole console)
  63. {
  64. console.Commands.AddCommand(
  65. "General",
  66. false,
  67. "stats show",
  68. "stats show [list|all|(<category>[.<container>])+",
  69. "Show statistical information for this server",
  70. "If no final argument is specified then legacy statistics information is currently shown.\n"
  71. + "'list' argument will show statistic categories.\n"
  72. + "'all' will show all statistics.\n"
  73. + "A <category> name will show statistics from that category.\n"
  74. + "A <category>.<container> name will show statistics from that category in that container.\n"
  75. + "More than one name can be given separated by spaces.\n",
  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.Output(" {0}", category);
  106. }
  107. else
  108. {
  109. SortedDictionary<string, SortedDictionary<string, Stat>> category;
  110. if (!RegisteredStats.TryGetValue(categoryName, out category))
  111. {
  112. con.Output("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.Output(
  139. "No such stat {0} in {1}.{2}", statName, categoryName, containerName);
  140. }
  141. }
  142. }
  143. else
  144. {
  145. con.Output("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. lock (RegisteredStats)
  236. {
  237. foreach (string catName in RegisteredStats.Keys)
  238. {
  239. // Do this category if null spec, "all" subcommand or category name matches passed parameter.
  240. // Skip category if none of the above.
  241. if (!(String.IsNullOrEmpty(pCategoryName) || pCategoryName == AllSubCommand || pCategoryName == catName))
  242. continue;
  243. OSDMap contMap = new OSDMap();
  244. foreach (string contName in RegisteredStats[catName].Keys)
  245. {
  246. if (!(string.IsNullOrEmpty(pContainerName) || pContainerName == AllSubCommand || pContainerName == contName))
  247. continue;
  248. OSDMap statMap = new OSDMap();
  249. SortedDictionary<string, Stat> theStats = RegisteredStats[catName][contName];
  250. foreach (string statName in theStats.Keys)
  251. {
  252. if (!(String.IsNullOrEmpty(pStatName) || pStatName == AllSubCommand || pStatName == statName))
  253. continue;
  254. statMap.Add(statName, theStats[statName].ToBriefOSDMap());
  255. }
  256. contMap.Add(contName, statMap);
  257. }
  258. map.Add(catName, contMap);
  259. }
  260. }
  261. return map;
  262. }
  263. public static Hashtable HandleStatsRequest(Hashtable request)
  264. {
  265. Hashtable responsedata = new Hashtable();
  266. // string regpath = request["uri"].ToString();
  267. int response_code = 200;
  268. string contenttype = "text/json";
  269. if (StatsPassword != String.Empty && (!request.ContainsKey("pass") || request["pass"].ToString() != StatsPassword))
  270. {
  271. responsedata["int_response_code"] = response_code;
  272. responsedata["content_type"] = "text/plain";
  273. responsedata["keepalive"] = false;
  274. responsedata["str_response_string"] = "Access denied";
  275. responsedata["access_control_allow_origin"] = "*";
  276. return responsedata;
  277. }
  278. string pCategoryName = StatsManager.AllSubCommand;
  279. string pContainerName = StatsManager.AllSubCommand;
  280. string pStatName = StatsManager.AllSubCommand;
  281. if (request.ContainsKey("cat")) pCategoryName = request["cat"].ToString();
  282. if (request.ContainsKey("cont")) pContainerName = request["cat"].ToString();
  283. if (request.ContainsKey("stat")) pStatName = request["stat"].ToString();
  284. string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString();
  285. // If requestor wants it as a callback function, build response as a function rather than just the JSON string.
  286. if (request.ContainsKey("callback"))
  287. {
  288. strOut = request["callback"].ToString() + "(" + strOut + ");";
  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. responsedata["int_response_code"] = response_code;
  293. responsedata["content_type"] = contenttype;
  294. responsedata["keepalive"] = false;
  295. responsedata["str_response_string"] = strOut;
  296. responsedata["access_control_allow_origin"] = "*";
  297. return responsedata;
  298. }
  299. // /// <summary>
  300. // /// Start collecting statistics related to assets.
  301. // /// Should only be called once.
  302. // /// </summary>
  303. // public static AssetStatsCollector StartCollectingAssetStats()
  304. // {
  305. // assetStats = new AssetStatsCollector();
  306. //
  307. // return assetStats;
  308. // }
  309. //
  310. // /// <summary>
  311. // /// Start collecting statistics related to users.
  312. // /// Should only be called once.
  313. // /// </summary>
  314. // public static UserStatsCollector StartCollectingUserStats()
  315. // {
  316. // userStats = new UserStatsCollector();
  317. //
  318. // return userStats;
  319. // }
  320. /// <summary>
  321. /// Register a statistic.
  322. /// </summary>
  323. /// <param name='stat'></param>
  324. /// <returns></returns>
  325. public static bool RegisterStat(Stat stat)
  326. {
  327. SortedDictionary<string, SortedDictionary<string, Stat>> category = null;
  328. SortedDictionary<string, Stat> container = null;
  329. lock (RegisteredStats)
  330. {
  331. // Stat name is not unique across category/container/shortname key.
  332. // XXX: For now just return false. This is to avoid problems in regression tests where all tests
  333. // in a class are run in the same instance of the VM.
  334. if (TryGetStatParents(stat, out category, out container))
  335. return false;
  336. if (container == null)
  337. container = new SortedDictionary<string, Stat>();
  338. if (category == null)
  339. category = new SortedDictionary<string, SortedDictionary<string, Stat>>();
  340. container[stat.ShortName] = stat;
  341. category[stat.Container] = container;
  342. RegisteredStats[stat.Category] = category;
  343. }
  344. return true;
  345. }
  346. /// <summary>
  347. /// Deregister a statistic
  348. /// </summary>>
  349. /// <param name='stat'></param>
  350. /// <returns></returns>
  351. public static bool DeregisterStat(Stat stat)
  352. {
  353. SortedDictionary<string, SortedDictionary<string, Stat>> category = null;
  354. SortedDictionary<string, Stat> container = null;
  355. lock (RegisteredStats)
  356. {
  357. if (!TryGetStatParents(stat, out category, out container))
  358. return false;
  359. if(container != null)
  360. {
  361. container.Remove(stat.ShortName);
  362. if(category != null && container.Count == 0)
  363. {
  364. category.Remove(stat.Container);
  365. if(category.Count == 0)
  366. RegisteredStats.Remove(stat.Category);
  367. }
  368. }
  369. return true;
  370. }
  371. }
  372. public static bool TryGetStat(string category, string container, string statShortName, out Stat stat)
  373. {
  374. stat = null;
  375. SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
  376. lock (RegisteredStats)
  377. {
  378. if (!TryGetStatsForCategory(category, out categoryStats))
  379. return false;
  380. SortedDictionary<string, Stat> containerStats;
  381. if (!categoryStats.TryGetValue(container, out containerStats))
  382. return false;
  383. return containerStats.TryGetValue(statShortName, out stat);
  384. }
  385. }
  386. public static bool TryGetStatsForCategory(
  387. string category, out SortedDictionary<string, SortedDictionary<string, Stat>> stats)
  388. {
  389. lock (RegisteredStats)
  390. return RegisteredStats.TryGetValue(category, out stats);
  391. }
  392. /// <summary>
  393. /// Get the same stat for each container in a given category.
  394. /// </summary>
  395. /// <returns>
  396. /// The stats if there were any to fetch. Otherwise null.
  397. /// </returns>
  398. /// <param name='category'></param>
  399. /// <param name='statShortName'></param>
  400. public static List<Stat> GetStatsFromEachContainer(string category, string statShortName)
  401. {
  402. SortedDictionary<string, SortedDictionary<string, Stat>> categoryStats;
  403. lock (RegisteredStats)
  404. {
  405. if (!RegisteredStats.TryGetValue(category, out categoryStats))
  406. return null;
  407. List<Stat> stats = null;
  408. foreach (SortedDictionary<string, Stat> containerStats in categoryStats.Values)
  409. {
  410. if (containerStats.ContainsKey(statShortName))
  411. {
  412. if (stats == null)
  413. stats = new List<Stat>();
  414. stats.Add(containerStats[statShortName]);
  415. }
  416. }
  417. return stats;
  418. }
  419. }
  420. public static bool TryGetStatParents(
  421. Stat stat,
  422. out SortedDictionary<string, SortedDictionary<string, Stat>> category,
  423. out SortedDictionary<string, Stat> container)
  424. {
  425. category = null;
  426. container = null;
  427. lock (RegisteredStats)
  428. {
  429. if (RegisteredStats.TryGetValue(stat.Category, out category))
  430. {
  431. if (category.TryGetValue(stat.Container, out container))
  432. {
  433. if (container.ContainsKey(stat.ShortName))
  434. return true;
  435. }
  436. }
  437. }
  438. return false;
  439. }
  440. public static void RecordStats()
  441. {
  442. lock (RegisteredStats)
  443. {
  444. foreach (SortedDictionary<string, SortedDictionary<string, Stat>> category in RegisteredStats.Values)
  445. {
  446. foreach (SortedDictionary<string, Stat> container in category.Values)
  447. {
  448. foreach (Stat stat in container.Values)
  449. {
  450. if (stat.MeasuresOfInterest != MeasuresOfInterest.None)
  451. stat.RecordValue();
  452. }
  453. }
  454. }
  455. }
  456. }
  457. }
  458. /// <summary>
  459. /// Stat type.
  460. /// </summary>
  461. /// <remarks>
  462. /// A push stat is one which is continually updated and so it's value can simply by read.
  463. /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
  464. /// </remarks>
  465. public enum StatType
  466. {
  467. Push,
  468. Pull
  469. }
  470. /// <summary>
  471. /// Measures of interest for this stat.
  472. /// </summary>
  473. [Flags]
  474. public enum MeasuresOfInterest
  475. {
  476. None,
  477. AverageChangeOverTime
  478. }
  479. /// <summary>
  480. /// Verbosity of stat.
  481. /// </summary>
  482. /// <remarks>
  483. /// Info will always be displayed.
  484. /// </remarks>
  485. public enum StatVerbosity
  486. {
  487. Debug,
  488. Info
  489. }
  490. }