OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
ServerStatsCollector.cs
Go to the documentation of this file.
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.Generic;
30 using System.Diagnostics;
31 using System.Linq;
32 using System.Net.NetworkInformation;
33 using System.Text;
34 using System.Threading;
35 using log4net;
36 using Nini.Config;
37 using OpenMetaverse.StructuredData;
38 using OpenSim.Framework;
39 
40 namespace OpenSim.Framework.Monitoring
41 {
42  public class ServerStatsCollector
43  {
44  private readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
45  private readonly string LogHeader = "[SERVER STATS]";
46 
47  public bool Enabled = false;
48  private static Dictionary<string, Stat> RegisteredStats = new Dictionary<string, Stat>();
49 
50  public readonly string CategoryServer = "server";
51 
52  public readonly string ContainerThreadpool = "threadpool";
53  public readonly string ContainerProcessor = "processor";
54  public readonly string ContainerMemory = "memory";
55  public readonly string ContainerNetwork = "network";
56  public readonly string ContainerProcess = "process";
57 
58  public string NetworkInterfaceTypes = "Ethernet";
59 
60  readonly int performanceCounterSampleInterval = 500;
61 // int lastperformanceCounterSampleTime = 0;
62 
63  private class PerfCounterControl
64  {
65  public PerformanceCounter perfCounter;
66  public int lastFetch;
67  public string name;
68  public PerfCounterControl(PerformanceCounter pPc)
69  : this(pPc, String.Empty)
70  {
71  }
72  public PerfCounterControl(PerformanceCounter pPc, string pName)
73  {
74  perfCounter = pPc;
75  lastFetch = 0;
76  name = pName;
77  }
78  }
79 
80  PerfCounterControl processorPercentPerfCounter = null;
81 
82  // IRegionModuleBase.Initialize
83  public void Initialise(IConfigSource source)
84  {
85  if (source == null)
86  return;
87 
88  IConfig cfg = source.Configs["Monitoring"];
89 
90  if (cfg != null)
91  Enabled = cfg.GetBoolean("ServerStatsEnabled", true);
92 
93  if (Enabled)
94  {
95  NetworkInterfaceTypes = cfg.GetString("NetworkInterfaceTypes", "Ethernet");
96  }
97  }
98 
99  public void Start()
100  {
101  if (RegisteredStats.Count == 0)
102  RegisterServerStats();
103  }
104 
105  public void Close()
106  {
107  if (RegisteredStats.Count > 0)
108  {
109  foreach (Stat stat in RegisteredStats.Values)
110  {
111  StatsManager.DeregisterStat(stat);
112  stat.Dispose();
113  }
114  RegisteredStats.Clear();
115  }
116  }
117 
118  private void MakeStat(string pName, string pDesc, string pUnit, string pContainer, Action<Stat> act)
119  {
120  MakeStat(pName, pDesc, pUnit, pContainer, act, MeasuresOfInterest.None);
121  }
122 
123  private void MakeStat(string pName, string pDesc, string pUnit, string pContainer, Action<Stat> act, MeasuresOfInterest moi)
124  {
125  string desc = pDesc;
126  if (desc == null)
127  desc = pName;
128  Stat stat = new Stat(pName, pName, desc, pUnit, CategoryServer, pContainer, StatType.Pull, moi, act, StatVerbosity.Debug);
129  StatsManager.RegisterStat(stat);
130  RegisteredStats.Add(pName, stat);
131  }
132 
133  public void RegisterServerStats()
134  {
135 // lastperformanceCounterSampleTime = Util.EnvironmentTickCount();
136  PerformanceCounter tempPC;
137  Stat tempStat;
138  string tempName;
139 
140  try
141  {
142  tempName = "CPUPercent";
143  tempPC = new PerformanceCounter("Processor", "% Processor Time", "_Total");
144  processorPercentPerfCounter = new PerfCounterControl(tempPC);
145  // A long time bug in mono is that CPU percent is reported as CPU percent idle. Windows reports CPU percent busy.
146  tempStat = new Stat(tempName, tempName, "", "percent", CategoryServer, ContainerProcessor,
147  StatType.Pull, (s) => { GetNextValue(s, processorPercentPerfCounter); },
148  StatVerbosity.Info);
149  StatsManager.RegisterStat(tempStat);
150  RegisteredStats.Add(tempName, tempStat);
151 
152  MakeStat("TotalProcessorTime", null, "sec", ContainerProcessor,
153  (s) => { s.Value = Math.Round(Process.GetCurrentProcess().TotalProcessorTime.TotalSeconds, 3); });
154 
155  MakeStat("UserProcessorTime", null, "sec", ContainerProcessor,
156  (s) => { s.Value = Math.Round(Process.GetCurrentProcess().UserProcessorTime.TotalSeconds, 3); });
157 
158  MakeStat("PrivilegedProcessorTime", null, "sec", ContainerProcessor,
159  (s) => { s.Value = Math.Round(Process.GetCurrentProcess().PrivilegedProcessorTime.TotalSeconds, 3); });
160 
161  MakeStat("Threads", null, "threads", ContainerProcessor,
162  (s) => { s.Value = Process.GetCurrentProcess().Threads.Count; });
163  }
164  catch (Exception e)
165  {
166  m_log.ErrorFormat("{0} Exception creating 'Process': {1}", LogHeader, e);
167  }
168 
169  MakeStat("BuiltinThreadpoolWorkerThreadsAvailable", null, "threads", ContainerThreadpool,
170  s =>
171  {
172  int workerThreads, iocpThreads;
173  ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
174  s.Value = workerThreads;
175  });
176 
177  MakeStat("BuiltinThreadpoolIOCPThreadsAvailable", null, "threads", ContainerThreadpool,
178  s =>
179  {
180  int workerThreads, iocpThreads;
181  ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
182  s.Value = iocpThreads;
183  });
184 
185  if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool && Util.GetSmartThreadPoolInfo() != null)
186  {
187  MakeStat("STPMaxThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MaxThreads);
188  MakeStat("STPMinThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MinThreads);
189  MakeStat("STPConcurrency", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().MaxConcurrentWorkItems);
190  MakeStat("STPActiveThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().ActiveThreads);
191  MakeStat("STPInUseThreads", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().InUseThreads);
192  MakeStat("STPWorkItemsWaiting", null, "threads", ContainerThreadpool, s => s.Value = Util.GetSmartThreadPoolInfo().WaitingCallbacks);
193  }
194 
195  MakeStat(
196  "HTTPRequestsMade",
197  "Number of outbound HTTP requests made",
198  "requests",
199  ContainerNetwork,
200  s => s.Value = WebUtil.RequestNumber,
201  MeasuresOfInterest.AverageChangeOverTime);
202 
203  try
204  {
205  List<string> okInterfaceTypes = new List<string>(NetworkInterfaceTypes.Split(','));
206 
207  IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces();
208  foreach (NetworkInterface nic in nics)
209  {
210  if (nic.OperationalStatus != OperationalStatus.Up)
211  continue;
212 
213  string nicInterfaceType = nic.NetworkInterfaceType.ToString();
214  if (!okInterfaceTypes.Contains(nicInterfaceType))
215  {
216  m_log.DebugFormat("{0} Not including stats for network interface '{1}' of type '{2}'.",
217  LogHeader, nic.Name, nicInterfaceType);
218  m_log.DebugFormat("{0} To include, add to comma separated list in [Monitoring]NetworkInterfaceTypes={1}",
219  LogHeader, NetworkInterfaceTypes);
220  continue;
221  }
222 
223  if (nic.Supports(NetworkInterfaceComponent.IPv4))
224  {
225  IPv4InterfaceStatistics nicStats = nic.GetIPv4Statistics();
226  if (nicStats != null)
227  {
228  MakeStat("BytesRcvd/" + nic.Name, nic.Name, "KB", ContainerNetwork,
229  (s) => { LookupNic(s, (ns) => { return ns.BytesReceived; }, 1024.0); });
230  MakeStat("BytesSent/" + nic.Name, nic.Name, "KB", ContainerNetwork,
231  (s) => { LookupNic(s, (ns) => { return ns.BytesSent; }, 1024.0); });
232  MakeStat("TotalBytes/" + nic.Name, nic.Name, "KB", ContainerNetwork,
233  (s) => { LookupNic(s, (ns) => { return ns.BytesSent + ns.BytesReceived; }, 1024.0); });
234  }
235  }
236  // TODO: add IPv6 (it may actually happen someday)
237  }
238  }
239  catch (Exception e)
240  {
241  m_log.ErrorFormat("{0} Exception creating 'Network Interface': {1}", LogHeader, e);
242  }
243 
244  MakeStat("ProcessMemory", null, "MB", ContainerMemory,
245  (s) => { s.Value = Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024d / 1024d, 3); });
246  MakeStat("HeapMemory", null, "MB", ContainerMemory,
247  (s) => { s.Value = Math.Round(GC.GetTotalMemory(false) / 1024d / 1024d, 3); });
248  MakeStat("LastHeapAllocationRate", null, "MB/sec", ContainerMemory,
249  (s) => { s.Value = Math.Round(MemoryWatchdog.LastHeapAllocationRate * 1000d / 1024d / 1024d, 3); });
250  MakeStat("AverageHeapAllocationRate", null, "MB/sec", ContainerMemory,
251  (s) => { s.Value = Math.Round(MemoryWatchdog.AverageHeapAllocationRate * 1000d / 1024d / 1024d, 3); });
252 
253  MakeStat("ProcessResident", null, "MB", ContainerProcess,
254  (s) =>
255  {
256  Process myprocess = Process.GetCurrentProcess();
257  myprocess.Refresh();
258  s.Value = Math.Round(Process.GetCurrentProcess().WorkingSet64 / 1024.0 / 1024.0);
259  });
260  MakeStat("ProcessPaged", null, "MB", ContainerProcess,
261  (s) =>
262  {
263  Process myprocess = Process.GetCurrentProcess();
264  myprocess.Refresh();
265  s.Value = Math.Round(Process.GetCurrentProcess().PagedMemorySize64 / 1024.0 / 1024.0);
266  });
267  MakeStat("ProcessVirtual", null, "MB", ContainerProcess,
268  (s) =>
269  {
270  Process myprocess = Process.GetCurrentProcess();
271  myprocess.Refresh();
272  s.Value = Math.Round(Process.GetCurrentProcess().VirtualMemorySize64 / 1024.0 / 1024.0);
273  });
274  MakeStat("PeakProcessResident", null, "MB", ContainerProcess,
275  (s) =>
276  {
277  Process myprocess = Process.GetCurrentProcess();
278  myprocess.Refresh();
279  s.Value = Math.Round(Process.GetCurrentProcess().PeakWorkingSet64 / 1024.0 / 1024.0);
280  });
281  MakeStat("PeakProcessPaged", null, "MB", ContainerProcess,
282  (s) =>
283  {
284  Process myprocess = Process.GetCurrentProcess();
285  myprocess.Refresh();
286  s.Value = Math.Round(Process.GetCurrentProcess().PeakPagedMemorySize64 / 1024.0 / 1024.0);
287  });
288  MakeStat("PeakProcessVirtual", null, "MB", ContainerProcess,
289  (s) =>
290  {
291  Process myprocess = Process.GetCurrentProcess();
292  myprocess.Refresh();
293  s.Value = Math.Round(Process.GetCurrentProcess().PeakVirtualMemorySize64 / 1024.0 / 1024.0);
294  });
295  }
296 
297  // Notes on performance counters:
298  // "How To Read Performance Counters": http://blogs.msdn.com/b/bclteam/archive/2006/06/02/618156.aspx
299  // "How to get the CPU Usage in C#": http://stackoverflow.com/questions/278071/how-to-get-the-cpu-usage-in-c
300  // "Mono Performance Counters": http://www.mono-project.com/Mono_Performance_Counters
301  private delegate double PerfCounterNextValue();
302 
303  private void GetNextValue(Stat stat, PerfCounterControl perfControl)
304  {
305  if (Util.EnvironmentTickCountSubtract(perfControl.lastFetch) > performanceCounterSampleInterval)
306  {
307  if (perfControl != null && perfControl.perfCounter != null)
308  {
309  try
310  {
311  stat.Value = Math.Round(perfControl.perfCounter.NextValue(), 3);
312  }
313  catch (Exception e)
314  {
315  m_log.ErrorFormat("{0} Exception on NextValue fetching {1}: {2}", LogHeader, stat.Name, e);
316  }
317 
318  perfControl.lastFetch = Util.EnvironmentTickCount();
319  }
320  }
321  }
322 
323  // Lookup the nic that goes with this stat and set the value by using a fetch action.
324  // Not sure about closure with delegates inside delegates.
325  private delegate double GetIPv4StatValue(IPv4InterfaceStatistics interfaceStat);
326  private void LookupNic(Stat stat, GetIPv4StatValue getter, double factor)
327  {
328  // Get the one nic that has the name of this stat
329  IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces().Where(
330  (network) => network.Name == stat.Description);
331  try
332  {
333  foreach (NetworkInterface nic in nics)
334  {
335  IPv4InterfaceStatistics intrStats = nic.GetIPv4Statistics();
336  if (intrStats != null)
337  {
338  double newVal = Math.Round(getter(intrStats) / factor, 3);
339  stat.Value = newVal;
340  }
341  break;
342  }
343  }
344  catch
345  {
346  // There are times interfaces go away so we just won't update the stat for this
347  m_log.ErrorFormat("{0} Exception fetching stat on interface '{1}'", LogHeader, stat.Description);
348  }
349  }
350  }
351 
353  {
355  string shortName,
356  string name,
357  string description,
358  string unitName,
359  string category,
360  string container
361  )
362  : base(
363  shortName,
364  name,
365  description,
366  unitName,
367  category,
368  container,
369  StatType.Push,
371  null,
373  {
374  }
375  public override string ToConsoleString()
376  {
377  StringBuilder sb = new StringBuilder();
378 
379  return sb.ToString();
380  }
381 
382  public override OSDMap ToOSDMap()
383  {
384  OSDMap ret = new OSDMap();
385 
386  return ret;
387  }
388  }
389 }
FireAndForgetMethod
The method used by Util.FireAndForget for asynchronously firing events
Definition: Util.cs:92
OpenMetaverse.StructuredData.OSDMap OSDMap
Holds individual statistic details
Definition: Stat.cs:41
ServerStatsAggregator(string shortName, string name, string description, string unitName, string category, string container)
StatVerbosity
Verbosity of stat.
System.Collections.IEnumerable IEnumerable
MeasuresOfInterest
Measures of interest for this stat.