本文演示了一个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');
然后启动我们的性能计数器收集服务
代码参见项目 https://github.com/geffzhang/PerformanceCounterCollect/