Fork me on GitHub

性能计数器数据收集服务

本文演示了一个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:  GO

services表 存储我们需要监控的服务的进程。 每个服务都需要监控一系列的性能计数器 (存储在 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');

然后启动我们的性能计数器收集服务

image

代码参见项目 https://github.com/geffzhang/PerformanceCounterCollect/

WMI Performance Counter Logging Console App

posted @ 2013-01-26 10:25  张善友  阅读(3759)  评论(3编辑  收藏  举报