Add BizTalk Checkmk Pulse local check
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
end_of_line = lf
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
artifacts/
|
||||||
|
.vs/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.nupkg
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj", "{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
# Dokumentation: BizTalk Checkmk Pulse
|
||||||
|
|
||||||
|
## Zielbild
|
||||||
|
|
||||||
|
Ziel ist ein wartbares Monitoring fuer BizTalk Server 2020 in den Umgebungen `ACC`, `DEV`, `TST` und `PRD`. Jede Umgebung besitzt einen BizTalk Server 2020 und einen SQL Server. SQL Server wird mit dem Checkmk-eigenen MSSQL-Plugin ueberwacht; fuer BizTalk liefert dieses Projekt die fehlende fachliche und technische Laufzeitsicht.
|
||||||
|
|
||||||
|
Das Monitoring soll:
|
||||||
|
|
||||||
|
- auf jeder BizTalk-Maschine lokal laufen
|
||||||
|
- ohne PowerShell-Abhaengigkeit funktionieren
|
||||||
|
- keine BizTalk-DLLs im Build erzwingen
|
||||||
|
- Checkmk-2.4-kompatible Services und Metriken erzeugen
|
||||||
|
- service-discovery-freundlich und dashboard-tauglich sein
|
||||||
|
- bei Fehlern gueltige `UNKNOWN`-Services statt kaputter Agent-Ausgaben liefern
|
||||||
|
|
||||||
|
## Technische Bewertung
|
||||||
|
|
||||||
|
### Option A: Checkmk Local Check mit C#/.NET Framework
|
||||||
|
|
||||||
|
Bewertung: empfohlen und umgesetzt.
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- Checkmk 2.4 unterstuetzt Local Checks direkt.
|
||||||
|
- Windows Server mit BizTalk 2020 bringt .NET Framework in der Regel passend mit.
|
||||||
|
- `System.Management` kann BizTalk-WMI lesen.
|
||||||
|
- Keine PowerShell Execution Policy, keine Script-Signing-Frage.
|
||||||
|
- Kein serverseitiges Checkmk-Python-Plugin notwendig.
|
||||||
|
- Rollout ist eine einfache Dateiablage unter `%ProgramData%\checkmk\agent\local`.
|
||||||
|
|
||||||
|
Nachteile:
|
||||||
|
|
||||||
|
- Schwellwerte sind in der `.exe.config`, nicht als Checkmk-Regelsatz in WATO.
|
||||||
|
- Eigene Graphing-Definitionen sind nicht enthalten; Checkmk zeigt Local-Check-Metriken trotzdem als Performance-Daten und Graphen.
|
||||||
|
|
||||||
|
### Option B: Agent Plugin plus serverseitiges Checkmk-Plugin
|
||||||
|
|
||||||
|
Bewertung: technisch elegant, aber fuer den ersten produktiven Schritt schwerer.
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- Checkmk-Regeln, Discovery und Metrikdefinitionen koennen sauber zentral modelliert werden.
|
||||||
|
- Bessere langfristige Erweiterbarkeit als MKP.
|
||||||
|
|
||||||
|
Nachteile:
|
||||||
|
|
||||||
|
- Checkmk-Check-API-Versionen muessen enger gepflegt werden.
|
||||||
|
- Server-seitige Installation in jeder Site erforderlich.
|
||||||
|
- Mehr Aufwand fuer Managed-Services-Betrieb und Updates.
|
||||||
|
|
||||||
|
Empfehlung: als Version 2 dieses Projekts denkbar, wenn die Local-Check-Variante stabil in PRD laeuft und zentrale Regelsaetze wirklich benoetigt werden.
|
||||||
|
|
||||||
|
### Option C: PowerShell Local Check
|
||||||
|
|
||||||
|
Bewertung: nicht empfohlen fuer diese Umgebung.
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- Schnell zu schreiben.
|
||||||
|
- WMI/CIM-Zugriff ist komfortabel.
|
||||||
|
|
||||||
|
Nachteile:
|
||||||
|
|
||||||
|
- PowerShell ist in vielen Serverumgebungen eingeschraenkt oder signaturpflichtig.
|
||||||
|
- Ausfuehrungsverhalten im Checkmk-Agent-Kontext ist haeufiger fehleranfaellig.
|
||||||
|
|
||||||
|
### Option D: BizTalk ExplorerOM/OperationsOM
|
||||||
|
|
||||||
|
Bewertung: fachlich stark, deployseitig unnoetig schwer.
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- Hoehere BizTalk-Abstraktion als rohe WMI-Klassen.
|
||||||
|
|
||||||
|
Nachteile:
|
||||||
|
|
||||||
|
- BizTalk-DLL-Versionen muessen beim Build und teilweise zur Laufzeit passen.
|
||||||
|
- Build-Agenten brauchen BizTalk-Komponenten oder SDK-Dateien.
|
||||||
|
- Fuer die benoetigten Zustandsdaten reicht WMI aus.
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
```text
|
||||||
|
Checkmk Windows Agent
|
||||||
|
|
|
||||||
|
| startet lokale Checks aus %ProgramData%\checkmk\agent\local
|
||||||
|
v
|
||||||
|
biztalk_checkmk_pulse.cmd
|
||||||
|
|
|
||||||
|
| startet
|
||||||
|
v
|
||||||
|
BizTalkCheckmkPulse.exe
|
||||||
|
|
|
||||||
|
| liest lokal
|
||||||
|
+-- WMI root\MicrosoftBizTalkServer
|
||||||
|
+-- Windows Application Event Log
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Checkmk Local Check Zeilen nach STDOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
Der `.cmd`-Wrapper liefert auch dann eine gueltige `UNKNOWN`-Zeile, wenn die EXE fehlt. Die EXE selbst faengt Laufzeitfehler ab und schreibt fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben.
|
||||||
|
|
||||||
|
## Datenquellen
|
||||||
|
|
||||||
|
### BizTalk WMI Namespace
|
||||||
|
|
||||||
|
Namespace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
root\MicrosoftBizTalkServer
|
||||||
|
```
|
||||||
|
|
||||||
|
Genutzte Klassen:
|
||||||
|
|
||||||
|
| Klasse | Zweck |
|
||||||
|
| --- | --- |
|
||||||
|
| `MSBTS_GroupSetting` | BizTalk-Gruppe und Management-DB-Hinweise. |
|
||||||
|
| `MSBTS_MessageBoxSetting` | MessageBox-DB-Hinweise. |
|
||||||
|
| `MSBTS_HostInstance` | Host-Instance-Zustand. |
|
||||||
|
| `MSBTS_ServiceInstance` | Suspended service instances. |
|
||||||
|
| `MSBTS_ReceiveLocation` | Receive-Location-Zustand. |
|
||||||
|
| `MSBTS_SendPort` | Send-Port-Zustand. |
|
||||||
|
| `MSBTS_Orchestration` | Orchestration-Zustand. |
|
||||||
|
| `MSBTS_ReceivePort` | Application-Mapping fuer Details. |
|
||||||
|
|
||||||
|
### Windows Application Event Log
|
||||||
|
|
||||||
|
Der Check liest standardmaessig das lokale Application Log fuer die letzten 60 Minuten und filtert auf Quellen wie:
|
||||||
|
|
||||||
|
- `BizTalk Server`
|
||||||
|
- `XLANG/s`
|
||||||
|
- `ENTSSO`
|
||||||
|
- `BizTalk Server Application`
|
||||||
|
- `BizTalk Server EDI`
|
||||||
|
|
||||||
|
Die Liste ist ueber `EventLogSources` konfigurierbar.
|
||||||
|
|
||||||
|
## Resilienz
|
||||||
|
|
||||||
|
Das Plugin ist bewusst defensiv gebaut:
|
||||||
|
|
||||||
|
- WMI-Queries haben ein konfigurierbares Timeout.
|
||||||
|
- Optionale WMI-Klassen erzeugen Diagnosehinweise statt Totalabbruch.
|
||||||
|
- Fehlende Properties werden als leer/unknown behandelt.
|
||||||
|
- Local-Check-Ausgaben verwenden gueltige Checkmk-Zeilen mit genau vier Feldern.
|
||||||
|
- Fatal Errors erzeugen `UNKNOWN` fuer alle stabilen Services.
|
||||||
|
- Event-Log-Auswertung ist isoliert; ein Fehler dort bricht WMI-Monitoring nicht ab.
|
||||||
|
- Service-Namen sind stabil, damit Service Discovery nicht bei jedem Lauf neue Services erzeugt.
|
||||||
|
|
||||||
|
## Statusmodell
|
||||||
|
|
||||||
|
### BizTalk Platform
|
||||||
|
|
||||||
|
- `OK`: BizTalk-WMI erreichbar.
|
||||||
|
- `UNKNOWN`: WMI nicht erreichbar oder kompletter Programmfehler.
|
||||||
|
|
||||||
|
Dieser Service ist der Integrationsindikator. Wenn er `UNKNOWN` ist, sind Berechtigungen, BizTalk-Installation oder WMI-Repository zu pruefen.
|
||||||
|
|
||||||
|
### BizTalk Suspended Instances
|
||||||
|
|
||||||
|
- `OK`: keine suspendierten Instanzen.
|
||||||
|
- `WARN`: mindestens `WarnResumableThreshold` resumable suspended instances.
|
||||||
|
- `CRIT`: mindestens `CritNonResumableThreshold` non-resumable suspended instances.
|
||||||
|
- `UNKNOWN`: Datenquelle nicht lesbar.
|
||||||
|
|
||||||
|
### BizTalk Host Instances
|
||||||
|
|
||||||
|
- `OK`: alle Host-Instanzen sind started.
|
||||||
|
- `WARN`: mindestens eine Host-Instanz ist pending.
|
||||||
|
- `CRIT`: mindestens eine Host-Instanz ist stopped oder unknown.
|
||||||
|
- `UNKNOWN`: keine Host-Instanzen gefunden oder Datenquelle nicht lesbar.
|
||||||
|
|
||||||
|
### BizTalk Runtime Artifacts
|
||||||
|
|
||||||
|
- `OK`: Artefakte lesbar, keine unbekannten Statuswerte.
|
||||||
|
- `WARN`: nur wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte vorhanden sind.
|
||||||
|
- `CRIT`: unbekannte Send-Port- oder Orchestration-Statuswerte.
|
||||||
|
|
||||||
|
Deaktivierte Receive Locations und gestoppte Ports koennen in BizTalk fachlich korrekt sein. Deshalb ist die Alarmierung hier standardmaessig informativ.
|
||||||
|
|
||||||
|
### BizTalk Event Log
|
||||||
|
|
||||||
|
- `OK`: keine relevanten Fehler/Warnungen ueber Schwellwert.
|
||||||
|
- `WARN`: Fehler oder Warnungen ab `EventLogWarnThreshold`.
|
||||||
|
- `CRIT`: Fehler ab `EventLogCritThreshold`.
|
||||||
|
- `UNKNOWN`: Event Log nicht lesbar.
|
||||||
|
|
||||||
|
## Dashboard-Empfehlung
|
||||||
|
|
||||||
|
Pro Umgebung sollte der BizTalk-Host in einem eigenen Host-Ordner oder Host-Tag fuer `ACC`, `DEV`, `TST`, `PRD` liegen. Im Dashboard eignen sich:
|
||||||
|
|
||||||
|
- Service State Widgets fuer die fuenf BizTalk-Services
|
||||||
|
- Graphen fuer `biztalk_suspended_total`, `biztalk_host_instances_stopped`, `biztalk_eventlog_errors`
|
||||||
|
- Hostgruppe/Ordner pro Umgebung
|
||||||
|
- Optional eine View gefiltert auf `Service starts with BizTalk`
|
||||||
|
|
||||||
|
Empfohlene Reihenfolge im Dashboard:
|
||||||
|
|
||||||
|
1. `BizTalk Platform`
|
||||||
|
2. `BizTalk Suspended Instances`
|
||||||
|
3. `BizTalk Host Instances`
|
||||||
|
4. `BizTalk Event Log`
|
||||||
|
5. `BizTalk Runtime Artifacts`
|
||||||
|
6. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
|
||||||
|
|
||||||
|
## Rollout-Vorgehen
|
||||||
|
|
||||||
|
1. Build-Paket erzeugen.
|
||||||
|
2. In `DEV` auf dem BizTalk-Server installieren.
|
||||||
|
3. `--self-test` und normalen Lauf ausfuehren.
|
||||||
|
4. Agent-Dump pruefen.
|
||||||
|
5. Checkmk Discovery durchfuehren.
|
||||||
|
6. Eine Woche Messwerte und false positives beobachten.
|
||||||
|
7. Nach `TST` und `ACC` uebernehmen.
|
||||||
|
8. In `PRD` mit `AlertOnArtifactRuntimeIssues=false` starten.
|
||||||
|
9. Nach Betriebsfreigabe Schwellwerte feinjustieren.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Service bleibt UNKNOWN
|
||||||
|
|
||||||
|
Pruefen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn WMI nicht erreichbar ist:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
wmic /namespace:\\root\MicrosoftBizTalkServer path MSBTS_HostInstance get HostName,RunningServer,ServiceState
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch den Agent-Dump verwenden:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keine Services in Discovery
|
||||||
|
|
||||||
|
Pruefen:
|
||||||
|
|
||||||
|
- Liegt `biztalk_checkmk_pulse.cmd` direkt unter `%ProgramData%\checkmk\agent\local`?
|
||||||
|
- Gibt der Wrapper direkt eine Zeile im Format `0 "Service" metric=value Details` aus?
|
||||||
|
- Wurde der Checkmk-Agent nach Policy-/Bakery-Aenderungen neu ausgerollt?
|
||||||
|
|
||||||
|
### Runtime Artifacts zeigt deaktivierte Artefakte
|
||||||
|
|
||||||
|
Das ist standardmaessig `OK`, damit gewollt deaktivierte BizTalk-Artefakte nicht alarmieren. Fuer strengere PRD-Standards:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<add key="AlertOnArtifactRuntimeIssues" value="true" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Weiterentwicklung
|
||||||
|
|
||||||
|
Sinnvolle naechste Ausbaustufen:
|
||||||
|
|
||||||
|
- MKP mit Agent-Bakery-Regel fuer zentrale Konfiguration.
|
||||||
|
- Optionales serverseitiges Check-Plugin nach Checkmk Check API V2.
|
||||||
|
- Custom Dashboard/View als Checkmk GUI Extension.
|
||||||
|
- Ergaenzung um MessageBox-Spool/Tracking-Daten, falls operativ benoetigt.
|
||||||
|
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# BizTalk Checkmk Pulse
|
||||||
|
|
||||||
|
`BizTalk Checkmk Pulse` ist ein lokaler Checkmk-Check fuer Microsoft BizTalk Server 2020 auf Windows. Er liest BizTalk-Betriebsdaten ueber WMI aus `root\MicrosoftBizTalkServer`, erzeugt Checkmk-Local-Check-Ausgaben und kann dadurch ohne serverseitigen Python-Check in Checkmk 2.4 integriert werden.
|
||||||
|
|
||||||
|
Der Ansatz ist fuer Umgebungen wie `ACC`, `DEV`, `TST` und `PRD` gedacht, in denen pro Umgebung ein BizTalk Server 2020 und ein SQL Server betrieben werden. SQL Server bleibt beim mitgelieferten Checkmk-MSSQL-Plugin; dieses Projekt ergaenzt die BizTalk-spezifische Sicht.
|
||||||
|
|
||||||
|
## Warum Local Check statt serverseitigem Check-Plugin?
|
||||||
|
|
||||||
|
Checkmk 2.4 kann Windows-Agent-Plugins und Local Checks direkt ausfuehren. Fuer diese Umgebung ist ein C#/.NET-Framework-Programm mit `.cmd`-Wrapper die robusteste Variante:
|
||||||
|
|
||||||
|
- keine PowerShell-Ausfuehrung erforderlich
|
||||||
|
- keine BizTalk-ExplorerOM/OperationsOM-DLL als Build-Abhaengigkeit
|
||||||
|
- keine Python-Check-API-Abhaengigkeit auf der Checkmk-Site
|
||||||
|
- einfache Verteilung auf die BizTalk-Maschinen per Dateiablage, Softwareverteilung oder Agent Bakery
|
||||||
|
- automatische Service Discovery in Checkmk
|
||||||
|
|
||||||
|
## Erzeugte Checkmk-Services
|
||||||
|
|
||||||
|
Standardmaessig entstehen diese stabilen Services:
|
||||||
|
|
||||||
|
- `BizTalk Platform`
|
||||||
|
- `BizTalk Suspended Instances`
|
||||||
|
- `BizTalk Host Instances`
|
||||||
|
- `BizTalk Runtime Artifacts`
|
||||||
|
- `BizTalk Event Log`
|
||||||
|
|
||||||
|
Wenn `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` gesetzt wird, wird der Name vor den Suffix gesetzt, z.B. `BizTalk PRD Suspended Instances`. Das ist praktisch, wenn die Umgebung bereits im Hostnamen oder Ordner abgebildet ist, aber nicht zwingend noetig.
|
||||||
|
|
||||||
|
Details zu Statuslogik und Metriken stehen in [docs/CheckmkServices.md](docs/CheckmkServices.md).
|
||||||
|
Beispielausgaben stehen in [docs/ExampleOutput.md](docs/ExampleOutput.md).
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Voraussetzungen auf einem Windows-Build-Host:
|
||||||
|
|
||||||
|
- Visual Studio 2022 Build Tools oder Visual Studio
|
||||||
|
- MSBuild im `PATH`
|
||||||
|
- .NET Framework 4.7.2 Developer Pack
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
scripts\build-release.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Deployment-Paket erstellen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
scripts\package-release.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Ergebnis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
artifacts\BizTalkCheckmkPulse-deploy\
|
||||||
|
biztalk_checkmk_pulse.cmd
|
||||||
|
BizTalkCheckmkPulse\
|
||||||
|
BizTalkCheckmkPulse.exe
|
||||||
|
BizTalkCheckmkPulse.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Validierung nach dem Build:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
artifacts\BizTalkCheckmkPulse-deploy\biztalk_checkmk_pulse.cmd --self-test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation auf dem BizTalk-Server
|
||||||
|
|
||||||
|
Kopiere den Inhalt von `artifacts\BizTalkCheckmkPulse-deploy` nach:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local
|
||||||
|
```
|
||||||
|
|
||||||
|
Zielstruktur:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Manueller Test auf dem BizTalk-Server:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
Agent-Ausgabe wie Checkmk sie sieht:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach in Checkmk fuer den BizTalk-Host eine Service Discovery ausfuehren, die neuen Services aufnehmen und Changes aktivieren.
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Die Konfiguration liegt neben der EXE:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Wichtige Werte:
|
||||||
|
|
||||||
|
| Key | Default | Bedeutung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Server` | `.` | Lokaler BizTalk-Server. Remote-WMI ist moeglich, aber nicht empfohlen. |
|
||||||
|
| `ServicePrefix` | `BizTalk` | Prefix fuer alle Checkmk-Services. |
|
||||||
|
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST` oder `PRD`. |
|
||||||
|
| `QueryTimeoutSeconds` | `25` | WMI-Timeout pro Query. |
|
||||||
|
| `WarnResumableThreshold` | `1` | WARN ab n resumable suspended instances. |
|
||||||
|
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable suspended instances. |
|
||||||
|
| `AlertOnArtifactRuntimeIssues` | `false` | Wenn `true`, werden deaktivierte Receive Locations und inaktive Ports/Orchestrations als WARN gewertet. |
|
||||||
|
| `EmitPerApplicationSuspensionServices` | `false` | Erzeugt zusaetzliche Services pro Anwendung mit Suspensions. |
|
||||||
|
| `ProbeEventLog` | `true` | Liest das Windows Application Log nach BizTalk-bezogenen Sources. |
|
||||||
|
| `EventLogLookbackMinutes` | `60` | Zeitraum fuer Event-Log-Auswertung. |
|
||||||
|
| `EventLogWarnThreshold` | `1` | WARN ab n Errors oder Warnings. |
|
||||||
|
| `EventLogCritThreshold` | `10` | CRIT ab n Errors. |
|
||||||
|
|
||||||
|
Empfehlung:
|
||||||
|
|
||||||
|
- In `DEV`, `TST`, `ACC`: `AlertOnArtifactRuntimeIssues=false`, damit bewusst gestoppte Artefakte nicht rauschen.
|
||||||
|
- In `PRD`: erst zwei Wochen beobachten; danach nur aktivieren, wenn die Runtime-Artefakte wirklich als Betriebsstandard immer aktiv sein muessen.
|
||||||
|
- `EmitPerApplicationSuspensionServices=true` nur verwenden, wenn Anwendungsteams eigene Services benoetigen. Sonst bleibt die Discovery schlanker.
|
||||||
|
|
||||||
|
## Integration in Checkmk Managed Services Edition 2.4
|
||||||
|
|
||||||
|
Manuelle Integration:
|
||||||
|
|
||||||
|
1. Dateien auf dem BizTalk-Server nach `%ProgramData%\checkmk\agent\local` kopieren.
|
||||||
|
2. Optional `BizTalkCheckmkPulse.exe.config` je Umgebung anpassen.
|
||||||
|
3. Agent-Dump pruefen.
|
||||||
|
4. Service Discovery auf dem BizTalk-Host ausfuehren.
|
||||||
|
5. Services in ein BizTalk-Dashboard aufnehmen.
|
||||||
|
|
||||||
|
Integration ueber Agent Bakery:
|
||||||
|
|
||||||
|
1. Deployment-Dateien in der Checkmk-Site als Custom-Agent-Dateien bereitstellen.
|
||||||
|
2. Windows-Agent-Regel fuer die BizTalk-Hosts erstellen.
|
||||||
|
3. Agent backen und auf ACC/DEV/TST/PRD-BizTalk-Hosts ausrollen.
|
||||||
|
4. Discovery und Dashboard-Aufnahme durchfuehren.
|
||||||
|
|
||||||
|
Die Checkmk-Dokumentation nennt fuer Windows Local Checks `%ProgramData%\checkmk\agent\local` und fuer Windows Agent Plugins `%ProgramData%\checkmk\agent\plugins`. Dieses Projekt nutzt bewusst `local`, weil der Zustand direkt vom Host berechnet und sofort als Checkmk-Service geliefert wird.
|
||||||
|
|
||||||
|
## Quellen
|
||||||
|
|
||||||
|
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html
|
||||||
|
- Checkmk Windows Agent und Plugin-Pfade: https://docs.checkmk.com/latest/en/agent_windows.html
|
||||||
|
- Checkmk Agent-Based Plugin-Entwicklung: https://docs.checkmk.com/latest/en/devel_check_plugins.html
|
||||||
|
- Checkmk Bakery API: https://docs.checkmk.com/latest/en/bakery_api.html
|
||||||
|
- Checkmk MKP-Pakete: https://docs.checkmk.com/latest/en/mkps.html
|
||||||
|
- Microsoft BizTalk WMI `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi
|
||||||
|
- Microsoft BizTalk WMI `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "BASE=%~dp0BizTalkCheckmkPulse"
|
||||||
|
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
|
||||||
|
|
||||||
|
if not exist "%EXE%" (
|
||||||
|
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below %BASE%
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
"%EXE%" %*
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# Checkmk Services und Metriken
|
||||||
|
|
||||||
|
Alle Services werden als Checkmk Local Checks ausgegeben. Das Format ist:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<state> "<service name>" <metrics-or-minus> <summary>
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen.
|
||||||
|
|
||||||
|
## BizTalk Platform
|
||||||
|
|
||||||
|
Zweck:
|
||||||
|
|
||||||
|
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
|
||||||
|
- Zeigt Server, Gruppe, Management-DB und MessageBox-DB, soweit WMI diese Werte liefert.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | WMI-Verbindung erfolgreich. |
|
||||||
|
| `UNKNOWN` | WMI-Verbindung oder Programmstart fehlgeschlagen. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- keine
|
||||||
|
|
||||||
|
## BizTalk Suspended Instances
|
||||||
|
|
||||||
|
Zweck:
|
||||||
|
|
||||||
|
- Ermittelt suspendierte BizTalk-Service-Instanzen.
|
||||||
|
- Unterscheidet resumable und non-resumable.
|
||||||
|
- Zeigt betroffene Anwendungen und Beispiele in der Summary.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | keine suspendierten Instanzen. |
|
||||||
|
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
|
||||||
|
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
|
||||||
|
| `UNKNOWN` | WMI oder Programmfehler. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_suspended_total` | Summe aller suspendierten Instanzen. |
|
||||||
|
| `biztalk_suspended_resumable` | Resumable suspended instances. |
|
||||||
|
| `biztalk_suspended_nonresumable` | Non-resumable suspended instances. |
|
||||||
|
|
||||||
|
## BizTalk Host Instances
|
||||||
|
|
||||||
|
Zweck:
|
||||||
|
|
||||||
|
- Ueberwacht BizTalk Host Instances auf dem BizTalk-Server.
|
||||||
|
- Nicht gestartete Host Instances sind normalerweise betriebsrelevant.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | alle Host Instances sind started. |
|
||||||
|
| `WARN` | mindestens eine Host Instance ist pending. |
|
||||||
|
| `CRIT` | mindestens eine Host Instance ist stopped oder unknown. |
|
||||||
|
| `UNKNOWN` | keine Host Instances gefunden oder WMI/Programmfehler. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_host_instances_total` | Gesamtzahl Host Instances. |
|
||||||
|
| `biztalk_host_instances_started` | Gestartete Host Instances. |
|
||||||
|
| `biztalk_host_instances_stopped` | Gestoppte Host Instances. |
|
||||||
|
| `biztalk_host_instances_pending` | StartPending/StopPending. |
|
||||||
|
| `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. |
|
||||||
|
|
||||||
|
## BizTalk Runtime Artifacts
|
||||||
|
|
||||||
|
Zweck:
|
||||||
|
|
||||||
|
- Summiert Laufzeitzustaende von Receive Locations, Send Ports und Orchestrations.
|
||||||
|
- Liefert dashboard-taugliche Zahlen zur Laufzeitlandschaft.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | Artefakte lesbar; keine unbekannten Statuswerte. |
|
||||||
|
| `WARN` | Nur bei `AlertOnArtifactRuntimeIssues=true` und inaktiven/deaktivierten Artefakten. |
|
||||||
|
| `CRIT` | Unbekannte Send-Port- oder Orchestration-Statuswerte. |
|
||||||
|
| `UNKNOWN` | Programmfehler. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_applications` | Anzahl erkannter BizTalk-Anwendungen. |
|
||||||
|
| `biztalk_receive_locations` | Gesamtzahl Receive Locations. |
|
||||||
|
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
|
||||||
|
| `biztalk_send_ports` | Gesamtzahl Send Ports. |
|
||||||
|
| `biztalk_send_ports_started` | Gestartete Send Ports. |
|
||||||
|
| `biztalk_send_ports_inactive` | Bound oder stopped Send Ports. |
|
||||||
|
| `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. |
|
||||||
|
| `biztalk_orchestrations` | Gesamtzahl Orchestrations. |
|
||||||
|
| `biztalk_orchestrations_started` | Gestartete Orchestrations. |
|
||||||
|
| `biztalk_orchestrations_inactive` | Bound, unbound oder stopped Orchestrations. |
|
||||||
|
| `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. |
|
||||||
|
|
||||||
|
## BizTalk Event Log
|
||||||
|
|
||||||
|
Zweck:
|
||||||
|
|
||||||
|
- Erfasst BizTalk-bezogene Errors und Warnings aus dem Windows Application Log.
|
||||||
|
- Ergaenzt WMI-Zustandsdaten um aktuelle Laufzeitereignisse.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | Schwellwerte nicht erreicht. |
|
||||||
|
| `WARN` | Errors oder Warnings ab `EventLogWarnThreshold`. |
|
||||||
|
| `CRIT` | Errors ab `EventLogCritThreshold`. |
|
||||||
|
| `UNKNOWN` | Application Event Log nicht lesbar. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_eventlog_errors` | BizTalk-bezogene Errors im Lookback-Fenster. |
|
||||||
|
| `biztalk_eventlog_warnings` | BizTalk-bezogene Warnings im Lookback-Fenster. |
|
||||||
|
|
||||||
|
## Optionale per-Application-Services
|
||||||
|
|
||||||
|
Wenn `EmitPerApplicationSuspensionServices=true` gesetzt ist, werden zusaetzliche Services fuer Anwendungen erzeugt, die suspendierte Instanzen haben:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BizTalk Suspended <ApplicationName>
|
||||||
|
```
|
||||||
|
|
||||||
|
Das ist fuer Anwendungsteams hilfreich, kann aber die Service-Anzahl erhoehen. Fuer den Basisausbau wird empfohlen, diese Option deaktiviert zu lassen.
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Beispielausgaben
|
||||||
|
|
||||||
|
## Self-Test
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 "BizTalk Platform" - Self test OK. Checkmk local check output is valid.
|
||||||
|
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0 Self test OK.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gesunder BizTalk-Server
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 "BizTalk Platform" - BizTalk WMI reachable, server=BIZTALK01, group=BizTalkMgmtDb, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
|
||||||
|
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found.
|
||||||
|
0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0
|
||||||
|
0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0
|
||||||
|
0 "BizTalk Event Log" biztalk_eventlog_errors=0;1;10;0|biztalk_eventlog_warnings=0;1;;0 BizTalk-related Application log entries in last 60 minutes: errors=0, warnings=0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suspended Instances
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 "BizTalk Suspended Instances" biztalk_suspended_total=3;;;0|biztalk_suspended_resumable=2;1;;0|biztalk_suspended_nonresumable=1;;1;0 3 suspended BizTalk service instance(s): resumable=2, nonresumable=1; applications=OrderProcessing(R=2, NR=1); examples=Send.Invoice on ProcessingHost
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fehlendes Deployment
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below C:\ProgramData\checkmk\agent\local\BizTalkCheckmkPulse
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Integration in Checkmk
|
||||||
|
|
||||||
|
## Zielstruktur auf dem BizTalk-Server
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manuelle Installation
|
||||||
|
|
||||||
|
1. Release-Paket auf den BizTalk-Server kopieren.
|
||||||
|
2. Inhalt nach `%ProgramData%\checkmk\agent\local` kopieren.
|
||||||
|
3. Optional `EnvironmentName` in der `.exe.config` setzen.
|
||||||
|
4. Test ausfuehren:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Agent-Ausgabe pruefen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
6. In Checkmk:
|
||||||
|
|
||||||
|
- Host oeffnen
|
||||||
|
- Service Discovery ausfuehren
|
||||||
|
- gefundene `BizTalk ...` Services aufnehmen
|
||||||
|
- Changes aktivieren
|
||||||
|
|
||||||
|
## Agent Bakery
|
||||||
|
|
||||||
|
Checkmk Managed Services Edition 2.4 enthaelt die kommerziellen Mechanismen fuer Agent Bakery. Fuer einen sauberen Rollout:
|
||||||
|
|
||||||
|
1. Deployment-Dateien als Custom Files oder ueber ein spaeteres MKP bereitstellen.
|
||||||
|
2. Regel nur auf BizTalk-Hosts anwenden, z.B. Host-Tag `application:biztalk`.
|
||||||
|
3. Gebackenen Windows-Agenten fuer die BizTalk-Hosts installieren.
|
||||||
|
4. Service Discovery ausfuehren.
|
||||||
|
|
||||||
|
Hinweis: Dieses Repository enthaelt bewusst noch kein Bakery-Python-Plugin. Die manuelle bzw. dateibasierte Verteilung ist die risikoarme erste Version. Eine Bakery-Erweiterung ist eine sinnvolle Version-2-Ausbaustufe.
|
||||||
|
|
||||||
|
## Empfohlene Host-Struktur
|
||||||
|
|
||||||
|
Host-Tags oder Ordner:
|
||||||
|
|
||||||
|
- `env:ACC`
|
||||||
|
- `env:DEV`
|
||||||
|
- `env:TST`
|
||||||
|
- `env:PRD`
|
||||||
|
- `app:biztalk`
|
||||||
|
|
||||||
|
Service-Filter fuer Views:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Service starts with: BizTalk
|
||||||
|
```
|
||||||
|
|
||||||
|
Dashboard-Kacheln:
|
||||||
|
|
||||||
|
- Host/Service state fuer BizTalk-Host
|
||||||
|
- Service state fuer `BizTalk Suspended Instances`
|
||||||
|
- Graph `biztalk_suspended_total`
|
||||||
|
- Graph `biztalk_host_instances_stopped`
|
||||||
|
- Graph `biztalk_eventlog_errors`
|
||||||
|
- MSSQL-Services des SQL-Servers daneben
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
where msbuild.exe >nul 2>nul
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo MSBuild.exe was not found in PATH. Run this from a Visual Studio Developer Command Prompt.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
msbuild BizTalkCheckmkPulse.sln /p:Configuration=Release /p:Platform="Any CPU" /m /v:minimal
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
call "%~dp0build-release.cmd"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
set "ROOT=%~dp0.."
|
||||||
|
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
|
||||||
|
set "APP=%OUT%\BizTalkCheckmkPulse"
|
||||||
|
|
||||||
|
if exist "%OUT%" rmdir /s /q "%OUT%"
|
||||||
|
mkdir "%APP%"
|
||||||
|
|
||||||
|
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
|
||||||
|
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
|
||||||
|
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
|
||||||
|
|
||||||
|
echo Deployment package written to %OUT%
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user