StatsManager.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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.Generic;
  29. namespace OpenSim.Framework.Monitoring
  30. {
  31. /// <summary>
  32. /// Singleton used to provide access to statistics reporters
  33. /// </summary>
  34. public class StatsManager
  35. {
  36. // Subcommand used to list other stats.
  37. public const string AllSubCommand = "all";
  38. // Subcommand used to list other stats.
  39. public const string ListSubCommand = "list";
  40. // All subcommands
  41. public static HashSet<string> SubCommands = new HashSet<string> { AllSubCommand, ListSubCommand };
  42. /// <summary>
  43. /// Registered stats categorized by category/container/shortname
  44. /// </summary>
  45. /// <remarks>
  46. /// Do not add or remove directly from this dictionary.
  47. /// </remarks>
  48. public static Dictionary<string, Dictionary<string, Dictionary<string, Stat>>> RegisteredStats
  49. = new Dictionary<string, Dictionary<string, Dictionary<string, Stat>>>();
  50. private static AssetStatsCollector assetStats;
  51. private static UserStatsCollector userStats;
  52. private static SimExtraStatsCollector simExtraStats = new SimExtraStatsCollector();
  53. public static AssetStatsCollector AssetStats { get { return assetStats; } }
  54. public static UserStatsCollector UserStats { get { return userStats; } }
  55. public static SimExtraStatsCollector SimExtraStats { get { return simExtraStats; } }
  56. public static void RegisterConsoleCommands(ICommandConsole console)
  57. {
  58. console.Commands.AddCommand(
  59. "General",
  60. false,
  61. "show stats",
  62. "show stats [list|all|<category>]",
  63. "Show statistical information for this server",
  64. "If no final argument is specified then legacy statistics information is currently shown.\n"
  65. + "If list is specified then statistic categories are shown.\n"
  66. + "If all is specified then all registered statistics are shown.\n"
  67. + "If a category name is specified then only statistics from that category are shown.\n"
  68. + "THIS STATS FACILITY IS EXPERIMENTAL AND DOES NOT YET CONTAIN ALL STATS",
  69. HandleShowStatsCommand);
  70. }
  71. public static void HandleShowStatsCommand(string module, string[] cmd)
  72. {
  73. ICommandConsole con = MainConsole.Instance;
  74. if (cmd.Length > 2)
  75. {
  76. var categoryName = cmd[2];
  77. if (categoryName == AllSubCommand)
  78. {
  79. foreach (var category in RegisteredStats.Values)
  80. {
  81. OutputCategoryStatsToConsole(con, category);
  82. }
  83. }
  84. else if (categoryName == ListSubCommand)
  85. {
  86. con.Output("Statistic categories available are:");
  87. foreach (string category in RegisteredStats.Keys)
  88. con.OutputFormat(" {0}", category);
  89. }
  90. else
  91. {
  92. Dictionary<string, Dictionary<string, Stat>> category;
  93. if (!RegisteredStats.TryGetValue(categoryName, out category))
  94. {
  95. con.OutputFormat("No such category as {0}", categoryName);
  96. }
  97. else
  98. {
  99. OutputCategoryStatsToConsole(con, category);
  100. }
  101. }
  102. }
  103. else
  104. {
  105. // Legacy
  106. con.Output(SimExtraStats.Report());
  107. }
  108. }
  109. private static void OutputCategoryStatsToConsole(
  110. ICommandConsole con, Dictionary<string, Dictionary<string, Stat>> category)
  111. {
  112. foreach (var container in category.Values)
  113. {
  114. foreach (Stat stat in container.Values)
  115. {
  116. con.Output(stat.ToConsoleString());
  117. }
  118. }
  119. }
  120. /// <summary>
  121. /// Start collecting statistics related to assets.
  122. /// Should only be called once.
  123. /// </summary>
  124. public static AssetStatsCollector StartCollectingAssetStats()
  125. {
  126. assetStats = new AssetStatsCollector();
  127. return assetStats;
  128. }
  129. /// <summary>
  130. /// Start collecting statistics related to users.
  131. /// Should only be called once.
  132. /// </summary>
  133. public static UserStatsCollector StartCollectingUserStats()
  134. {
  135. userStats = new UserStatsCollector();
  136. return userStats;
  137. }
  138. /// <summary>
  139. /// Registers a statistic.
  140. /// </summary>
  141. /// <param name='stat'></param>
  142. /// <returns></returns>
  143. public static bool RegisterStat(Stat stat)
  144. {
  145. Dictionary<string, Dictionary<string, Stat>> category = null, newCategory;
  146. Dictionary<string, Stat> container = null, newContainer;
  147. lock (RegisteredStats)
  148. {
  149. // Stat name is not unique across category/container/shortname key.
  150. // XXX: For now just return false. This is to avoid problems in regression tests where all tests
  151. // in a class are run in the same instance of the VM.
  152. if (TryGetStat(stat, out category, out container))
  153. return false;
  154. // We take a copy-on-write approach here of replacing dictionaries when keys are added or removed.
  155. // This means that we don't need to lock or copy them on iteration, which will be a much more
  156. // common operation after startup.
  157. if (container != null)
  158. newContainer = new Dictionary<string, Stat>(container);
  159. else
  160. newContainer = new Dictionary<string, Stat>();
  161. if (category != null)
  162. newCategory = new Dictionary<string, Dictionary<string, Stat>>(category);
  163. else
  164. newCategory = new Dictionary<string, Dictionary<string, Stat>>();
  165. newContainer[stat.ShortName] = stat;
  166. newCategory[stat.Container] = newContainer;
  167. RegisteredStats[stat.Category] = newCategory;
  168. }
  169. return true;
  170. }
  171. /// <summary>
  172. /// Deregister a statistic
  173. /// </summary>>
  174. /// <param name='stat'></param>
  175. /// <returns></returns
  176. public static bool DeregisterStat(Stat stat)
  177. {
  178. Dictionary<string, Dictionary<string, Stat>> category = null, newCategory;
  179. Dictionary<string, Stat> container = null, newContainer;
  180. lock (RegisteredStats)
  181. {
  182. if (!TryGetStat(stat, out category, out container))
  183. return false;
  184. newContainer = new Dictionary<string, Stat>(container);
  185. newContainer.Remove(stat.ShortName);
  186. newCategory = new Dictionary<string, Dictionary<string, Stat>>(category);
  187. newCategory.Remove(stat.Container);
  188. newCategory[stat.Container] = newContainer;
  189. RegisteredStats[stat.Category] = newCategory;
  190. return true;
  191. }
  192. }
  193. public static bool TryGetStats(string category, out Dictionary<string, Dictionary<string, Stat>> stats)
  194. {
  195. return RegisteredStats.TryGetValue(category, out stats);
  196. }
  197. public static bool TryGetStat(
  198. Stat stat,
  199. out Dictionary<string, Dictionary<string, Stat>> category,
  200. out Dictionary<string, Stat> container)
  201. {
  202. category = null;
  203. container = null;
  204. lock (RegisteredStats)
  205. {
  206. if (RegisteredStats.TryGetValue(stat.Category, out category))
  207. {
  208. if (category.TryGetValue(stat.Container, out container))
  209. {
  210. if (container.ContainsKey(stat.ShortName))
  211. return true;
  212. }
  213. }
  214. }
  215. return false;
  216. }
  217. }
  218. /// <summary>
  219. /// Stat type.
  220. /// </summary>
  221. /// <remarks>
  222. /// A push stat is one which is continually updated and so it's value can simply by read.
  223. /// A pull stat is one where reading the value triggers a collection method - the stat is not continually updated.
  224. /// </remarks>
  225. public enum StatType
  226. {
  227. Push,
  228. Pull
  229. }
  230. /// <summary>
  231. /// Verbosity of stat.
  232. /// </summary>
  233. /// <remarks>
  234. /// Info will always be displayed.
  235. /// </remarks>
  236. public enum StatVerbosity
  237. {
  238. Debug,
  239. Info
  240. }
  241. /// <summary>
  242. /// Holds individual static details
  243. /// </summary>
  244. public class Stat
  245. {
  246. /// <summary>
  247. /// Category of this stat (e.g. cache, scene, etc).
  248. /// </summary>
  249. public string Category { get; private set; }
  250. /// <summary>
  251. /// Containing name for this stat.
  252. /// FIXME: In the case of a scene, this is currently the scene name (though this leaves
  253. /// us with a to-be-resolved problem of non-unique region names).
  254. /// </summary>
  255. /// <value>
  256. /// The container.
  257. /// </value>
  258. public string Container { get; private set; }
  259. public StatType StatType { get; private set; }
  260. /// <summary>
  261. /// Action used to update this stat when the value is requested if it's a pull type.
  262. /// </summary>
  263. public Action<Stat> PullAction { get; private set; }
  264. public StatVerbosity Verbosity { get; private set; }
  265. public string ShortName { get; private set; }
  266. public string Name { get; private set; }
  267. public string Description { get; private set; }
  268. public virtual string UnitName { get; private set; }
  269. public virtual double Value
  270. {
  271. get
  272. {
  273. // Asking for an update here means that the updater cannot access this value without infinite recursion.
  274. // XXX: A slightly messy but simple solution may be to flick a flag so we can tell if this is being
  275. // called by the pull action and just return the value.
  276. if (StatType == StatType.Pull)
  277. PullAction(this);
  278. return m_value;
  279. }
  280. set
  281. {
  282. m_value = value;
  283. }
  284. }
  285. private double m_value;
  286. /// <summary>
  287. /// Constructor
  288. /// </summary>
  289. /// <param name='shortName'>Short name for the stat. Must not contain spaces. e.g. "LongFrames"</param>
  290. /// <param name='name'>Human readable name for the stat. e.g. "Long frames"</param>
  291. /// <param name='description'>Description of stat</param>
  292. /// <param name='unitName'>
  293. /// Unit name for the stat. Should be preceeded by a space if the unit name isn't normally appeneded immediately to the value.
  294. /// e.g. " frames"
  295. /// </param>
  296. /// <param name='category'>Category under which this stat should appear, e.g. "scene". Do not capitalize.</param>
  297. /// <param name='container'>Entity to which this stat relates. e.g. scene name if this is a per scene stat.</param>
  298. /// <param name='type'>Push or pull</param>
  299. /// <param name='pullAction'>Pull stats need an action to update the stat on request. Push stats should set null here.</param>
  300. /// <param name='verbosity'>Verbosity of stat. Controls whether it will appear in short stat display or only full display.</param>
  301. public Stat(
  302. string shortName,
  303. string name,
  304. string description,
  305. string unitName,
  306. string category,
  307. string container,
  308. StatType type,
  309. Action<Stat> pullAction,
  310. StatVerbosity verbosity)
  311. {
  312. if (StatsManager.SubCommands.Contains(category))
  313. throw new Exception(
  314. string.Format("Stat cannot be in category '{0}' since this is reserved for a subcommand", category));
  315. ShortName = shortName;
  316. Name = name;
  317. Description = description;
  318. UnitName = unitName;
  319. Category = category;
  320. Container = container;
  321. StatType = type;
  322. if (StatType == StatType.Push && pullAction != null)
  323. throw new Exception("A push stat cannot have a pull action");
  324. else
  325. PullAction = pullAction;
  326. Verbosity = verbosity;
  327. }
  328. public virtual string ToConsoleString()
  329. {
  330. return string.Format(
  331. "{0}.{1}.{2} : {3}{4}", Category, Container, ShortName, Value, UnitName);
  332. }
  333. }
  334. public class PercentageStat : Stat
  335. {
  336. public int Antecedent { get; set; }
  337. public int Consequent { get; set; }
  338. public override double Value
  339. {
  340. get
  341. {
  342. int c = Consequent;
  343. // Avoid any chance of a multi-threaded divide-by-zero
  344. if (c == 0)
  345. return 0;
  346. return (double)Antecedent / c * 100;
  347. }
  348. set
  349. {
  350. throw new Exception("Cannot set value on a PercentageStat");
  351. }
  352. }
  353. public PercentageStat(
  354. string shortName,
  355. string name,
  356. string description,
  357. string category,
  358. string container,
  359. StatType type,
  360. Action<Stat> pullAction,
  361. StatVerbosity verbosity)
  362. : base(shortName, name, description, "%", category, container, type, pullAction, verbosity) {}
  363. public override string ToConsoleString()
  364. {
  365. return string.Format(
  366. "{0}.{1}.{2} : {3:0.##}{4} ({5}/{6})",
  367. Category, Container, ShortName, Value, UnitName, Antecedent, Consequent);
  368. }
  369. }
  370. }