本文演示了一个Windows服务收集性能计数器的数据,将性能计数器数据写入数据库。Windows服务中调用WebAPI服务中。下面简要介绍下我的改造,项目虽小,其中用到了众多的开源项目Topshelf、NLog、Dapper,ASP.NET Web API,Newtonsoft.Json等等:
1、数据库模型,以下是MS SQL Server的模型:
1: USE [PerfmonCounter] 2: GO 3: /****** Object: Table [dbo].[service_counter_snapshots] Script Date: 01/25/2013 22:40:20 ******/ 4: SET ANSI_NULLS ON 5: GO 6: SET QUOTED_IDENTIFIER ON 7: GO 8: SET ANSI_PADDING ON 9: GO 10: CREATE TABLE [dbo].[service_counter_snapshots]( 11: [Id] [int] IDENTITY(1,1) NOT NULL, 12: [ServiceCounterId] [int] NOT NULL, 13: [SnapshotMachineName] [varchar](100) NULL, 14: [CreationTimeUtc] [datetime] NOT NULL, 15: [ServiceCounterValue] [float] NULL, 16: PRIMARY KEY CLUSTERED 17: ( 18: [Id] ASC 19: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 20: ) ON [PRIMARY] 21: GO 22: SET ANSI_PADDING OFF 23: GO 24: /****** Object: Table [dbo].[services] Script Date: 01/25/2013 22:40:20 ******/ 25: SET ANSI_NULLS ON 26: GO 27: SET QUOTED_IDENTIFIER ON 28: GO 29: SET ANSI_PADDING ON 30: GO 31: CREATE TABLE [dbo].[services]( 32: [Name] [varchar](100) NOT NULL, 33: [DisplayName] [varchar](1000) NULL, 34: PRIMARY KEY CLUSTERED 35: ( 36: [Name] ASC 37: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 38: ) ON [PRIMARY] 39: GO 40: SET ANSI_PADDING OFF 41: GO 42: /****** Object: Table [dbo].[service_counters] Script Date: 01/25/2013 22:40:20 ******/ 43: SET ANSI_NULLS ON 44: GO 45: SET QUOTED_IDENTIFIER ON 46: GO 47: SET ANSI_PADDING ON 48: GO 49: CREATE TABLE [dbo].[service_counters]( 50: [Id] [int] IDENTITY(1,1) NOT NULL, 51: [ServiceName] [varchar](100) NOT NULL, 52: [MachineName] [varchar](100) NULL, 53: [CategoryName] [varchar](100) NOT NULL, 54: [CounterName] [varchar](100) NOT NULL, 55: [InstanceName] [varchar](100) NULL, 56: [DisplayName] [varchar](1000) NULL, 57: [DisplayType] [varchar](7) NOT NULL, 58: PRIMARY KEY CLUSTERED 59: ( 60: [Id] ASC 61: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 62: ) ON [PRIMARY] 63: GO 64: SET ANSI_PADDING OFF 65: GO 66: /****** Object: Default [DF__service_c__Displ__08EA5793] Script Date: 01/25/2013 22:40:20 ******/ 67: ALTER TABLE [dbo].[service_counters] ADD DEFAULT ('table') FOR [DisplayType] 68: GO 69: /****** Object: ForeignKey [FK__service_c__Servi__09DE7BCC] Script Date: 01/25/2013 22:40:20 ******/ 70: ALTER TABLE [dbo].[service_counters] WITH CHECK ADD FOREIGN KEY([ServiceName]) 71: REFERENCES [dbo].[services]([Name]) 72: GOservices表 存储我们需要监控的服务的进程。 每个服务都需要监控一系列的性能计数器 (存储在 service_counters 表)。数据收集服务在启动的时候根据service_counters 表创建 System.Diagnostics.PerformanceCounter class 的实例列表。 服务每隔一段时间收集一次性能计数器数据并把它存储到service_counter_snapshots 表。
所有的数据操作通过一个Restful服务接口DataCollectorController 进行操作。
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Net; 5: using System.Net.Http; 6: using System.Web.Http; 7: using PerformanceCounterCollect.Models; 8: using PerformanceCounterCollect.Web.Models; 9: 10: namespace PerformanceCounterCollect.Web.Controllers 11: { 12: public class DataCollectorController : ApiController 13: { 14: private ServiceCounterRepository scRepository; 15: private ServiceCounterSnapshotRepository scSnapshotReposity; 16: 17: public DataCollectorController() 18: { 19: scRepository = new ServiceCounterRepository(); 20: scSnapshotReposity = new ServiceCounterSnapshotRepository(); 21: } 22: 23: // GET api/values/5 24: [HttpGet] 25: public IEnumerable<ServiceCounter> SelectServiceCounter(string machineName) 26: { 27: return scRepository.SelectServiceCounter(machineName); 28: } 29: 30: // POST api/values 31: [HttpPost] 32: public IEnumerable<ServiceCounterSnapshot> SaveServiceSnapshots([FromBody]IEnumerable<ServiceCounterSnapshot> value) 33: { 34: return scSnapshotReposity.SaveServiceSnapshots(value); 35: } 36: } 37: 38: } 数据操作使用到了轻型的ORM类Dapper,使用了Repository模式。 1: using System; 2: using System.Data; 3: using System.Data.SqlClient; 4: using System.Linq; 5: using System.Web.Configuration; 6: using Dapper; 7: 8: namespace PerformanceCounterCollect.Web.Models 9: { 10: public abstract class BaseRepository 11: { 12: protected static void SetIdentity<T>(IDbConnection connection, Action<T> setId) 13: { 14: dynamic identity = connection.Query("SELECT @@IDENTITY AS Id").Single(); 15: T newId = (T)identity.Id; 16: setId(newId); 17: } 18: 19: protected static IDbConnection OpenConnection() 20: { 21: IDbConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["SqlDiagnosticsDb"].ConnectionString); 22: connection.Open(); 23: return connection; 24: } 25: } 26: } 1: using System; 2: using System.Collections.Generic; 3: using System.Data; 4: using System.Linq; 5: using System.Web; 6: using Dapper; 7: using PerformanceCounterCollect.Models; 8: 9: 10: namespace PerformanceCounterCollect.Web.Models 11: { 12: public class ServiceCounterRepository : BaseRepository 13: { 14: public IEnumerable<ServiceCounter> SelectServiceCounter(string machineName) 15: { 16: using (IDbConnection connection = OpenConnection()) 17: { 18: string query = "select Id,ServiceName,CategoryName,CounterName,InstanceName from service_counters where MachineName=@MachineName"; 19: return connection.Query<ServiceCounter>(query, new { MachineName = machineName }); 20: } 21: } 22: 23: 24: } 25: } 1: using System; 2: using System.Collections.Generic; 3: using System.Data; 4: using System.Linq; 5: using System.Web; 6: using Dapper; 7: using PerformanceCounterCollect.Models; 8: 9: namespace PerformanceCounterCollect.Web.Models 10: { 11: public class ServiceCounterSnapshotRepository: BaseRepository 12: { 13: public IEnumerable<ServiceCounterSnapshot> SaveServiceSnapshots(IEnumerable<ServiceCounterSnapshot> snapshots) 14: { 15: using (IDbConnection connection = OpenConnection()) 16: { 17: foreach (var snapshot in snapshots) 18: { 19: // insert new snapshot to the database 20: int retVal = connection.Execute( 21: @"insert into service_counter_snapshots(ServiceCounterId,SnapshotMachineName,CreationTimeUtc,ServiceCounterValue) values ( 22: @ServiceCounterId,@SnapshotMachineName,@CreationTimeUtc,@ServiceCounterValue)", snapshot); 23: SetIdentity<int>(connection, id => snapshot.Id = id); 24: } 25: } 26: return snapshots; 27: } 28: } 29: } 2、监控服务,也就是数据收集代理程序Monitoring Service: 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7: using Topshelf; 8: using Topshelf.Logging; 9: 10: namespace PerformanceCounterCollect.Services 11: { 12: class PerfmonWorker: ServiceControl 13: { 14: private readonly LogWriter logger = HostLogger.Get<PerfmonWorker>(); 15: public static bool ShouldStop { get; private set; } 16: private ManualResetEvent stopHandle; 17: 18: public bool Start(HostControl hostControl) 19: { 20: logger.Info("Starting PerfmonWorker..."); 21: 22: stopHandle = new ManualResetEvent(false); 23: 24: ThreadPool.QueueUserWorkItem(new ServiceMonitor().Monitor, stopHandle); 25: 26: return true; 27: } 28: 29: public bool Stop(HostControl hostControl) 30: { 31: ShouldStop = true; 32: logger.Info("Stopping PerfmonWorker..."); 33: // wait for all threads to finish 34: stopHandle.WaitOne(ServiceMonitor.SleepIntervalInMilliSecs + 10); 35: 36: return true; 37: } 38: } 39: 40: }服务使用了Topshelf和NLog,具体参看《使用Topshelf 5步创建Windows 服务》。在服务启动的时候开启监控线程,执行方法ServiceMonitor.Monitor:
1: using System; 2: using System.Collections.Generic; 3: using System.Diagnostics; 4: using System.Linq; 5: using System.Text; 6: using System.Threading; 7: using System.Threading.Tasks; 8: using PerformanceCounterCollect.Models; 9: using Topshelf.Logging; 10: 11: namespace PerformanceCounterCollect.Services 12: { 13: sealed class ServiceMonitor 14: { 15: public const int SleepIntervalInMilliSecs = 50000; 16: 17: private readonly LogWriter logger = HostLogger.Get<ServiceMonitor>(); 18: private IList<Tuple<int, PerformanceCounter>> serviceCounters; 19: 20: public void Monitor(object state) 21: { 22: ManualResetEvent stopHandle = (ManualResetEvent)state; 23: String machineName = Environment.MachineName; 24: try 25: { 26: Initialize(machineName); 27: var snapshots = new ServiceCounterSnapshot[serviceCounters.Count]; 28: 29: while (!PerfmonWorker.ShouldStop) 30: { 31: Thread.Sleep(SleepIntervalInMilliSecs); 32: 33: // this would be our timestamp value by which we will group the snapshots 34: DateTime timeStamp = DateTime.UtcNow; 35: // collect snapshots 36: for (int i = 0; i < serviceCounters.Count; i++) 37: { 38: var snapshot = new ServiceCounterSnapshot(); 39: snapshot.CreationTimeUtc = timeStamp; 40: snapshot.SnapshotMachineName = machineName; 41: snapshot.ServiceCounterId = serviceCounters[i].Item1; 42: try 43: { 44: snapshot.ServiceCounterValue = serviceCounters[i].Item2.NextValue(); 45: logger.DebugFormat("Performance counter {0} read value: {1}", GetPerfCounterPath(serviceCounters[i].Item2), 46: snapshot.ServiceCounterValue); 47: } 48: catch (InvalidOperationException) 49: { 50: snapshot.ServiceCounterValue = null; 51: logger.DebugFormat("Performance counter {0} didn't send any value.", GetPerfCounterPath(serviceCounters[i].Item2)); 52: } 53: snapshots[i] = snapshot; 54: } 55: SaveServiceSnapshots(snapshots); 56: } 57: } 58: finally 59: { 60: stopHandle.Set(); 61: } 62: } 63: 64: private void Initialize(String machineName) 65: { 66: try 67: { 68: var counters = new List<Tuple<int, PerformanceCounter>>(); 69: 70: foreach (var counter in PerfmonClient.SelectServiceCounter(machineName)) 71: { 72: logger.InfoFormat(@"Creating performance counter: {0}\{1}\{2}\{3}", counter.MachineName ?? ".", counter.CategoryName, 73: counter.CounterName, counter.InstanceName); 74: var perfCounter = new PerformanceCounter(counter.CategoryName, counter.CounterName, counter.InstanceName, counter.MachineName ?? "."); 75: counters.Add(new Tuple<int, PerformanceCounter>(counter.Id, perfCounter)); 76: // first value doesn't matter so we should call the counter at least once 77: try { perfCounter.NextValue(); } 78: catch { } 79: } 80: 81: 82: serviceCounters = counters; 83: } 84: catch (Exception ex) 85: { 86: logger.Error(ex); 87: } 88: } 89: 90: private void SaveServiceSnapshots(IEnumerable<ServiceCounterSnapshot> snapshots) 91: { 92: PerfmonClient.SaveServiceSnapshots(snapshots); 93: } 94: 95: private String GetPerfCounterPath(PerformanceCounter cnt) 96: { 97: return String.Format(@"{0}\{1}\{2}\{3}", cnt.MachineName, cnt.CategoryName, cnt.CounterName, cnt.InstanceName); 98: } 99: } 100: }在Monitor 方法中初始化完要采集的性能计数器实例后开启一个主循环,定期的收集数据,如果相关的性能计数器实例没有运行,计数器将会抛出InvalidOperationException 我们就把它设置为null。数据集的数据通过WebAPI发回服务器端存储,这样就可以实现性能计数器的集中存储了。
3、使用方法
使用很简单,首先定义我们要收集的数据
insert into services values ('notepad', 'notepad process test'); insert into service_counters values ( 'notepad', ‘GEFFZHANG-PC’, 'process', '% Processor Time', 'notepad', null, 'graph'); insert into service_counters values ( 'notepad', ‘GEFFZHANG-PC’, 'process', 'working set', 'notepad', null, 'graph');然后启动我们的性能计数器收集服务
本文来自云栖社区合作伙伴“doNET跨平台”,了解相关信息可以关注“opendotnet”微信公众号