Add BizTalk Checkmk Pulse local check

This commit is contained in:
2026-07-22 11:34:28 +02:00
commit 7660dc4555
20 changed files with 1919 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!-- Empty or "." means local BizTalk machine. -->
<add key="Server" value="." />
<add key="ServicePrefix" value="BizTalk" />
<add key="EnvironmentName" value="" />
<add key="QueryTimeoutSeconds" value="25" />
<add key="WarnResumableThreshold" value="1" />
<add key="CritNonResumableThreshold" value="1" />
<add key="MaxSummaryItems" value="12" />
<!-- In many BizTalk landscapes stopped artifacts can be intentional. -->
<add key="AlertOnArtifactRuntimeIssues" value="false" />
<add key="EmitPerApplicationSuspensionServices" value="false" />
<add key="ProbeEventLog" value="true" />
<add key="EventLogLookbackMinutes" value="60" />
<add key="EventLogWarnThreshold" value="1" />
<add key="EventLogCritThreshold" value="10" />
<add key="EventLogSources" value="BizTalk Server|XLANG/s|ENTSSO|BizTalk Server Application|BizTalk Server EDI" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition="false" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkCheckmkPulse</RootNamespace>
<AssemblyName>BizTalkCheckmkPulse</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
</ItemGroup>
<ItemGroup>
<Compile Include="CheckmkLocalFormatter.cs" />
<Compile Include="EventLogProbe.cs" />
<Compile Include="MonitoringOptions.cs" />
<Compile Include="Models.cs" />
<Compile Include="Program.cs" />
<Compile Include="WmiBizTalkProbe.cs" />
<Compile Include="WmiHelpers.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,355 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace BizTalkCheckmkPulse
{
internal sealed class CheckmkLocalFormatter
{
private const int HostStopped = 1;
private const int HostStartPending = 2;
private const int HostStopPending = 3;
private const int HostStarted = 4;
private const int HostContinuePending = 5;
private const int HostPausePending = 6;
private const int HostPaused = 7;
private const int HostUnknown = 8;
private readonly MonitoringOptions _options;
public CheckmkLocalFormatter(MonitoringOptions options)
{
_options = options;
}
public IEnumerable<string> Format(ProbeResult result)
{
yield return FormatPlatform(result);
yield return FormatSuspendedInstances(result);
yield return FormatHostInstances(result);
yield return FormatRuntimeArtifacts(result);
yield return FormatEventLog(result);
if (_options.EmitPerApplicationSuspensionServices)
{
foreach (var line in FormatApplicationSuspensions(result))
{
yield return line;
}
}
}
public IEnumerable<string> FormatSelfTest()
{
yield return BuildLine(CheckState.Ok, _options.ServiceName("Platform"), "-", "Self test OK. Checkmk local check output is valid.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Suspended Instances"), "biztalk_suspended_total=0;;;0", "Self test OK.");
}
public IEnumerable<string> FormatFatal(string message)
{
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", message);
}
private string FormatPlatform(ProbeResult result)
{
var state = result.Platform.WmiConnected ? CheckState.Ok : CheckState.Unknown;
var detail = new StringBuilder();
detail.Append(result.Platform.WmiConnected ? "BizTalk WMI reachable" : "BizTalk WMI not reachable");
detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName));
AppendOptional(detail, "group", result.Platform.GroupName);
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), result.Diagnostics));
}
private string FormatSuspendedInstances(ProbeResult result)
{
var total = result.SuspendedInstances.Count;
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable);
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
total,
resumable,
_options.WarnResumableThreshold,
nonresumable,
_options.CritNonResumableThreshold);
var detail = total == 0
? "No suspended BizTalk service instances found."
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable);
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
}
private string FormatHostInstances(ProbeResult result)
{
var total = result.HostInstances.Count;
var started = result.HostInstances.Count(x => x.ServiceState == HostStarted);
var stopped = result.HostInstances.Count(x => x.ServiceState == HostStopped || x.ServiceState == HostPaused);
var pending = result.HostInstances.Count(x => x.ServiceState == HostStartPending || x.ServiceState == HostStopPending || x.ServiceState == HostContinuePending || x.ServiceState == HostPausePending);
var unknown = total - started - stopped - pending;
var state = total == 0 ? CheckState.Unknown : stopped > 0 || unknown > 0 ? CheckState.Critical : pending > 0 ? CheckState.Warning : CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_host_instances_total={0};;;0|biztalk_host_instances_started={1};;;0|biztalk_host_instances_stopped={2};;1;0|biztalk_host_instances_pending={3};1;;0|biztalk_host_instances_unknown={4};;1;0",
total,
started,
stopped,
pending,
unknown);
var detail = new StringBuilder();
detail.Append("Host instances total=").Append(total)
.Append(", started=").Append(started)
.Append(", stopped=").Append(stopped)
.Append(", pending=").Append(pending)
.Append(", unknown=").Append(unknown);
var affected = result.HostInstances
.Where(x => x.ServiceState != HostStarted)
.Take(_options.MaxSummaryItems)
.Select(x => EmptyAsUnknown(x.HostName) + "@" + EmptyAsUnknown(x.RunningServer) + "=" + HostStateName(x.ServiceState));
AppendList(detail, "affected", affected);
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
}
private string FormatRuntimeArtifacts(ProbeResult result)
{
var receiveLocations = result.Applications.Sum(x => x.ReceiveLocationTotal);
var receiveLocationsDisabled = result.Applications.Sum(x => x.ReceiveLocationDisabled);
var sendPorts = result.Applications.Sum(x => x.SendPortTotal);
var sendPortsStarted = result.Applications.Sum(x => x.SendPortStarted);
var sendPortsInactive = result.Applications.Sum(x => x.SendPortStopped + x.SendPortBound);
var sendPortsUnknown = result.Applications.Sum(x => x.SendPortUnknown);
var orchestrations = result.Applications.Sum(x => x.OrchestrationTotal);
var orchestrationsStarted = result.Applications.Sum(x => x.OrchestrationStarted);
var orchestrationsInactive = result.Applications.Sum(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound);
var orchestrationsUnknown = result.Applications.Sum(x => x.OrchestrationUnknown);
var state = sendPortsUnknown > 0 || orchestrationsUnknown > 0
? CheckState.Critical
: _options.AlertOnArtifactRuntimeIssues && (receiveLocationsDisabled > 0 || sendPortsInactive > 0 || orchestrationsInactive > 0)
? CheckState.Warning
: CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_applications={0};;;0|biztalk_receive_locations={1};;;0|biztalk_receive_locations_disabled={2};1;;0|biztalk_send_ports={3};;;0|biztalk_send_ports_started={4};;;0|biztalk_send_ports_inactive={5};1;;0|biztalk_send_ports_unknown={6};;1;0|biztalk_orchestrations={7};;;0|biztalk_orchestrations_started={8};;;0|biztalk_orchestrations_inactive={9};1;;0|biztalk_orchestrations_unknown={10};;1;0",
result.Applications.Count,
receiveLocations,
receiveLocationsDisabled,
sendPorts,
sendPortsStarted,
sendPortsInactive,
sendPortsUnknown,
orchestrations,
orchestrationsStarted,
orchestrationsInactive,
orchestrationsUnknown);
var detail = new StringBuilder();
detail.Append("Applications=").Append(result.Applications.Count)
.Append("; receive_locations total=").Append(receiveLocations).Append(", disabled=").Append(receiveLocationsDisabled)
.Append("; send_ports total=").Append(sendPorts).Append(", started=").Append(sendPortsStarted).Append(", inactive=").Append(sendPortsInactive).Append(", unknown=").Append(sendPortsUnknown)
.Append("; orchestrations total=").Append(orchestrations).Append(", started=").Append(orchestrationsStarted).Append(", inactive=").Append(orchestrationsInactive).Append(", unknown=").Append(orchestrationsUnknown);
var affected = result.Applications
.Where(x => x.ReceiveLocationDisabled > 0 || x.SendPortStopped + x.SendPortBound + x.SendPortUnknown > 0 || x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0)
.Take(_options.MaxSummaryItems)
.Select(x => x.ApplicationName + "(rl_disabled=" + x.ReceiveLocationDisabled + ", sp_inactive=" + (x.SendPortStopped + x.SendPortBound) + ", orch_inactive=" + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound) + ")");
AppendList(detail, "notable_apps", affected);
return BuildLine(state, _options.ServiceName("Runtime Artifacts"), metrics, detail.ToString());
}
private string FormatEventLog(ProbeResult result)
{
if (!_options.ProbeEventLog)
{
return BuildLine(CheckState.Ok, _options.ServiceName("Event Log"), "-", "Event log probe disabled.");
}
if (!result.EventLog.Available)
{
return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", "Application event log could not be read: " + result.EventLog.Failure);
}
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
? CheckState.Critical
: _options.EventLogWarnThreshold > 0 && (result.EventLog.Errors >= _options.EventLogWarnThreshold || result.EventLog.Warnings >= _options.EventLogWarnThreshold)
? CheckState.Warning
: CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_eventlog_errors={0};{1};{2};0|biztalk_eventlog_warnings={3};{1};;0",
result.EventLog.Errors,
_options.EventLogWarnThreshold,
_options.EventLogCritThreshold,
result.EventLog.Warnings);
var detail = string.Format(
CultureInfo.InvariantCulture,
"BizTalk-related Application log entries in last {0} minutes: errors={1}, warnings={2}",
_options.EventLogLookbackMinutes,
result.EventLog.Errors,
result.EventLog.Warnings);
return BuildLine(state, _options.ServiceName("Event Log"), metrics, detail);
}
private IEnumerable<string> FormatApplicationSuspensions(ProbeResult result)
{
foreach (var group in result.SuspendedInstances.GroupBy(x => x.ApplicationName).OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
{
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable);
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
group.Count(),
resumable,
_options.WarnResumableThreshold,
nonresumable,
_options.CritNonResumableThreshold);
yield return BuildLine(state, _options.ServiceName("Suspended " + group.Key), metrics, "Suspended instances: resumable=" + resumable + ", nonresumable=" + nonresumable);
}
}
private CheckState DetermineSuspensionState(int resumable, int nonresumable)
{
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
{
return CheckState.Critical;
}
if (_options.WarnResumableThreshold > 0 && resumable >= _options.WarnResumableThreshold)
{
return CheckState.Warning;
}
return CheckState.Ok;
}
private string BuildSuspensionDetail(IEnumerable<SuspendedInstance> instances, int total, int resumable, int nonresumable)
{
var builder = new StringBuilder();
builder.Append(total).Append(" suspended BizTalk service instance(s): resumable=").Append(resumable)
.Append(", nonresumable=").Append(nonresumable);
var apps = instances
.GroupBy(x => x.ApplicationName)
.OrderByDescending(x => x.Count())
.Take(_options.MaxSummaryItems)
.Select(x => x.Key + "(R=" + x.Count(y => y.Kind == SuspendedKind.Resumable) + ", NR=" + x.Count(y => y.Kind == SuspendedKind.NonResumable) + ")");
AppendList(builder, "applications", apps);
var examples = instances
.OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue)
.Take(Math.Min(_options.MaxSummaryItems, 5))
.Select(x => EmptyAsUnknown(x.ServiceName) + " on " + EmptyAsUnknown(x.HostName));
AppendList(builder, "examples", examples);
return builder.ToString();
}
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
{
return ((int)state).ToString(CultureInfo.InvariantCulture)
+ " \"" + SanitizeService(serviceName) + "\" "
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
+ " "
+ SanitizeDetail(detail);
}
private static string AppendDiagnostics(string detail, IEnumerable<string> diagnostics)
{
var visible = diagnostics == null ? new string[0] : diagnostics.Take(3).ToArray();
if (visible.Length == 0)
{
return detail;
}
return detail + "; diagnostics: " + string.Join("; ", visible);
}
private static void AppendList(StringBuilder builder, string label, IEnumerable<string> values)
{
var list = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
if (list.Length > 0)
{
builder.Append("; ").Append(label).Append("=").Append(string.Join(", ", list));
}
}
private static void AppendOptional(StringBuilder builder, string label, string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
builder.Append(", ").Append(label).Append("=").Append(value);
}
}
private static string JoinDb(string server, string database)
{
if (string.IsNullOrWhiteSpace(server) && string.IsNullOrWhiteSpace(database))
{
return string.Empty;
}
return EmptyAsUnknown(server) + "\\" + EmptyAsUnknown(database);
}
private static string HostStateName(int state)
{
switch (state)
{
case HostStopped:
return "Stopped";
case HostStartPending:
return "StartPending";
case HostStopPending:
return "StopPending";
case HostStarted:
return "Started";
case HostContinuePending:
return "ContinuePending";
case HostPausePending:
return "PausePending";
case HostPaused:
return "Paused";
case HostUnknown:
return "Unknown";
default:
return "Unknown(" + state.ToString(CultureInfo.InvariantCulture) + ")";
}
}
private static string EmptyAsUnknown(string value)
{
return string.IsNullOrWhiteSpace(value) ? "unknown" : value.Trim();
}
private static string SanitizeService(string value)
{
return SanitizeDetail(value).Replace("\"", "'");
}
private static string SanitizeDetail(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "No details.";
}
return value.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ').Trim();
}
}
}
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace BizTalkCheckmkPulse
{
internal sealed class EventLogProbe
{
private readonly MonitoringOptions _options;
public EventLogProbe(MonitoringOptions options)
{
_options = options;
}
public void Query(ProbeResult result)
{
result.EventLog.Since = DateTime.Now.AddMinutes(-_options.EventLogLookbackMinutes);
try
{
using (var log = new EventLog("Application", "."))
{
var entries = log.Entries;
for (var i = entries.Count - 1; i >= 0; i--)
{
var entry = entries[i];
if (entry.TimeGenerated < result.EventLog.Since)
{
break;
}
if (!IsRelevantSource(entry.Source))
{
continue;
}
if (entry.EntryType == EventLogEntryType.Error)
{
result.EventLog.Errors++;
}
else if (entry.EntryType == EventLogEntryType.Warning)
{
result.EventLog.Warnings++;
}
}
}
result.EventLog.Available = true;
}
catch (Exception ex)
{
result.EventLog.Available = false;
result.EventLog.Failure = ex.GetType().Name + ": " + ex.Message;
}
}
private bool IsRelevantSource(string source)
{
if (string.IsNullOrWhiteSpace(source))
{
return false;
}
return _options.EventLogSources.Any(x => source.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
namespace BizTalkCheckmkPulse
{
internal enum CheckState
{
Ok = 0,
Warning = 1,
Critical = 2,
Unknown = 3
}
internal enum SuspendedKind
{
Resumable,
NonResumable
}
internal sealed class ProbeResult
{
public ProbeResult()
{
Diagnostics = new List<string>();
Platform = new PlatformState();
HostInstances = new List<HostInstanceState>();
SuspendedInstances = new List<SuspendedInstance>();
Applications = new List<ApplicationRuntimeState>();
EventLog = new EventLogState();
}
public List<string> Diagnostics { get; private set; }
public PlatformState Platform { get; private set; }
public List<HostInstanceState> HostInstances { get; private set; }
public List<SuspendedInstance> SuspendedInstances { get; private set; }
public List<ApplicationRuntimeState> Applications { get; private set; }
public EventLogState EventLog { get; private set; }
}
internal sealed class PlatformState
{
public bool WmiConnected { get; set; }
public string ServerName { get; set; }
public string GroupName { get; set; }
public string ManagementDbServer { get; set; }
public string ManagementDbName { get; set; }
public string MessageBoxDbServer { get; set; }
public string MessageBoxDbName { get; set; }
}
internal sealed class HostInstanceState
{
public string InstanceName { get; set; }
public string HostName { get; set; }
public string RunningServer { get; set; }
public int ServiceState { get; set; }
}
internal sealed class SuspendedInstance
{
public string ApplicationName { get; set; }
public string ServiceName { get; set; }
public string HostName { get; set; }
public string ServiceClass { get; set; }
public string InstanceId { get; set; }
public string ErrorDescription { get; set; }
public DateTime? SuspendTime { get; set; }
public SuspendedKind Kind { get; set; }
}
internal sealed class ApplicationRuntimeState
{
public string ApplicationName { get; set; }
public int ReceiveLocationTotal { get; set; }
public int ReceiveLocationDisabled { get; set; }
public int SendPortTotal { get; set; }
public int SendPortStarted { get; set; }
public int SendPortStopped { get; set; }
public int SendPortBound { get; set; }
public int SendPortUnknown { get; set; }
public int OrchestrationTotal { get; set; }
public int OrchestrationStarted { get; set; }
public int OrchestrationStopped { get; set; }
public int OrchestrationBound { get; set; }
public int OrchestrationUnbound { get; set; }
public int OrchestrationUnknown { get; set; }
}
internal sealed class EventLogState
{
public bool Available { get; set; }
public int Errors { get; set; }
public int Warnings { get; set; }
public DateTime Since { get; set; }
public string Failure { get; set; }
}
}
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
namespace BizTalkCheckmkPulse
{
internal sealed class MonitoringOptions
{
public string Server { get; set; }
public string ServicePrefix { get; set; }
public string EnvironmentName { get; set; }
public int QueryTimeoutSeconds { get; set; }
public int WarnResumableThreshold { get; set; }
public int CritNonResumableThreshold { get; set; }
public int MaxSummaryItems { get; set; }
public bool AlertOnArtifactRuntimeIssues { get; set; }
public bool EmitPerApplicationSuspensionServices { get; set; }
public bool ProbeEventLog { get; set; }
public int EventLogLookbackMinutes { get; set; }
public int EventLogWarnThreshold { get; set; }
public int EventLogCritThreshold { get; set; }
public IReadOnlyList<string> EventLogSources { get; set; }
public bool SelfTest { get; set; }
public MonitoringOptions()
{
Server = ".";
ServicePrefix = "BizTalk";
EnvironmentName = string.Empty;
QueryTimeoutSeconds = 25;
WarnResumableThreshold = 1;
CritNonResumableThreshold = 1;
MaxSummaryItems = 12;
AlertOnArtifactRuntimeIssues = false;
EmitPerApplicationSuspensionServices = false;
ProbeEventLog = true;
EventLogLookbackMinutes = 60;
EventLogWarnThreshold = 1;
EventLogCritThreshold = 10;
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
}
public string ServiceName(string suffix)
{
var prefix = string.IsNullOrWhiteSpace(EnvironmentName)
? ServicePrefix
: ServicePrefix + " " + EnvironmentName.Trim();
return prefix.Trim() + " " + suffix;
}
public static MonitoringOptions Load(string[] args)
{
var options = new MonitoringOptions();
var settings = ConfigurationManager.AppSettings;
options.Server = ReadString(settings, "Server", options.Server);
options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix);
options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName);
options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120);
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues);
options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices);
options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog);
options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080);
options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000);
options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000);
options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources)));
ApplyArguments(options, args ?? new string[0]);
return options;
}
private static void ApplyArguments(MonitoringOptions options, string[] args)
{
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (EqualsAny(arg, "--self-test", "/self-test"))
{
options.SelfTest = true;
}
else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length)
{
options.Server = args[++i];
}
else if (EqualsAny(arg, "--environment", "/environment") && i + 1 < args.Length)
{
options.EnvironmentName = args[++i];
}
else if (EqualsAny(arg, "--emit-app-services", "/emit-app-services"))
{
options.EmitPerApplicationSuspensionServices = true;
}
else if (EqualsAny(arg, "--alert-artifacts", "/alert-artifacts"))
{
options.AlertOnArtifactRuntimeIssues = true;
}
}
}
private static string ReadString(System.Collections.Specialized.NameValueCollection settings, string key, string fallback)
{
var value = settings[key];
return value == null ? fallback : value.Trim();
}
private static int ReadInt(System.Collections.Specialized.NameValueCollection settings, string key, int fallback, int min, int max)
{
int parsed;
if (!int.TryParse(settings[key], NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed))
{
return fallback;
}
if (parsed < min)
{
return min;
}
return parsed > max ? max : parsed;
}
private static bool ReadBool(System.Collections.Specialized.NameValueCollection settings, string key, bool fallback)
{
bool parsed;
return bool.TryParse(settings[key], out parsed) ? parsed : fallback;
}
private static IReadOnlyList<string> ReadSources(string value)
{
return (value ?? string.Empty)
.Split(new[] { '|', ';', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Where(x => x.Length > 0)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
private static bool EqualsAny(string value, params string[] candidates)
{
return candidates.Any(x => string.Equals(value, x, StringComparison.OrdinalIgnoreCase));
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BizTalkCheckmkPulse
{
internal static class Program
{
private static int Main(string[] args)
{
MonitoringOptions options = null;
try
{
Console.OutputEncoding = new UTF8Encoding(false);
options = MonitoringOptions.Load(args);
var formatter = new CheckmkLocalFormatter(options);
if (options.SelfTest)
{
foreach (var line in formatter.FormatSelfTest())
{
Console.WriteLine(line);
}
return 0;
}
var result = new ProbeResult();
var wmiProbe = new WmiBizTalkProbe(options);
wmiProbe.Query(result);
if (options.ProbeEventLog)
{
var eventLogProbe = new EventLogProbe(options);
eventLogProbe.Query(result);
}
foreach (var line in formatter.Format(result))
{
Console.WriteLine(line);
}
return 0;
}
catch (Exception ex)
{
var fallbackOptions = options ?? new MonitoringOptions();
var formatter = new CheckmkLocalFormatter(fallbackOptions);
foreach (var line in formatter.FormatFatal("BizTalk Checkmk Pulse failed: " + ex.GetType().Name + ": " + ex.Message))
{
Console.WriteLine(line);
}
return 0;
}
}
}
}
+288
View File
@@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
namespace BizTalkCheckmkPulse
{
internal sealed class WmiBizTalkProbe
{
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
private const string UnknownApplication = "(unknown)";
private const int SuspendedResumable = 4;
private const int SuspendedNonResumable = 32;
private const int SendPortBound = 1;
private const int SendPortStopped = 2;
private const int SendPortStarted = 3;
private const int OrchestrationUnbound = 1;
private const int OrchestrationBound = 2;
private const int OrchestrationStopped = 3;
private const int OrchestrationStarted = 4;
private readonly MonitoringOptions _options;
public WmiBizTalkProbe(MonitoringOptions options)
{
_options = options;
}
public void Query(ProbeResult result)
{
var server = string.IsNullOrWhiteSpace(_options.Server) || _options.Server == "."
? Environment.MachineName
: _options.Server.Trim();
result.Platform.ServerName = server;
var scope = new ManagementScope("\\\\" + server + "\\" + NamespacePath);
scope.Connect();
result.Platform.WmiConnected = true;
QueryPlatform(scope, result);
QueryHostInstances(scope, result);
var applicationIndex = BuildApplicationIndex(scope, result);
QueryApplicationRuntime(scope, result);
QuerySuspendedInstances(scope, result, applicationIndex);
}
private void QueryPlatform(ManagementScope scope, ProbeResult result)
{
TryQuery(scope, result, "MSBTS_GroupSetting", "SELECT * FROM MSBTS_GroupSetting", item =>
{
result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName"));
result.Platform.ManagementDbServer = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbServerName"), WmiHelpers.GetString(item, "DBServerName"));
result.Platform.ManagementDbName = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbName"), WmiHelpers.GetString(item, "DatabaseName"));
}, true);
TryQuery(scope, result, "MSBTS_MessageBoxSetting", "SELECT * FROM MSBTS_MessageBoxSetting", item =>
{
if (string.IsNullOrWhiteSpace(result.Platform.MessageBoxDbServer))
{
result.Platform.MessageBoxDbServer = FirstNonEmpty(WmiHelpers.GetString(item, "DBServerName"), WmiHelpers.GetString(item, "ServerName"));
result.Platform.MessageBoxDbName = FirstNonEmpty(WmiHelpers.GetString(item, "DBName"), WmiHelpers.GetString(item, "DatabaseName"));
}
}, true);
}
private Dictionary<string, string> BuildApplicationIndex(ManagementScope scope, ProbeResult result)
{
var index = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
AddArtifactIndex(scope, result, index, "MSBTS_SendPort");
AddArtifactIndex(scope, result, index, "MSBTS_ReceivePort");
AddArtifactIndex(scope, result, index, "MSBTS_ReceiveLocation");
AddArtifactIndex(scope, result, index, "MSBTS_Orchestration");
return index;
}
private void AddArtifactIndex(ManagementScope scope, ProbeResult result, Dictionary<string, string> index, string className)
{
TryQuery(scope, result, className, "SELECT * FROM " + className, item =>
{
var app = GetApplicationName(item);
foreach (var key in CandidateKeys(item))
{
if (!string.IsNullOrWhiteSpace(key) && !index.ContainsKey(key))
{
index.Add(key, app);
}
}
}, false);
}
private void QueryHostInstances(ManagementScope scope, ProbeResult result)
{
TryQuery(scope, result, "MSBTS_HostInstance", "SELECT * FROM MSBTS_HostInstance", item =>
{
result.HostInstances.Add(new HostInstanceState
{
InstanceName = FirstNonEmpty(WmiHelpers.GetString(item, "InstanceName"), WmiHelpers.GetString(item, "Name")),
HostName = WmiHelpers.GetString(item, "HostName"),
RunningServer = WmiHelpers.GetString(item, "RunningServer"),
ServiceState = WmiHelpers.GetInt32(item, "ServiceState", 0)
});
}, true);
}
private void QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
{
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.ReceiveLocationTotal++;
if (WmiHelpers.GetBoolean(item, "IsDisabled", false))
{
app.ReceiveLocationDisabled++;
}
}, true);
TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.SendPortTotal++;
switch (WmiHelpers.GetInt32(item, "Status", 0))
{
case SendPortStarted:
app.SendPortStarted++;
break;
case SendPortStopped:
app.SendPortStopped++;
break;
case SendPortBound:
app.SendPortBound++;
break;
default:
app.SendPortUnknown++;
break;
}
}, true);
TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.OrchestrationTotal++;
switch (WmiHelpers.GetInt32(item, "OrchestrationStatus", 0))
{
case OrchestrationStarted:
app.OrchestrationStarted++;
break;
case OrchestrationStopped:
app.OrchestrationStopped++;
break;
case OrchestrationBound:
app.OrchestrationBound++;
break;
case OrchestrationUnbound:
app.OrchestrationUnbound++;
break;
default:
app.OrchestrationUnknown++;
break;
}
}, true);
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
}
private void QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
{
TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
{
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
result.SuspendedInstances.Add(new SuspendedInstance
{
ApplicationName = ResolveApplication(item, applicationIndex),
ServiceName = FirstNonEmpty(WmiHelpers.GetString(item, "ServiceName"), WmiHelpers.GetString(item, "Name")),
HostName = WmiHelpers.GetString(item, "HostName"),
ServiceClass = WmiHelpers.GetString(item, "ServiceClass"),
InstanceId = WmiHelpers.GetString(item, "InstanceID"),
ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"),
SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"),
Kind = serviceStatus == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable
});
}, true);
}
private void TryQuery(ManagementScope scope, ProbeResult result, string label, string queryText, Action<ManagementObject> action, bool required)
{
try
{
ForEachObject(scope, queryText, action);
}
catch (ManagementException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
catch (UnauthorizedAccessException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
catch (System.Runtime.InteropServices.COMException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
}
private void ForEachObject(ManagementScope scope, string queryText, Action<ManagementObject> action)
{
var queryOptions = new EnumerationOptions
{
ReturnImmediately = true,
Rewindable = false,
Timeout = TimeSpan.FromSeconds(_options.QueryTimeoutSeconds)
};
using (var searcher = new ManagementObjectSearcher(scope, new ObjectQuery(queryText), queryOptions))
using (var collection = searcher.Get())
{
foreach (ManagementObject item in collection)
{
using (item)
{
action(item);
}
}
}
}
private static ApplicationRuntimeState GetApplication(Dictionary<string, ApplicationRuntimeState> apps, string applicationName)
{
var name = string.IsNullOrWhiteSpace(applicationName) ? UnknownApplication : applicationName.Trim();
ApplicationRuntimeState app;
if (!apps.TryGetValue(name, out app))
{
app = new ApplicationRuntimeState { ApplicationName = name };
apps.Add(name, app);
}
return app;
}
private static string ResolveApplication(ManagementBaseObject item, Dictionary<string, string> applicationIndex)
{
var direct = GetApplicationName(item);
if (!string.Equals(direct, UnknownApplication, StringComparison.OrdinalIgnoreCase))
{
return direct;
}
foreach (var key in CandidateKeys(item))
{
string app;
if (!string.IsNullOrWhiteSpace(key) && applicationIndex.TryGetValue(key, out app))
{
return app;
}
}
return UnknownApplication;
}
private static IEnumerable<string> CandidateKeys(ManagementBaseObject item)
{
yield return WmiHelpers.GetString(item, "ApplicationName");
yield return WmiHelpers.GetString(item, "ServiceName");
yield return WmiHelpers.GetString(item, "Name");
yield return WmiHelpers.GetString(item, "HostName");
}
private static string GetApplicationName(ManagementBaseObject item)
{
return FirstNonEmpty(
WmiHelpers.GetString(item, "ApplicationName"),
WmiHelpers.GetString(item, "Application"),
WmiHelpers.GetString(item, "BizTalkApplication")) ?? UnknownApplication;
}
private static string FirstNonEmpty(params string[] values)
{
return values == null ? null : values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
}
private static void AddDiagnostic(ProbeResult result, string label, string message, bool required)
{
result.Diagnostics.Add((required ? "Required" : "Optional") + " WMI query " + label + " failed: " + message);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using System.Globalization;
using System.Management;
namespace BizTalkCheckmkPulse
{
internal static class WmiHelpers
{
public static string GetString(ManagementBaseObject item, string propertyName)
{
if (item == null || string.IsNullOrWhiteSpace(propertyName))
{
return string.Empty;
}
try
{
var value = item.Properties[propertyName] == null ? null : item.Properties[propertyName].Value;
return value == null ? string.Empty : Convert.ToString(value, CultureInfo.InvariantCulture).Trim();
}
catch (ManagementException)
{
return string.Empty;
}
}
public static int GetInt32(ManagementBaseObject item, string propertyName, int fallback)
{
var value = GetString(item, propertyName);
int parsed;
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
}
public static bool GetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
{
var value = GetString(item, propertyName);
bool parsed;
return bool.TryParse(value, out parsed) ? parsed : fallback;
}
public static DateTime? GetDmtfDateTime(ManagementBaseObject item, string propertyName)
{
var value = GetString(item, propertyName);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
try
{
return ManagementDateTimeConverter.ToDateTime(value);
}
catch (ArgumentOutOfRangeException)
{
return null;
}
}
}
}