Permalink
Browse files

Add support for monitoring Win32_Services via the WMI provider. (#293)

* Add support for monitoring Win32_Services via the WMI provider.
* Made the support for Services more generic by dropping the Win32/Windows references and also switching the service filtering parameter from WQL to regex.  Also other minor cleanup.
  • Loading branch information...
KennethScott authored and NickCraver committed Nov 19, 2017
1 parent 09cbb38 commit 7406b634cb8307b9906e9e1c510a7666493023a4
@@ -3,6 +3,7 @@
using System.Linq;
using StackExchange.Opserver.Data.Dashboard.Providers;
using System.Globalization;
using System.Text.RegularExpressions;
namespace StackExchange.Opserver.Data.Dashboard
{
@@ -226,10 +227,11 @@ public string ApplicationMemoryTextSummary
public TimeSpan? PollInterval => PollIntervalSeconds.HasValue ? TimeSpan.FromSeconds(PollIntervalSeconds.Value) : (TimeSpan?) null;
// Interfaces, Volumes and Applications are set by the provider
// Interfaces, Volumes, Applications, and Services are set by the provider
public List<Interface> Interfaces { get; internal set; }
public List<Volume> Volumes { get; internal set; }
public List<Application> Apps { get; internal set; }
public List<NodeService> Services { get; internal set; }
public Interface GetInterface(string id)
{
@@ -258,6 +260,15 @@ public Application GetApp(string id)
return null;
}
public NodeService GetService(string id)
{
foreach (var s in Services)
{
if (s.Id == id) return s;
}
return null;
}
private static readonly List<IPNet> EmptyIPs = new List<IPNet>();
public List<IPNet> IPs => Interfaces?.SelectMany(i => i.IPs).ToList() ?? EmptyIPs;
@@ -272,12 +283,14 @@ public Application GetApp(string id)
public DashboardSettings.NodeSettings Settings => _settings ?? (_settings = Current.Settings.Dashboard.GetNodeSettings(PrettyName));
private decimal? GetSetting(Func<INodeSettings, decimal?> func) => func(Settings) ?? func(Category?.Settings) ?? func(Current.Settings.Dashboard);
private Regex GetSetting(Func<INodeSettings, Regex> func) => func(Settings) ?? func(Category?.Settings) ?? func(Current.Settings.Dashboard);
public decimal? CPUWarningPercent => GetSetting(i => i.CPUWarningPercent);
public decimal? CPUCriticalPercent => GetSetting(i => i.CPUCriticalPercent);
public decimal? MemoryWarningPercent => GetSetting(i => i.MemoryCriticalPercent);
public decimal? MemoryCriticalPercent => GetSetting(i => i.MemoryCriticalPercent);
public decimal? DiskWarningPercent => GetSetting(i => i.DiskWarningPercent);
public decimal? DiskCriticalPercent => GetSetting(i => i.DiskCriticalPercent);
public Regex ServicesPatternRegEx => GetSetting(i => i.ServicesPatternRegEx);
private List<Interface> _primaryInterfaces;
public List<Interface> PrimaryInterfaces
@@ -305,6 +318,7 @@ public void SetReferences()
Interfaces?.ForEach(i => i.Node = this);
Volumes?.ForEach(v => v.Node = this);
Apps?.ForEach(a => a.Node = this);
Services?.ForEach(s => s.Node = this);
}
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Text.RegularExpressions;
namespace StackExchange.Opserver.Data.Dashboard
{
/// <summary>
/// Service class
/// </summary>
public partial class NodeService : IMonitorStatus
{
public Node Node { get; set; }
public string Id { get; internal set; }
public DateTime? LastSync { get; internal set; }
public string Name { get; internal set; }
public string Caption { get; internal set; }
public string Description { get; internal set; }
public NodeStatus Status { get; internal set; }
public string DisplayName { get; internal set; }
public bool Running { get; internal set; }
public string StartMode { get; internal set; }
public string StartName { get; internal set; }
public string State { get; internal set; }
public MonitorStatus MonitorStatus => Status.ToMonitorStatus();
// TODO: Implement
public string MonitorStatusReason => null;
}
}
@@ -55,6 +55,7 @@ public WmiNode(string endpoint)
Caches = new List<Cache>(2);
Interfaces = new List<Interface>(2);
Volumes = new List<Volume>(3);
Services = new List<NodeService>();
VMs = new List<Node>();
Apps = new List<Application>();
@@ -32,7 +32,7 @@ public async Task<Node> PollNodeInfoAsync()
{
try
{
var tasks = new[] { UpdateNodeDataAsync(), GetAllInterfacesAsync(), GetAllVolumesAsync() };
var tasks = new[] { UpdateNodeDataAsync(), GetAllInterfacesAsync(), GetAllVolumesAsync(), GetServicesAsync() };
await Task.WhenAll(tasks).ConfigureAwait(false);
SetReferences();
ClearSummaries();
@@ -310,6 +310,61 @@ FROM Win32_LogicalDisk
}
}
private async Task GetServicesAsync()
{
string query = @"
SELECT Caption,
Description,
DisplayName,
Name,
Started,
StartMode,
StartName,
State
FROM Win32_Service"; // windows services
using (var q = Wmi.Query(Endpoint, query))
{
foreach (var service in await q.GetDynamicResultAsync().ConfigureAwait(false))
{
if (ServicesPatternRegEx?.IsMatch(service.Name) ?? true)
{
var id = service.Name;
var s = Services.Find(x => x.Id == id) ?? new NodeService();
s.Id = id;
s.Caption = service.Caption;
s.DisplayName = service.DisplayName;
s.Description = service.Description;
s.Name = service.Name;
s.State = service.State;
s.LastSync = DateTime.UtcNow;
switch (service.State)
{
case "Running":
s.Status = NodeStatus.Active;
break;
case "Stopped":
s.Status = NodeStatus.Down;
break;
default:
s.Status = NodeStatus.Unknown;
break;
}
s.Running = service.Started;
s.StartMode = service.StartMode;
s.StartName = service.StartName;
if (s.Node == null)
{
s.Node = this;
Services.Add(s);
}
}
}
}
}
private async Task PollCpuUtilizationAsync()
{
var query = IsVMHost
@@ -527,4 +582,4 @@ private async Task<bool> GetCanQueryAdapterUtilization()
#endregion
}
}
}
}
@@ -92,6 +92,7 @@
<Compile Include="Data\Dashboard\Providers\WmiDataProvider.Polling.cs">
<DependentUpon>WmiDataProvider.cs</DependentUpon>
</Compile>
<Compile Include="Data\Dashboard\NodeService.cs" />
<Compile Include="Data\DataProvider.cs" />
<Compile Include="Data\Elastic\ElasticCluster.Actions.cs">
<DependentUpon>ElasticCluster.cs</DependentUpon>
@@ -61,6 +61,22 @@ public class DashboardSettings : Settings<DashboardSettings>, INodeSettings
/// </summary>
public bool ShowVolumePerformance { get; set; }
/// <summary>
/// The Pattern to match on node services, all services matching this pattern will be shown on the dashboard.
/// </summary>
public string ServicesPattern { get; set; }
private Regex _servicesPatternRegEx;
public Regex ServicesPatternRegEx
{
get { return _servicesPatternRegEx ?? (_servicesPatternRegEx = GetPatternMatcher(ServicesPattern)); }
set { _servicesPatternRegEx = value; }
}
protected Regex GetPatternMatcher(string pattern)
{
return pattern.IsNullOrEmpty() ? null : new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
}
#endregion
/// <summary>
@@ -127,6 +143,18 @@ protected Regex GetPatternMatcher(string pattern)
/// Percent at which disk utilization on a node is marked at a critical level
/// </summary>
public decimal? DiskCriticalPercent { get; set; }
/// <summary>
/// The Pattern to match on node services, all services matching this pattern will be shown on the dashboard.
/// </summary>
public string ServicesPattern { get; set; }
private Regex _servicesPatternRegEx;
public Regex ServicesPatternRegEx
{
get { return _servicesPatternRegEx ?? (_servicesPatternRegEx = GetPatternMatcher(ServicesPattern)); }
set { _servicesPatternRegEx = value; }
}
}
/// <summary>
@@ -191,6 +219,18 @@ protected Regex GetPatternMatcher(string pattern)
/// Percent at which disk utilization on a node is marked at a critical level
/// </summary>
public decimal? DiskCriticalPercent { get; set; }
/// <summary>
/// The Pattern to match on node services, all services matching this pattern will be shown on the dashboard.
/// </summary>
public string ServicesPattern { get; set; }
private Regex _servicesPatternRegEx;
public Regex ServicesPatternRegEx
{
get { return _servicesPatternRegEx ?? (_servicesPatternRegEx = GetPatternMatcher(ServicesPattern)); }
set { _servicesPatternRegEx = value; }
}
}
}
@@ -203,5 +243,6 @@ public interface INodeSettings
decimal? MemoryCriticalPercent { get; set; }
decimal? DiskWarningPercent { get; set; }
decimal? DiskCriticalPercent { get; set; }
Regex ServicesPatternRegEx { get; set; }
}
}
View
@@ -319,6 +319,9 @@
<Content Include="Views\Dashboard\Node.Network.cshtml">
<DependentUpon>Node.cshtml</DependentUpon>
</Content>
<Content Include="Views\Dashboard\Node.Services.cshtml">
<DependentUpon>Node.cshtml</DependentUpon>
</Content>
<Content Include="Views\Dashboard\Node.Stats.cshtml">
<DependentUpon>Node.cshtml</DependentUpon>
</Content>
@@ -0,0 +1,32 @@
@using StackExchange.Opserver.Data.Dashboard
@using StackExchange.Opserver.Models
@model Node
<h5 class="page-header">@Model.Services.Count.Pluralize("Service")</h5>
<table class="table table-striped table-middle table-super-condensed table-responsive table-dashboard text-nowrap">
<thead>
<tr>
<th>Service</th>
<th>Description</th>
<th>State</th>
<th>Startup Type</th>
<th>Log On As</th>
</tr>
</thead>
<tbody>
@foreach (var i in Model.Services.OrderBy(i => i.DisplayName))
{
<tr class="@i.RowClass()">
<td title="@i.Caption
Last Updated @(i.LastSync?.ToRelativeTime() ?? "Unknown")" data-val="@(i.Id)">
@i.IconSpan()
@i.DisplayName
</td>
<td class="text-wrap">@i.Description</td>
<td>@i.State</td>
<td>@i.StartMode</td>
<td>@i.StartName</td>
</tr>
}
</tbody>
</table>
@@ -30,6 +30,11 @@
@Html.Partial("Node.Interfaces", Model)
}
@if (Model.Services.Any())
{
@Html.Partial("Node.Services", Model)
}
@if (Current.Settings.Dashboard.ShowVolumePerformance && Model.Volumes.Any())
{
@Html.Partial("Node.Volumes", Model)

0 comments on commit 7406b63

Please sign in to comment.