aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Monitoring/ServerStatsCollector.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/Monitoring/ServerStatsCollector.cs')
-rw-r--r--OpenSim/Framework/Monitoring/ServerStatsCollector.cs346
1 files changed, 346 insertions, 0 deletions
diff --git a/OpenSim/Framework/Monitoring/ServerStatsCollector.cs b/OpenSim/Framework/Monitoring/ServerStatsCollector.cs
new file mode 100644
index 0000000..77315bb
--- /dev/null
+++ b/OpenSim/Framework/Monitoring/ServerStatsCollector.cs
@@ -0,0 +1,346 @@
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
28using System;
29using System.Collections.Generic;
30using System.Diagnostics;
31using System.Linq;
32using System.Net.NetworkInformation;
33using System.Text;
34using System.Threading;
35using log4net;
36using Nini.Config;
37using OpenMetaverse.StructuredData;
38using OpenSim.Framework;
39
40namespace 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
254 // Notes on performance counters:
255 // "How To Read Performance Counters": http://blogs.msdn.com/b/bclteam/archive/2006/06/02/618156.aspx
256 // "How to get the CPU Usage in C#": http://stackoverflow.com/questions/278071/how-to-get-the-cpu-usage-in-c
257 // "Mono Performance Counters": http://www.mono-project.com/Mono_Performance_Counters
258 private delegate double PerfCounterNextValue();
259
260 private void GetNextValue(Stat stat, PerfCounterControl perfControl)
261 {
262 if (Util.EnvironmentTickCountSubtract(perfControl.lastFetch) > performanceCounterSampleInterval)
263 {
264 if (perfControl != null && perfControl.perfCounter != null)
265 {
266 try
267 {
268 stat.Value = Math.Round(perfControl.perfCounter.NextValue(), 3);
269 }
270 catch (Exception e)
271 {
272 m_log.ErrorFormat("{0} Exception on NextValue fetching {1}: {2}", LogHeader, stat.Name, e);
273 }
274
275 perfControl.lastFetch = Util.EnvironmentTickCount();
276 }
277 }
278 }
279
280 // Lookup the nic that goes with this stat and set the value by using a fetch action.
281 // Not sure about closure with delegates inside delegates.
282 private delegate double GetIPv4StatValue(IPv4InterfaceStatistics interfaceStat);
283 private void LookupNic(Stat stat, GetIPv4StatValue getter, double factor)
284 {
285 // Get the one nic that has the name of this stat
286 IEnumerable<NetworkInterface> nics = NetworkInterface.GetAllNetworkInterfaces().Where(
287 (network) => network.Name == stat.Description);
288 try
289 {
290 foreach (NetworkInterface nic in nics)
291 {
292 IPv4InterfaceStatistics intrStats = nic.GetIPv4Statistics();
293 if (intrStats != null)
294 {
295 double newVal = Math.Round(getter(intrStats) / factor, 3);
296 stat.Value = newVal;
297 }
298 break;
299 }
300 }
301 catch
302 {
303 // There are times interfaces go away so we just won't update the stat for this
304 m_log.ErrorFormat("{0} Exception fetching stat on interface '{1}'", LogHeader, stat.Description);
305 }
306 }
307 }
308
309 public class ServerStatsAggregator : Stat
310 {
311 public ServerStatsAggregator(
312 string shortName,
313 string name,
314 string description,
315 string unitName,
316 string category,
317 string container
318 )
319 : base(
320 shortName,
321 name,
322 description,
323 unitName,
324 category,
325 container,
326 StatType.Push,
327 MeasuresOfInterest.None,
328 null,
329 StatVerbosity.Info)
330 {
331 }
332 public override string ToConsoleString()
333 {
334 StringBuilder sb = new StringBuilder();
335
336 return sb.ToString();
337 }
338
339 public override OSDMap ToOSDMap()
340 {
341 OSDMap ret = new OSDMap();
342
343 return ret;
344 }
345 }
346}