Initial commit for the biztalk suspended isntances local check
build / build (push) Has been cancelled
build / build (push) Has been cancelled
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
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{md,yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build release
|
||||
shell: cmd
|
||||
run: scripts\build-release.cmd
|
||||
- name: Package deployment folder
|
||||
shell: cmd
|
||||
run: scripts\package-release.cmd
|
||||
- name: Upload deployment artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: BizTalkSuspendedInstancesCheck-deploy
|
||||
path: artifacts\BizTalkSuspendedInstancesCheck-deploy
|
||||
@@ -0,0 +1,8 @@
|
||||
bin/
|
||||
obj/
|
||||
artifacts/
|
||||
*.user
|
||||
*.suo
|
||||
.vs/
|
||||
TestResults/
|
||||
|
||||
@@ -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}") = "BizTalkSuspendedInstancesCheck", "src\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.csproj", "{ED7AF215-F12B-4E2E-811B-693A537C9493}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
# Dokumentation: BizTalk Runtime Checkmk Local Check
|
||||
|
||||
## Ziel
|
||||
|
||||
Der Check beantwortet fuer den lokalen BizTalk-Server:
|
||||
|
||||
- Gibt es suspendierte BizTalk-Service-Instanzen?
|
||||
- Sind sie resumable oder non-resumable?
|
||||
- Welche BizTalk-Anwendungen sind betroffen?
|
||||
- Sind BizTalk Host-Instanzen gestartet?
|
||||
- Wie ist der aggregierte Laufzeitstatus wichtiger BizTalk-Artefakte?
|
||||
|
||||
Er ist fuer Checkmk Windows Agent Local Checks gebaut und benoetigt keine PowerShell.
|
||||
|
||||
## Architektur
|
||||
|
||||
Komponenten:
|
||||
|
||||
- `BizTalkSuspendedInstancesCheck.exe`: C#/.NET-Framework-Konsolenprogramm
|
||||
- `BizTalkSuspendedInstancesCheck.exe.config`: Konfiguration
|
||||
- `biztalk_suspended_instances.cmd`: Checkmk-Wrapper fuer `%ProgramData%\checkmk\agent\local`
|
||||
|
||||
Laufzeitfluss:
|
||||
|
||||
1. Checkmk Windows Agent ruft `biztalk_suspended_instances.cmd` auf.
|
||||
2. Der Wrapper startet die EXE.
|
||||
3. Die EXE fragt `root\MicrosoftBizTalkServer` per WMI ab.
|
||||
4. Die EXE schreibt mehrere Checkmk-konforme Local-Check-Zeilen nach STDOUT.
|
||||
|
||||
Die Anwendung gibt selbst bei Fehlern eine syntaktisch gueltige Checkmk-Zeile aus. Dadurch wird der Monitoring-Zustand `UNKNOWN` sichtbar, statt den Agenten still zu stoeren.
|
||||
|
||||
## Ausfuehrung durch Checkmk
|
||||
|
||||
Der Check wird als Checkmk Local Check betrieben. Das bedeutet: Checkmk fuehrt auf dem BizTalk-Server keine zentrale Abfrage gegen BizTalk aus, sondern der Checkmk Windows Agent startet lokal abgelegte Checks und sammelt deren STDOUT ein.
|
||||
|
||||
Der relevante Agent-Pfad auf Windows ist:
|
||||
|
||||
```cmd
|
||||
%ProgramData%\checkmk\agent\local
|
||||
```
|
||||
|
||||
In diesem Verzeichnis liegt unser mitgelieferter Wrapper:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
```
|
||||
|
||||
Der Wrapper ruft die eigentliche C#-EXE auf:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
```
|
||||
|
||||
Die vollstaendige Zielstruktur ist:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||
```
|
||||
|
||||
Das Aufrufskript muss nicht separat neu entwickelt werden. Es ist Bestandteil dieses Repositories:
|
||||
|
||||
```text
|
||||
deployment\checkmk\biztalk_suspended_instances.cmd
|
||||
```
|
||||
|
||||
Beim Paketieren mit `scripts\package-release.cmd` wird dieses Skript in den Deployment-Ordner kopiert. Der Inhalt von `artifacts\BizTalkSuspendedInstancesCheck-deploy` kann komplett nach `%ProgramData%\checkmk\agent\local` kopiert werden.
|
||||
|
||||
Der technische Ablauf ist:
|
||||
|
||||
1. Checkmk Windows Agent startet `biztalk_suspended_instances.cmd`.
|
||||
2. Die `.cmd` sucht `BizTalkSuspendedInstancesCheck.exe` zuerst im Unterordner `BizTalkSuspendedInstancesCheck`.
|
||||
3. Falls die EXE dort nicht liegt, sucht die `.cmd` die EXE direkt neben dem Skript.
|
||||
4. Falls die EXE nicht gefunden wird, erzeugt die `.cmd` eine gueltige Checkmk-Zeile mit Status `UNKNOWN`.
|
||||
5. Falls die EXE gefunden wird, fragt sie BizTalk per WMI ab.
|
||||
6. Die EXE schreibt die Checkmk-Services nach STDOUT.
|
||||
7. Der Checkmk Agent nimmt diese Zeilen in seine Agentenausgabe auf.
|
||||
8. Nach Checkmk Service Discovery koennen die Services uebernommen werden.
|
||||
|
||||
Manueller Test auf dem BizTalk-Server:
|
||||
|
||||
```cmd
|
||||
"%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd"
|
||||
```
|
||||
|
||||
Agentenausgabe pruefen:
|
||||
|
||||
```cmd
|
||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||
```
|
||||
|
||||
## WMI-Abfragen
|
||||
|
||||
Primaere Abfrage:
|
||||
|
||||
```sql
|
||||
SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32
|
||||
```
|
||||
|
||||
Statuswerte:
|
||||
|
||||
- `4`: Suspended (resumable)
|
||||
- `32`: Suspended (not resumable)
|
||||
|
||||
Weitere Betriebsabfragen:
|
||||
|
||||
```sql
|
||||
SELECT * FROM MSBTS_HostInstance
|
||||
SELECT * FROM MSBTS_ReceiveLocation
|
||||
SELECT * FROM MSBTS_SendPort
|
||||
SELECT * FROM MSBTS_Orchestration
|
||||
```
|
||||
|
||||
Aus Betriebssicht werden damit folgende Risiken sichtbar:
|
||||
|
||||
- Gestoppte oder haengende Host-Instanzen.
|
||||
- Deaktivierte Receive Locations.
|
||||
- Nicht gestartete Send Ports.
|
||||
- Nicht gestartete oder nicht gebundene Orchestrations.
|
||||
- Unbekannte WMI-Statuswerte, die auf Versions-/Schemaabweichungen oder Lesefehler hindeuten koennen.
|
||||
|
||||
Zur Zuordnung der Anwendung wird zuerst versucht, eine Anwendungs-Property direkt aus der Service-Instanz zu lesen. Falls BizTalk diese nicht liefert, baut der Check einen lokalen Index ueber bekannte BizTalk-Artefakte:
|
||||
|
||||
- `MSBTS_SendPort`
|
||||
- `MSBTS_ReceivePort`
|
||||
- `MSBTS_ReceiveLocation`
|
||||
- `MSBTS_Orchestration`
|
||||
|
||||
Wenn keine Zuordnung moeglich ist, erscheint die Instanz unter `(Unknown Application)`. Das ist absichtlich kein Fehler, weil BizTalk nicht fuer jede Instanz eine direkte Anwendungseigenschaft liefern muss.
|
||||
|
||||
## Checkmk-Ausgabe
|
||||
|
||||
Der Check gibt standardmaessig drei stabile Services aus. Es werden keine dynamischen Services pro Anwendung erzeugt, damit Checkmk Service Discovery stabil bleibt.
|
||||
|
||||
Beispiel ohne Befund:
|
||||
|
||||
```text
|
||||
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;;;0|biztalk_host_instances_pending=0;;;0|biztalk_host_instances_unknown=0;;;0 total=4, started=4, stopped=0, pending=0, unknown=0
|
||||
0 "BizTalk Runtime Artifacts" biztalk_applications=6;;;0|biztalk_receive_locations=20;;;0|biztalk_receive_locations_disabled=0;;;0|biztalk_send_ports=30;;;0|biztalk_send_ports_started=30;;;0|biztalk_send_ports_stopped=0;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unknown=0;;;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_stopped=0;;;0|biztalk_orchestrations_bound=0;;;0|biztalk_orchestrations_unbound=0;;;0|biztalk_orchestrations_unknown=0;;;0 applications=6 | ReceiveLocations total=20, disabled=0 | SendPorts total=30, started=30, stopped=0, bound=0, unknown=0 | Orchestrations total=12, started=12, stopped=0, bound=0, unbound=0, unknown=0
|
||||
```
|
||||
|
||||
Beispiel mit non-resumable Instanz:
|
||||
|
||||
```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=2. ApplicationA R=2 NR=0; ApplicationB R=0 NR=1
|
||||
```
|
||||
|
||||
Beispiel fuer Host-Instanzen:
|
||||
|
||||
```text
|
||||
2 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=3;;;0|biztalk_host_instances_stopped=1;;;0|biztalk_host_instances_pending=0;;;0|biztalk_host_instances_unknown=0;;;0 total=4, started=3, stopped=1, pending=0, unknown=0 | Not started: ProcessingHost@BIZTALK01=Stopped
|
||||
```
|
||||
|
||||
Beispiel fuer Artefaktstatus:
|
||||
|
||||
```text
|
||||
0 "BizTalk Runtime Artifacts" biztalk_applications=6;;;0|biztalk_receive_locations=20;;;0|biztalk_receive_locations_disabled=1;;;0|biztalk_send_ports=30;;;0|biztalk_send_ports_started=28;;;0|biztalk_send_ports_stopped=2;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unknown=0;;;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=11;;;0|biztalk_orchestrations_stopped=1;;;0|biztalk_orchestrations_bound=0;;;0|biztalk_orchestrations_unbound=0;;;0|biztalk_orchestrations_unknown=0;;;0 applications=6 | ReceiveLocations total=20, disabled=1 | SendPorts total=30, started=28, stopped=2, bound=0, unknown=0 | Orchestrations total=12, started=11, stopped=1, bound=0, unbound=0, unknown=0 | Notable apps: ApplicationA RL disabled=1, SP stopped/bound/unknown=2/0/0, Orch stopped/bound/unbound/unknown=1/0/0/0
|
||||
```
|
||||
|
||||
## Schwellwerte
|
||||
|
||||
Default:
|
||||
|
||||
- Resumable ab `1`: `WARN`
|
||||
- Non-resumable ab `1`: `CRIT`
|
||||
- Host-Instanz `Stopped` oder unbekannt: `CRIT`
|
||||
- Host-Instanz `StartPending` oder `StopPending`: `WARN`
|
||||
- Artefakte mit unbekanntem Status: `CRIT`
|
||||
- Deaktivierte/inaktive Artefakte: nur Anzeige, optional `WARN` mit `AlertOnArtifactRuntimeIssues=true`
|
||||
|
||||
Begruendung:
|
||||
|
||||
- Resumable Suspensions sind stoerungsrelevant, aber potentiell wiederaufnehmbar.
|
||||
- Non-resumable Suspensions verlangen normalerweise manuelle Bereinigung oder fachliche Analyse und werden daher kritisch bewertet.
|
||||
- Gestoppte Host-Instanzen wirken direkt auf die Verarbeitung und werden daher kritisch bewertet.
|
||||
- Deaktivierte Receive Locations, gestoppte Send Ports und gestoppte Orchestrations koennen gewollt sein; deshalb werden sie zuerst als Betriebsinformation ausgegeben.
|
||||
|
||||
## Betrieb
|
||||
|
||||
Empfohlener Installationspfad:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||
```
|
||||
|
||||
Der Wrapper sucht die EXE zuerst im Unterordner `BizTalkSuspendedInstancesCheck` und danach direkt neben der `.cmd`. Damit funktioniert sowohl eine saubere Unterordner-Installation als auch ein flaches Deployment.
|
||||
|
||||
Nach Aenderungen an den Service-Namen muss in Checkmk eine erneute Service Discovery erfolgen. Reine Schwellwert- oder Anzeigeaenderungen brauchen keine neue Discovery.
|
||||
|
||||
## Rechte
|
||||
|
||||
Der Check muss unter dem Konto des Checkmk Windows Agent auf dem BizTalk-Server WMI-Leserechte fuer `root\MicrosoftBizTalkServer` haben. In der Praxis ist das meist `LocalSystem` oder ein dediziertes Agent-Servicekonto mit lokalen Rechten.
|
||||
|
||||
## Resilienz
|
||||
|
||||
- Keine PowerShell-Abhaengigkeit.
|
||||
- Keine BizTalk-Assembly-Abhaengigkeit im Build.
|
||||
- UTF-8-Ausgabe wird in der EXE gesetzt.
|
||||
- WMI-Timeout ist konfigurierbar.
|
||||
- Fehler erzeugen `UNKNOWN` statt leerer Ausgabe.
|
||||
- Artefakt-Index ist best effort; fehlende Anwendungszuordnung verhindert den Status nicht.
|
||||
- Ein stabiler Checkmk-Service vermeidet Service-Discovery-Flapping bei wechselnden Anwendungsnamen.
|
||||
- Runtime-Artefakte werden aggregiert und begrenzt ausgegeben, damit Checkmk-Summaries lesbar bleiben.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Wichtige `appSettings`:
|
||||
|
||||
- `ServiceName`: Service fuer suspendierte Instanzen.
|
||||
- `HostInstancesServiceName`: Service fuer Host-Instanzen.
|
||||
- `RuntimeArtifactsServiceName`: Service fuer Artefaktstatus.
|
||||
- `MaxApplicationDetails`: maximale Anzahl Anwendungen in der Suspensions-Summary.
|
||||
- `MaxRuntimeApplicationDetails`: maximale Anzahl auffaelliger Anwendungen in der Runtime-Summary.
|
||||
- `AlertOnStoppedHostInstances`: Host-Instanz-Abweichungen alarmieren.
|
||||
- `AlertOnArtifactRuntimeIssues`: deaktivierte/inaktive Artefakte als `WARN` werten.
|
||||
- `EmitPerApplicationServices`: zusaetzliche Suspensions-Services pro Anwendung erzeugen. Nur verwenden, wenn dynamische Services in Checkmk gewuenscht sind.
|
||||
|
||||
## Gitea
|
||||
|
||||
Das Repository enthaelt `.gitea/workflows/build.yml`. Der Workflow erwartet einen Windows Runner mit Visual Studio Build Tools und .NET Framework 4.6.1 Developer Pack.
|
||||
|
||||
Wenn euer Gitea Runner anders gelabelt ist, `runs-on` in `.gitea/workflows/build.yml` anpassen.
|
||||
@@ -0,0 +1,196 @@
|
||||
# BizTalk Runtime Checkmk Local Check
|
||||
|
||||
Lokaler Checkmk-Check fuer Microsoft BizTalk Server 2020. Der Check prueft ueber WMI im Namespace `root\MicrosoftBizTalkServer`, ob suspendierte BizTalk-Service-Instanzen vorhanden sind, und liefert zusaetzliche Betriebsinformationen zu Host-Instanzen und BizTalk-Artefakten.
|
||||
|
||||
## Entscheidung
|
||||
|
||||
PowerShell ist in der Zielumgebung abgeriegelt. Die sinnvollste Option ist deshalb ein kleines C#/.NET-Framework-Konsolenprogramm, das per `.cmd` aus dem Checkmk-Agent-Local-Verzeichnis gestartet wird.
|
||||
|
||||
Bewertung:
|
||||
|
||||
- C#/.NET Framework: geeignet. Auf Windows Server mit BizTalk vorhanden, direkte WMI-Unterstuetzung via `System.Management`, keine PowerShell-Ausfuehrung, keine BizTalk-DLL als Compile-Time-Abhaengigkeit.
|
||||
- PowerShell: fachlich moeglich, aber in der Umgebung ausgeschlossen.
|
||||
- VBScript/JScript: technisch moeglich, aber schlechter wartbar und fehleranfaelliger bei Encoding und Fehlerbehandlung.
|
||||
- BizTalk OperationsOM/ExplorerOM DLLs: fachlich moeglich, aber deploy- und buildseitig unnoetig schwer, da die DLLs auf Build- und Laufzeitumgebung passen muessen.
|
||||
|
||||
Der Check nutzt fuer Suspensions die WMI-Klasse `MSBTS_ServiceInstance`. Microsoft dokumentiert fuer `ServiceStatus` den Wert `4` als `Suspended (resumable)` und `32` als `Suspended (not resumable)`. Fuer die weiteren Betriebsinformationen werden `MSBTS_HostInstance`, `MSBTS_ReceiveLocation`, `MSBTS_SendPort` und `MSBTS_Orchestration` gelesen.
|
||||
|
||||
## Checkmk-Verhalten
|
||||
|
||||
Der Check erzeugt standardmaessig drei stabile Checkmk-Services:
|
||||
|
||||
- `BizTalk Suspended Instances`
|
||||
- `BizTalk Host Instances`
|
||||
- `BizTalk Runtime Artifacts`
|
||||
|
||||
### BizTalk Suspended Instances
|
||||
|
||||
Statuslogik:
|
||||
|
||||
- `OK`: keine suspendierten Instanzen
|
||||
- `WARN`: mindestens eine resumable suspendierte Instanz
|
||||
- `CRIT`: mindestens eine non-resumable suspendierte Instanz
|
||||
- `UNKNOWN`: WMI-Zugriff, Konfiguration oder Laufzeitfehler
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_suspended_total`
|
||||
- `biztalk_suspended_resumable`
|
||||
- `biztalk_suspended_nonresumable`
|
||||
|
||||
Die betroffenen BizTalk-Anwendungen stehen in der Checkmk-Summary, z.B. `ApplicationA R=2 NR=0; ApplicationB R=0 NR=1`.
|
||||
|
||||
### BizTalk Host Instances
|
||||
|
||||
Statuslogik:
|
||||
|
||||
- `OK`: alle Host-Instanzen sind `Started`
|
||||
- `WARN`: Host-Instanzen sind in `StartPending` oder `StopPending`
|
||||
- `CRIT`: mindestens eine Host-Instanz ist `Stopped` oder hat einen unbekannten Zustand
|
||||
- `UNKNOWN`: WMI liefert keine Host-Instanzen
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_host_instances_total`
|
||||
- `biztalk_host_instances_started`
|
||||
- `biztalk_host_instances_stopped`
|
||||
- `biztalk_host_instances_pending`
|
||||
- `biztalk_host_instances_unknown`
|
||||
|
||||
Die Summary zeigt Gesamtzahlen und bis zu 12 nicht gestartete Host-Instanzen im Format `Host@Server=Status`.
|
||||
|
||||
### BizTalk Runtime Artifacts
|
||||
|
||||
Dieser Service ist standardmaessig informativ (`OK`), solange keine unbekannten WMI-Zustaende auftreten. Grund: deaktivierte Receive Locations, gestoppte Send Ports oder gestoppte Orchestrations koennen in BizTalk fachlich beabsichtigt sein.
|
||||
|
||||
Statuslogik:
|
||||
|
||||
- `OK`: Artefaktstatus lesbar, keine unbekannten Statuswerte
|
||||
- `CRIT`: Send Ports oder Orchestrations haben unbekannte WMI-Statuswerte
|
||||
- Optional `WARN`: wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte gefunden werden
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_applications`
|
||||
- `biztalk_receive_locations`, `biztalk_receive_locations_disabled`
|
||||
- `biztalk_send_ports`, `biztalk_send_ports_started`, `biztalk_send_ports_stopped`, `biztalk_send_ports_bound`, `biztalk_send_ports_unknown`
|
||||
- `biztalk_orchestrations`, `biztalk_orchestrations_started`, `biztalk_orchestrations_stopped`, `biztalk_orchestrations_bound`, `biztalk_orchestrations_unbound`, `biztalk_orchestrations_unknown`
|
||||
|
||||
Die Summary ist bewusst bereits lesbar formatiert und enthaelt Abschnittstrenner, z.B. `ReceiveLocations total=20, disabled=1 | SendPorts ... | Notable apps: ...`.
|
||||
|
||||
## Build
|
||||
|
||||
Voraussetzungen auf dem Build-Host:
|
||||
|
||||
- Windows
|
||||
- Visual Studio Build Tools oder Visual Studio mit MSBuild
|
||||
- .NET Framework 4.6.1 Developer Pack
|
||||
|
||||
Build:
|
||||
|
||||
```cmd
|
||||
scripts\build-release.cmd
|
||||
```
|
||||
|
||||
Deployment-Ordner erzeugen:
|
||||
|
||||
```cmd
|
||||
scripts\package-release.cmd
|
||||
```
|
||||
|
||||
Das Ergebnis liegt unter `artifacts\BizTalkSuspendedInstancesCheck-deploy`.
|
||||
|
||||
Der Gitea-Workflow `.gitea/workflows/build.yml` fuehrt denselben Release-Build und die Paketierung aus.
|
||||
|
||||
## Wie Checkmk den Check ausfuehrt
|
||||
|
||||
Checkmk ruft unser C#-Programm nicht direkt per zentraler Checkmk-Regel auf. Der Checkmk Windows Agent startet lokale Checks auf dem BizTalk-Server automatisch aus dem lokalen Agent-Verzeichnis:
|
||||
|
||||
```cmd
|
||||
%ProgramData%\checkmk\agent\local
|
||||
```
|
||||
|
||||
Dort muss eine ausfuehrbare Datei liegen. Fuer diesen Check ist das der bereits mitgelieferte Wrapper:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
```
|
||||
|
||||
Der Wrapper startet anschliessend die eigentliche EXE:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
```
|
||||
|
||||
Zielstruktur auf dem BizTalk-Server:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||
```
|
||||
|
||||
Der Ablauf ist:
|
||||
|
||||
1. Checkmk Windows Agent startet `biztalk_suspended_instances.cmd`.
|
||||
2. Die `.cmd` sucht die EXE im Unterordner `BizTalkSuspendedInstancesCheck`.
|
||||
3. Die EXE liest BizTalk per WMI.
|
||||
4. Die EXE schreibt Checkmk-Local-Check-Zeilen nach STDOUT.
|
||||
5. Der Checkmk Agent liefert diese Zeilen an den Checkmk Server.
|
||||
6. Nach Service Discovery erscheinen die Services in Checkmk.
|
||||
|
||||
Das Skript muss also nicht mehr neu erstellt werden. Es liegt im Repository unter `deployment\checkmk\biztalk_suspended_instances.cmd` und wird beim Paketieren in den Deployment-Ordner kopiert. Falls die EXE fehlt, gibt die `.cmd` eine gueltige `UNKNOWN`-Zeile aus, damit ein unvollstaendiges Deployment in Checkmk sichtbar wird.
|
||||
|
||||
## Deployment auf BizTalk-Server
|
||||
|
||||
1. Release bauen oder aus Gitea-Artefakt laden.
|
||||
2. Inhalt von `artifacts\BizTalkSuspendedInstancesCheck-deploy` nach `%ProgramData%\checkmk\agent\local` kopieren.
|
||||
3. Auf dem BizTalk-Server als lokaler Administrator testen:
|
||||
|
||||
```cmd
|
||||
"%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd"
|
||||
```
|
||||
|
||||
4. Agentenausgabe pruefen:
|
||||
|
||||
```cmd
|
||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||
```
|
||||
|
||||
5. In Checkmk Service Discovery fuer den Host ausfuehren und den Service aufnehmen.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Datei `BizTalkSuspendedInstancesCheck.exe.config` kann neben der EXE angepasst werden.
|
||||
|
||||
Wichtige Werte:
|
||||
|
||||
- `ServiceName`: Checkmk-Service-Name
|
||||
- `HostInstancesServiceName`: Service-Name fuer Host-Instanzen
|
||||
- `RuntimeArtifactsServiceName`: Service-Name fuer Artefaktstatus
|
||||
- `Server`: leer oder `.` fuer lokalen Server
|
||||
- `WarnResumableThreshold`: Default `1`
|
||||
- `CritNonResumableThreshold`: Default `1`
|
||||
- `QueryTimeoutSeconds`: Default `25`
|
||||
- `MaxApplicationDetails`: Anzahl Anwendungen in der Summary, Default `12`
|
||||
- `MaxRuntimeApplicationDetails`: Anzahl auffaelliger Anwendungen in der Runtime-Summary, Default `10`
|
||||
- `AlertOnStoppedHostInstances`: Host-Instanzen mit Status `Stopped`/unbekannt alarmieren, Default `true`
|
||||
- `AlertOnArtifactRuntimeIssues`: deaktivierte/inaktive Artefakte als `WARN` werten, Default `false`
|
||||
- `EmitPerApplicationServices`: optional zusaetzliche Services pro Anwendung fuer Suspensions, Default `false`
|
||||
|
||||
## Gitea
|
||||
|
||||
Das Verzeichnis ist als eigenes Git-Repository mit Branch `main` angelegt. Fuer ein neues Gitea-Repository:
|
||||
|
||||
```cmd
|
||||
git remote add origin <gitea-url>
|
||||
git add .
|
||||
git commit -m "Add BizTalk Checkmk local check"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Quellen
|
||||
|
||||
- Checkmk Local Checks: https://docs.checkmk.com/latest/de/localchecks.html
|
||||
- Microsoft BizTalk `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi
|
||||
- Microsoft BizTalk `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
|
||||
@@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "SERVICE_NAME=BizTalk Suspended Instances"
|
||||
set "CHECK_DIR=%~dp0BizTalkSuspendedInstancesCheck"
|
||||
set "CHECK_EXE=%CHECK_DIR%\BizTalkSuspendedInstancesCheck.exe"
|
||||
|
||||
if not exist "%CHECK_EXE%" (
|
||||
set "CHECK_EXE=%~dp0BizTalkSuspendedInstancesCheck.exe"
|
||||
)
|
||||
|
||||
if not exist "%CHECK_EXE%" (
|
||||
echo 3 "%SERVICE_NAME%" - Check executable not found. Expected "%CHECK_DIR%\BizTalkSuspendedInstancesCheck.exe".
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
"%CHECK_EXE%" %*
|
||||
exit /b 0
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# Checkmk Services und Metriken
|
||||
|
||||
Dieser Check erzeugt standardmaessig drei stabile Local-Check-Services.
|
||||
|
||||
## Aufruf
|
||||
|
||||
Checkmk startet unter Windows lokale Checks aus:
|
||||
|
||||
```cmd
|
||||
%ProgramData%\checkmk\agent\local
|
||||
```
|
||||
|
||||
Der mitgelieferte Wrapper ist:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||
```
|
||||
|
||||
Er ruft die EXE im Unterordner auf:
|
||||
|
||||
```text
|
||||
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||
```
|
||||
|
||||
Das Skript liegt im Repository unter `deployment\checkmk\biztalk_suspended_instances.cmd` und wird durch `scripts\package-release.cmd` in den Deployment-Ordner kopiert.
|
||||
|
||||
## BizTalk Suspended Instances
|
||||
|
||||
Zweck: suspendierte Service-Instanzen erkennen und betroffene Anwendungen direkt in der Summary anzeigen.
|
||||
|
||||
Status:
|
||||
|
||||
- `OK`: keine Suspensions
|
||||
- `WARN`: resumable Suspensions ab Schwellwert
|
||||
- `CRIT`: non-resumable Suspensions ab Schwellwert
|
||||
- `UNKNOWN`: Check- oder WMI-Fehler
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_suspended_total`
|
||||
- `biztalk_suspended_resumable`
|
||||
- `biztalk_suspended_nonresumable`
|
||||
|
||||
## BizTalk Host Instances
|
||||
|
||||
Zweck: gestartete, gestoppte, pending und unbekannte Host-Instanzen sichtbar machen.
|
||||
|
||||
Status:
|
||||
|
||||
- `OK`: alle Host-Instanzen gestartet
|
||||
- `WARN`: mindestens eine Host-Instanz pending
|
||||
- `CRIT`: mindestens eine Host-Instanz stopped oder unknown
|
||||
- `UNKNOWN`: WMI liefert keine Host-Instanzen
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_host_instances_total`
|
||||
- `biztalk_host_instances_started`
|
||||
- `biztalk_host_instances_stopped`
|
||||
- `biztalk_host_instances_pending`
|
||||
- `biztalk_host_instances_unknown`
|
||||
|
||||
## BizTalk Runtime Artifacts
|
||||
|
||||
Zweck: aggregierte Betriebsuebersicht fuer Receive Locations, Send Ports und Orchestrations.
|
||||
|
||||
Status:
|
||||
|
||||
- `OK`: Statuswerte lesbar
|
||||
- `CRIT`: unbekannte Send-Port- oder Orchestration-Statuswerte
|
||||
- Optional `WARN`: wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte vorhanden sind
|
||||
|
||||
Metriken:
|
||||
|
||||
- `biztalk_applications`
|
||||
- `biztalk_receive_locations`
|
||||
- `biztalk_receive_locations_disabled`
|
||||
- `biztalk_send_ports`
|
||||
- `biztalk_send_ports_started`
|
||||
- `biztalk_send_ports_stopped`
|
||||
- `biztalk_send_ports_bound`
|
||||
- `biztalk_send_ports_unknown`
|
||||
- `biztalk_orchestrations`
|
||||
- `biztalk_orchestrations_started`
|
||||
- `biztalk_orchestrations_stopped`
|
||||
- `biztalk_orchestrations_bound`
|
||||
- `biztalk_orchestrations_unbound`
|
||||
- `biztalk_orchestrations_unknown`
|
||||
@@ -0,0 +1,34 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "ROOT=%~dp0.."
|
||||
set "SOLUTION=%ROOT%\BizTalkSuspendedInstancesCheck.sln"
|
||||
|
||||
if not defined MSBUILD (
|
||||
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if exist "%VSWHERE%" (
|
||||
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do (
|
||||
set "MSBUILD=%%i"
|
||||
goto :found_msbuild
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
:found_msbuild
|
||||
if not defined MSBUILD (
|
||||
for /f "tokens=*" %%i in ('where msbuild 2^>nul') do (
|
||||
set "MSBUILD=%%i"
|
||||
goto :found_msbuild_path
|
||||
)
|
||||
)
|
||||
|
||||
:found_msbuild_path
|
||||
if not defined MSBUILD (
|
||||
echo MSBuild was not found. Install Visual Studio Build Tools and the .NET Framework 4.6.1 Developer Pack.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Using MSBuild: %MSBUILD%
|
||||
"%MSBUILD%" "%SOLUTION%" /m /restore /p:Configuration=Release /p:Platform="Any CPU"
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "ROOT=%~dp0.."
|
||||
set "PROJECT_BIN=%ROOT%\src\BizTalkSuspendedInstancesCheck\bin\Release"
|
||||
set "ARTIFACTS=%ROOT%\artifacts"
|
||||
set "DEPLOY=%ARTIFACTS%\BizTalkSuspendedInstancesCheck-deploy"
|
||||
set "APPDIR=%DEPLOY%\BizTalkSuspendedInstancesCheck"
|
||||
|
||||
call "%ROOT%\scripts\build-release.cmd"
|
||||
if errorlevel 1 exit /b %ERRORLEVEL%
|
||||
|
||||
if exist "%DEPLOY%" rmdir /s /q "%DEPLOY%"
|
||||
mkdir "%APPDIR%"
|
||||
|
||||
copy "%ROOT%\deployment\checkmk\biztalk_suspended_instances.cmd" "%DEPLOY%\" >nul
|
||||
copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.exe" "%APPDIR%\" >nul
|
||||
copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.exe.config" "%APPDIR%\" >nul
|
||||
if exist "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.pdb" copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.pdb" "%APPDIR%\" >nul
|
||||
copy "%ROOT%\README.md" "%DEPLOY%\" >nul
|
||||
copy "%ROOT%\Dokumentation.md" "%DEPLOY%\" >nul
|
||||
if exist "%ROOT%\docs" xcopy "%ROOT%\docs" "%DEPLOY%\docs\" /e /i /y >nul
|
||||
|
||||
echo Deployment folder created: %DEPLOY%
|
||||
exit /b 0
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="ServiceName" value="BizTalk Suspended Instances" />
|
||||
<add key="HostInstancesServiceName" value="BizTalk Host Instances" />
|
||||
<add key="RuntimeArtifactsServiceName" value="BizTalk Runtime Artifacts" />
|
||||
<add key="Server" value="." />
|
||||
<add key="WarnResumableThreshold" value="1" />
|
||||
<add key="CritNonResumableThreshold" value="1" />
|
||||
<add key="QueryTimeoutSeconds" value="25" />
|
||||
<add key="MaxApplicationDetails" value="12" />
|
||||
<add key="MaxRuntimeApplicationDetails" value="10" />
|
||||
<add key="AlertOnStoppedHostInstances" value="true" />
|
||||
<add key="AlertOnArtifactRuntimeIssues" value="false" />
|
||||
<add key="EmitPerApplicationServices" value="false" />
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
internal sealed class ApplicationIndex
|
||||
{
|
||||
private const string UnknownApplication = "(Unknown Application)";
|
||||
private readonly Dictionary<string, string> _byArtifactName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public void Add(ManagementBaseObject item)
|
||||
{
|
||||
var application = FirstNonEmpty(
|
||||
SafeGetString(item, "ApplicationName"),
|
||||
SafeGetString(item, "Application"),
|
||||
SafeGetString(item, "BizTalkApplication"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(application))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddName(SafeGetString(item, "Name"), application);
|
||||
AddName(SafeGetString(item, "ServiceName"), application);
|
||||
AddName(SafeGetString(item, "ReceivePortName"), application);
|
||||
AddName(SafeGetString(item, "SendPortName"), application);
|
||||
}
|
||||
|
||||
public string Resolve(ManagementBaseObject serviceInstance)
|
||||
{
|
||||
var direct = FirstNonEmpty(
|
||||
SafeGetString(serviceInstance, "ApplicationName"),
|
||||
SafeGetString(serviceInstance, "Application"),
|
||||
SafeGetString(serviceInstance, "BizTalkApplication"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(direct))
|
||||
{
|
||||
return direct;
|
||||
}
|
||||
|
||||
var serviceName = SafeGetString(serviceInstance, "ServiceName");
|
||||
var name = SafeGetString(serviceInstance, "Name");
|
||||
var serviceClass = SafeGetString(serviceInstance, "ServiceClass");
|
||||
|
||||
return ResolveByName(serviceName)
|
||||
?? ResolveByName(name)
|
||||
?? ResolveByName(LastSegment(serviceName))
|
||||
?? ResolveByName(LastSegment(name))
|
||||
?? ResolveByName(serviceClass)
|
||||
?? UnknownApplication;
|
||||
}
|
||||
|
||||
private void AddName(string name, string application)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalized = name.Trim();
|
||||
if (!_byArtifactName.ContainsKey(normalized))
|
||||
{
|
||||
_byArtifactName.Add(normalized, application.Trim());
|
||||
}
|
||||
|
||||
var lastSegment = LastSegment(normalized);
|
||||
if (!string.IsNullOrWhiteSpace(lastSegment) && !_byArtifactName.ContainsKey(lastSegment))
|
||||
{
|
||||
_byArtifactName.Add(lastSegment, application.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string application;
|
||||
return _byArtifactName.TryGetValue(name.Trim(), out application) ? application : null;
|
||||
}
|
||||
|
||||
private static string LastSegment(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var trimmed = value.Trim();
|
||||
var index = trimmed.LastIndexOf('.');
|
||||
if (index < 0 || index + 1 >= trimmed.Length)
|
||||
{
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return trimmed.Substring(index + 1);
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static string SafeGetString(ManagementBaseObject item, string propertyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (PropertyData property in item.Properties)
|
||||
{
|
||||
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var value = item[property.Name];
|
||||
return value == null ? string.Empty : Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ManagementException)
|
||||
{
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{ED7AF215-F12B-4E2E-811B-693A537C9493}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>BizTalkSuspendedInstancesCheck</RootNamespace>
|
||||
<AssemblyName>BizTalkSuspendedInstancesCheck</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</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>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</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>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApplicationIndex.cs" />
|
||||
<Compile Include="CheckmkFormatter.cs" />
|
||||
<Compile Include="Models.cs" />
|
||||
<Compile Include="MonitoringOptions.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="WmiBizTalkProbe.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
internal sealed class CheckmkFormatter
|
||||
{
|
||||
private const int HostStopped = 1;
|
||||
private const int HostStartPending = 2;
|
||||
private const int HostStopPending = 3;
|
||||
private const int HostStarted = 4;
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
public CheckmkFormatter(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Format(ProbeResult result)
|
||||
{
|
||||
yield return FormatAggregate(result);
|
||||
yield return FormatHostInstances(result);
|
||||
yield return FormatRuntimeArtifacts(result);
|
||||
|
||||
if (_options.EmitPerApplicationServices)
|
||||
{
|
||||
foreach (var line in FormatApplications(result))
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatUnknown(string message)
|
||||
{
|
||||
return BuildLine(CheckState.Unknown, _options.ServiceName, "-", SanitizeDetail(message));
|
||||
}
|
||||
|
||||
public string FormatSelfTest()
|
||||
{
|
||||
return BuildLine(CheckState.Ok, _options.ServiceName, "-", "Self test OK. Checkmk local check output format is valid.");
|
||||
}
|
||||
|
||||
private string FormatHostInstances(ProbeResult result)
|
||||
{
|
||||
var hosts = result.HostInstances;
|
||||
var total = hosts.Count;
|
||||
var started = hosts.Count(x => x.ServiceState == HostStarted);
|
||||
var stopped = hosts.Count(x => x.ServiceState == HostStopped);
|
||||
var pending = hosts.Count(x => x.ServiceState == HostStartPending || x.ServiceState == HostStopPending);
|
||||
var unknown = total - started - stopped - pending;
|
||||
var state = DetermineHostState(total, stopped, pending, unknown);
|
||||
var metrics = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"biztalk_host_instances_total={0};;;0|biztalk_host_instances_started={1};;;0|biztalk_host_instances_stopped={2};;;0|biztalk_host_instances_pending={3};;;0|biztalk_host_instances_unknown={4};;;0",
|
||||
total,
|
||||
started,
|
||||
stopped,
|
||||
pending,
|
||||
unknown);
|
||||
var detail = BuildHostDetail(hosts, total, started, stopped, pending, unknown);
|
||||
|
||||
return BuildLine(state, _options.HostInstancesServiceName, metrics, AppendDiagnostics(detail, result.Diagnostics));
|
||||
}
|
||||
|
||||
private string FormatRuntimeArtifacts(ProbeResult result)
|
||||
{
|
||||
var applications = result.Applications;
|
||||
var receiveLocations = applications.Sum(x => x.ReceiveLocationTotal);
|
||||
var receiveLocationsDisabled = applications.Sum(x => x.ReceiveLocationDisabled);
|
||||
var sendPorts = applications.Sum(x => x.SendPortTotal);
|
||||
var sendPortsStarted = applications.Sum(x => x.SendPortStarted);
|
||||
var sendPortsStopped = applications.Sum(x => x.SendPortStopped);
|
||||
var sendPortsBound = applications.Sum(x => x.SendPortBound);
|
||||
var sendPortsUnknown = applications.Sum(x => x.SendPortUnknown);
|
||||
var orchestrations = applications.Sum(x => x.OrchestrationTotal);
|
||||
var orchestrationsStarted = applications.Sum(x => x.OrchestrationStarted);
|
||||
var orchestrationsStopped = applications.Sum(x => x.OrchestrationStopped);
|
||||
var orchestrationsBound = applications.Sum(x => x.OrchestrationBound);
|
||||
var orchestrationsUnbound = applications.Sum(x => x.OrchestrationUnbound);
|
||||
var orchestrationsUnknown = applications.Sum(x => x.OrchestrationUnknown);
|
||||
var state = DetermineRuntimeArtifactState(
|
||||
receiveLocationsDisabled,
|
||||
sendPortsStopped + sendPortsBound,
|
||||
sendPortsUnknown,
|
||||
orchestrationsStopped + orchestrationsBound + orchestrationsUnbound,
|
||||
orchestrationsUnknown);
|
||||
var metrics = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"biztalk_applications={0};;;0|biztalk_receive_locations={1};;;0|biztalk_receive_locations_disabled={2};;;0|biztalk_send_ports={3};;;0|biztalk_send_ports_started={4};;;0|biztalk_send_ports_stopped={5};;;0|biztalk_send_ports_bound={6};;;0|biztalk_send_ports_unknown={7};;;0|biztalk_orchestrations={8};;;0|biztalk_orchestrations_started={9};;;0|biztalk_orchestrations_stopped={10};;;0|biztalk_orchestrations_bound={11};;;0|biztalk_orchestrations_unbound={12};;;0|biztalk_orchestrations_unknown={13};;;0",
|
||||
applications.Count,
|
||||
receiveLocations,
|
||||
receiveLocationsDisabled,
|
||||
sendPorts,
|
||||
sendPortsStarted,
|
||||
sendPortsStopped,
|
||||
sendPortsBound,
|
||||
sendPortsUnknown,
|
||||
orchestrations,
|
||||
orchestrationsStarted,
|
||||
orchestrationsStopped,
|
||||
orchestrationsBound,
|
||||
orchestrationsUnbound,
|
||||
orchestrationsUnknown);
|
||||
var detail = BuildRuntimeArtifactDetail(
|
||||
applications,
|
||||
receiveLocations,
|
||||
receiveLocationsDisabled,
|
||||
sendPorts,
|
||||
sendPortsStarted,
|
||||
sendPortsStopped,
|
||||
sendPortsBound,
|
||||
sendPortsUnknown,
|
||||
orchestrations,
|
||||
orchestrationsStarted,
|
||||
orchestrationsStopped,
|
||||
orchestrationsBound,
|
||||
orchestrationsUnbound,
|
||||
orchestrationsUnknown);
|
||||
|
||||
return BuildLine(state, _options.RuntimeArtifactsServiceName, metrics, AppendDiagnostics(detail, result.Diagnostics));
|
||||
}
|
||||
|
||||
private string FormatAggregate(ProbeResult result)
|
||||
{
|
||||
var instances = result.SuspendedInstances;
|
||||
var resumable = instances.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||
var nonresumable = instances.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||
var state = DetermineState(resumable, nonresumable);
|
||||
var metrics = BuildMetrics(instances.Count, resumable, nonresumable);
|
||||
var detail = BuildAggregateDetail(result, instances.Count, resumable, nonresumable);
|
||||
|
||||
return BuildLine(state, _options.ServiceName, metrics, detail);
|
||||
}
|
||||
|
||||
private IEnumerable<string> FormatApplications(ProbeResult result)
|
||||
{
|
||||
foreach (var group in result.SuspendedInstances.GroupBy(x => x.ApplicationName).OrderBy(x => x.Key))
|
||||
{
|
||||
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||
var state = DetermineState(resumable, nonresumable);
|
||||
var serviceName = _options.ServiceName + " " + group.Key;
|
||||
var metrics = BuildMetrics(group.Count(), resumable, nonresumable);
|
||||
var detail = group.Count() + " suspended instance(s): resumable=" + resumable + ", nonresumable=" + nonresumable;
|
||||
yield return BuildLine(state, serviceName, metrics, detail);
|
||||
}
|
||||
}
|
||||
|
||||
private CheckState DetermineState(int resumable, int nonresumable)
|
||||
{
|
||||
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
|
||||
{
|
||||
return CheckState.Critical;
|
||||
}
|
||||
|
||||
if (_options.WarnResumableThreshold > 0 && resumable >= _options.WarnResumableThreshold)
|
||||
{
|
||||
return CheckState.Warn;
|
||||
}
|
||||
|
||||
return CheckState.Ok;
|
||||
}
|
||||
|
||||
private CheckState DetermineHostState(int total, int stopped, int pending, int unknown)
|
||||
{
|
||||
if (total == 0)
|
||||
{
|
||||
return CheckState.Unknown;
|
||||
}
|
||||
|
||||
if (!_options.AlertOnStoppedHostInstances)
|
||||
{
|
||||
return unknown > 0 ? CheckState.Warn : CheckState.Ok;
|
||||
}
|
||||
|
||||
if (stopped > 0 || unknown > 0)
|
||||
{
|
||||
return CheckState.Critical;
|
||||
}
|
||||
|
||||
if (pending > 0)
|
||||
{
|
||||
return CheckState.Warn;
|
||||
}
|
||||
|
||||
return CheckState.Ok;
|
||||
}
|
||||
|
||||
private CheckState DetermineRuntimeArtifactState(int disabledReceiveLocations, int inactiveSendPorts, int unknownSendPorts, int inactiveOrchestrations, int unknownOrchestrations)
|
||||
{
|
||||
if (unknownSendPorts > 0 || unknownOrchestrations > 0)
|
||||
{
|
||||
return CheckState.Critical;
|
||||
}
|
||||
|
||||
if (_options.AlertOnArtifactRuntimeIssues && (disabledReceiveLocations > 0 || inactiveSendPorts > 0 || inactiveOrchestrations > 0))
|
||||
{
|
||||
return CheckState.Warn;
|
||||
}
|
||||
|
||||
return CheckState.Ok;
|
||||
}
|
||||
|
||||
private string BuildMetrics(int total, int resumable, int nonresumable)
|
||||
{
|
||||
return 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);
|
||||
}
|
||||
|
||||
private string BuildAggregateDetail(ProbeResult result, int total, int resumable, int nonresumable)
|
||||
{
|
||||
if (total == 0)
|
||||
{
|
||||
return AppendDiagnostics("No suspended BizTalk service instances found.", result.Diagnostics);
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(total)
|
||||
.Append(" suspended BizTalk service instance(s): resumable=")
|
||||
.Append(resumable)
|
||||
.Append(", nonresumable=")
|
||||
.Append(nonresumable)
|
||||
.Append(", applications=")
|
||||
.Append(result.SuspendedInstances.Select(x => x.ApplicationName).Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
||||
.Append(". ");
|
||||
|
||||
builder.Append(BuildApplicationSummary(result.SuspendedInstances));
|
||||
AppendInstanceHints(builder, result.SuspendedInstances);
|
||||
|
||||
return AppendDiagnostics(builder.ToString(), result.Diagnostics);
|
||||
}
|
||||
|
||||
private string BuildHostDetail(IEnumerable<HostInstanceState> hosts, int total, int started, int stopped, int pending, int unknown)
|
||||
{
|
||||
if (total == 0)
|
||||
{
|
||||
return "No BizTalk host instances returned by WMI.";
|
||||
}
|
||||
|
||||
var detail = "total=" + total
|
||||
+ ", started=" + started
|
||||
+ ", stopped=" + stopped
|
||||
+ ", pending=" + pending
|
||||
+ ", unknown=" + unknown;
|
||||
|
||||
var notStarted = hosts
|
||||
.Where(x => x.ServiceState != HostStarted)
|
||||
.OrderBy(x => x.RunningServer)
|
||||
.ThenBy(x => x.HostName)
|
||||
.ThenBy(x => x.InstanceName)
|
||||
.Take(12)
|
||||
.Select(x => EmptyAsUnknown(x.HostName) + "@" + EmptyAsUnknown(x.RunningServer) + "=" + FormatHostState(x.ServiceState));
|
||||
|
||||
var notStartedText = string.Join("; ", notStarted);
|
||||
if (!string.IsNullOrWhiteSpace(notStartedText))
|
||||
{
|
||||
detail += " | Not started: " + notStartedText;
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
private string BuildRuntimeArtifactDetail(
|
||||
IEnumerable<ApplicationRuntimeState> applications,
|
||||
int receiveLocations,
|
||||
int receiveLocationsDisabled,
|
||||
int sendPorts,
|
||||
int sendPortsStarted,
|
||||
int sendPortsStopped,
|
||||
int sendPortsBound,
|
||||
int sendPortsUnknown,
|
||||
int orchestrations,
|
||||
int orchestrationsStarted,
|
||||
int orchestrationsStopped,
|
||||
int orchestrationsBound,
|
||||
int orchestrationsUnbound,
|
||||
int orchestrationsUnknown)
|
||||
{
|
||||
var appList = applications.ToList();
|
||||
var detail = "applications=" + appList.Count
|
||||
+ " | ReceiveLocations total=" + receiveLocations + ", disabled=" + receiveLocationsDisabled
|
||||
+ " | SendPorts total=" + sendPorts + ", started=" + sendPortsStarted + ", stopped=" + sendPortsStopped + ", bound=" + sendPortsBound + ", unknown=" + sendPortsUnknown
|
||||
+ " | Orchestrations total=" + orchestrations + ", started=" + orchestrationsStarted + ", stopped=" + orchestrationsStopped + ", bound=" + orchestrationsBound + ", unbound=" + orchestrationsUnbound + ", unknown=" + orchestrationsUnknown;
|
||||
|
||||
var notable = appList
|
||||
.Where(HasRuntimeDifference)
|
||||
.OrderByDescending(x => x.SendPortUnknown + x.OrchestrationUnknown)
|
||||
.ThenByDescending(x => x.ReceiveLocationDisabled + x.SendPortStopped + x.SendPortBound + x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
|
||||
.ThenBy(x => x.ApplicationName)
|
||||
.Take(_options.MaxRuntimeApplicationDetails)
|
||||
.Select(FormatApplicationRuntime);
|
||||
|
||||
var notableText = string.Join("; ", notable);
|
||||
if (!string.IsNullOrWhiteSpace(notableText))
|
||||
{
|
||||
detail += " | Notable apps: " + notableText;
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
private string BuildApplicationSummary(IEnumerable<SuspendedInstance> instances)
|
||||
{
|
||||
var groups = instances
|
||||
.GroupBy(x => x.ApplicationName)
|
||||
.OrderByDescending(x => x.Count(y => y.Kind == SuspendedKind.NonResumable))
|
||||
.ThenByDescending(x => x.Count())
|
||||
.ThenBy(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
var shown = groups.Take(_options.MaxApplicationDetails)
|
||||
.Select(group => group.Key + " R=" + group.Count(x => x.Kind == SuspendedKind.Resumable) + " NR=" + group.Count(x => x.Kind == SuspendedKind.NonResumable));
|
||||
|
||||
var summary = string.Join("; ", shown);
|
||||
if (groups.Count > _options.MaxApplicationDetails)
|
||||
{
|
||||
summary += "; +" + (groups.Count - _options.MaxApplicationDetails).ToString(CultureInfo.InvariantCulture) + " more application(s)";
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
private static void AppendInstanceHints(StringBuilder builder, IEnumerable<SuspendedInstance> instances)
|
||||
{
|
||||
var hints = instances
|
||||
.OrderByDescending(x => x.Kind == SuspendedKind.NonResumable)
|
||||
.ThenBy(x => x.ApplicationName)
|
||||
.Take(5)
|
||||
.Select(x => "host=" + EmptyAsUnknown(x.HostName) + ", service=" + EmptyAsUnknown(x.ServiceName) + ", id=" + EmptyAsUnknown(x.InstanceId));
|
||||
|
||||
builder.Append("\\nExamples: ").Append(string.Join(" | ", hints));
|
||||
}
|
||||
|
||||
private static bool HasRuntimeDifference(ApplicationRuntimeState app)
|
||||
{
|
||||
return app.ReceiveLocationDisabled > 0
|
||||
|| app.SendPortStopped > 0
|
||||
|| app.SendPortBound > 0
|
||||
|| app.SendPortUnknown > 0
|
||||
|| app.OrchestrationStopped > 0
|
||||
|| app.OrchestrationBound > 0
|
||||
|| app.OrchestrationUnbound > 0
|
||||
|| app.OrchestrationUnknown > 0;
|
||||
}
|
||||
|
||||
private static string FormatApplicationRuntime(ApplicationRuntimeState app)
|
||||
{
|
||||
return app.ApplicationName
|
||||
+ " RL disabled=" + app.ReceiveLocationDisabled
|
||||
+ ", SP stopped/bound/unknown=" + app.SendPortStopped + "/" + app.SendPortBound + "/" + app.SendPortUnknown
|
||||
+ ", Orch stopped/bound/unbound/unknown=" + app.OrchestrationStopped + "/" + app.OrchestrationBound + "/" + app.OrchestrationUnbound + "/" + app.OrchestrationUnknown;
|
||||
}
|
||||
|
||||
private static string FormatHostState(int state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case HostStarted: return "Started";
|
||||
case HostStopped: return "Stopped";
|
||||
case HostStartPending: return "StartPending";
|
||||
case HostStopPending: return "StopPending";
|
||||
default: return "Unknown " + state;
|
||||
}
|
||||
}
|
||||
|
||||
private static string AppendDiagnostics(string detail, IEnumerable<string> diagnostics)
|
||||
{
|
||||
var diagnosticText = string.Join("; ", diagnostics.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||
if (string.IsNullOrWhiteSpace(diagnosticText))
|
||||
{
|
||||
return detail;
|
||||
}
|
||||
|
||||
return detail + "\\nDiagnostics: " + diagnosticText;
|
||||
}
|
||||
|
||||
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
|
||||
{
|
||||
return ((int)state).ToString(CultureInfo.InvariantCulture)
|
||||
+ " \""
|
||||
+ SanitizeServiceName(serviceName)
|
||||
+ "\" "
|
||||
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
|
||||
+ " "
|
||||
+ SanitizeDetail(detail);
|
||||
}
|
||||
|
||||
private static string SanitizeServiceName(string value)
|
||||
{
|
||||
var sanitized = string.IsNullOrWhiteSpace(value) ? "BizTalk Suspended Instances" : value.Trim();
|
||||
var invalid = new[] { ';', '~', '!', '$', '%', '^', '&', '*', '|', '\\', '\'', '"', '<', '>', '?', ',', '(', ')', '=', '`' };
|
||||
foreach (var item in invalid)
|
||||
{
|
||||
sanitized = sanitized.Replace(item, ' ');
|
||||
}
|
||||
|
||||
return CollapseSpaces(sanitized);
|
||||
}
|
||||
|
||||
private static string SanitizeDetail(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "No details.";
|
||||
}
|
||||
|
||||
return value.Replace("\r\n", "\\n").Replace("\r", "\\n").Replace("\n", "\\n");
|
||||
}
|
||||
|
||||
private static string EmptyAsUnknown(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "<unknown>" : value.Trim();
|
||||
}
|
||||
|
||||
private static string CollapseSpaces(string value)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var previousSpace = false;
|
||||
foreach (var character in value)
|
||||
{
|
||||
if (char.IsWhiteSpace(character))
|
||||
{
|
||||
if (!previousSpace)
|
||||
{
|
||||
builder.Append(' ');
|
||||
previousSpace = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(character);
|
||||
previousSpace = false;
|
||||
}
|
||||
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
internal enum CheckState
|
||||
{
|
||||
Ok = 0,
|
||||
Warn = 1,
|
||||
Critical = 2,
|
||||
Unknown = 3
|
||||
}
|
||||
|
||||
internal enum SuspendedKind
|
||||
{
|
||||
Resumable,
|
||||
NonResumable
|
||||
}
|
||||
|
||||
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 ErrorId { get; set; }
|
||||
|
||||
public string ErrorCategory { get; set; }
|
||||
|
||||
public string ErrorDescription { get; set; }
|
||||
|
||||
public string SuspendTime { get; set; }
|
||||
|
||||
public int ServiceStatus { get; set; }
|
||||
|
||||
public SuspendedKind Kind { 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 ApplicationRuntimeState
|
||||
{
|
||||
public ApplicationRuntimeState()
|
||||
{
|
||||
ReceiveLocationTotal = 0;
|
||||
SendPortTotal = 0;
|
||||
OrchestrationTotal = 0;
|
||||
}
|
||||
|
||||
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 ProbeResult
|
||||
{
|
||||
public ProbeResult()
|
||||
{
|
||||
SuspendedInstances = new List<SuspendedInstance>();
|
||||
HostInstances = new List<HostInstanceState>();
|
||||
Applications = new List<ApplicationRuntimeState>();
|
||||
Diagnostics = new List<string>();
|
||||
}
|
||||
|
||||
public List<SuspendedInstance> SuspendedInstances { get; private set; }
|
||||
|
||||
public List<HostInstanceState> HostInstances { get; private set; }
|
||||
|
||||
public List<ApplicationRuntimeState> Applications { get; private set; }
|
||||
|
||||
public List<string> Diagnostics { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
internal sealed class MonitoringOptions
|
||||
{
|
||||
public MonitoringOptions()
|
||||
{
|
||||
ServiceName = "BizTalk Suspended Instances";
|
||||
HostInstancesServiceName = "BizTalk Host Instances";
|
||||
RuntimeArtifactsServiceName = "BizTalk Runtime Artifacts";
|
||||
Server = ".";
|
||||
WarnResumableThreshold = 1;
|
||||
CritNonResumableThreshold = 1;
|
||||
QueryTimeoutSeconds = 25;
|
||||
MaxApplicationDetails = 12;
|
||||
MaxRuntimeApplicationDetails = 10;
|
||||
AlertOnStoppedHostInstances = true;
|
||||
AlertOnArtifactRuntimeIssues = false;
|
||||
EmitPerApplicationServices = false;
|
||||
}
|
||||
|
||||
public string ServiceName { get; set; }
|
||||
|
||||
public string HostInstancesServiceName { get; set; }
|
||||
|
||||
public string RuntimeArtifactsServiceName { get; set; }
|
||||
|
||||
public string Server { get; set; }
|
||||
|
||||
public int WarnResumableThreshold { get; set; }
|
||||
|
||||
public int CritNonResumableThreshold { get; set; }
|
||||
|
||||
public int QueryTimeoutSeconds { get; set; }
|
||||
|
||||
public int MaxApplicationDetails { get; set; }
|
||||
|
||||
public int MaxRuntimeApplicationDetails { get; set; }
|
||||
|
||||
public bool AlertOnStoppedHostInstances { get; set; }
|
||||
|
||||
public bool AlertOnArtifactRuntimeIssues { get; set; }
|
||||
|
||||
public bool EmitPerApplicationServices { get; set; }
|
||||
|
||||
public bool SelfTest { get; set; }
|
||||
|
||||
public static MonitoringOptions Load(string[] args)
|
||||
{
|
||||
var options = new MonitoringOptions
|
||||
{
|
||||
ServiceName = ReadString("ServiceName", "BizTalk Suspended Instances"),
|
||||
HostInstancesServiceName = ReadString("HostInstancesServiceName", "BizTalk Host Instances"),
|
||||
RuntimeArtifactsServiceName = ReadString("RuntimeArtifactsServiceName", "BizTalk Runtime Artifacts"),
|
||||
Server = ReadString("Server", "."),
|
||||
WarnResumableThreshold = ReadInt("WarnResumableThreshold", 1, 0, 1000000),
|
||||
CritNonResumableThreshold = ReadInt("CritNonResumableThreshold", 1, 0, 1000000),
|
||||
QueryTimeoutSeconds = ReadInt("QueryTimeoutSeconds", 25, 3, 300),
|
||||
MaxApplicationDetails = ReadInt("MaxApplicationDetails", 12, 1, 100),
|
||||
MaxRuntimeApplicationDetails = ReadInt("MaxRuntimeApplicationDetails", 10, 1, 100),
|
||||
AlertOnStoppedHostInstances = ReadBool("AlertOnStoppedHostInstances", true),
|
||||
AlertOnArtifactRuntimeIssues = ReadBool("AlertOnArtifactRuntimeIssues", false),
|
||||
EmitPerApplicationServices = ReadBool("EmitPerApplicationServices", false)
|
||||
};
|
||||
|
||||
ApplyArguments(options, args ?? new string[0]);
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void ApplyArguments(MonitoringOptions options, string[] args)
|
||||
{
|
||||
var map = new Dictionary<string, Action<string>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "--service-name", value => options.ServiceName = RequireValue("--service-name", value) },
|
||||
{ "--host-service-name", value => options.HostInstancesServiceName = RequireValue("--host-service-name", value) },
|
||||
{ "--runtime-service-name", value => options.RuntimeArtifactsServiceName = RequireValue("--runtime-service-name", value) },
|
||||
{ "--server", value => options.Server = RequireValue("--server", value) },
|
||||
{ "--warn-resumable", value => options.WarnResumableThreshold = ParseInt("--warn-resumable", value, 0, 1000000) },
|
||||
{ "--crit-nonresumable", value => options.CritNonResumableThreshold = ParseInt("--crit-nonresumable", value, 0, 1000000) },
|
||||
{ "--timeout-seconds", value => options.QueryTimeoutSeconds = ParseInt("--timeout-seconds", value, 3, 300) },
|
||||
{ "--max-application-details", value => options.MaxApplicationDetails = ParseInt("--max-application-details", value, 1, 100) },
|
||||
{ "--max-runtime-application-details", value => options.MaxRuntimeApplicationDetails = ParseInt("--max-runtime-application-details", value, 1, 100) }
|
||||
};
|
||||
|
||||
for (var index = 0; index < args.Length; index++)
|
||||
{
|
||||
var arg = args[index];
|
||||
if (string.Equals(arg, "--self-test", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.SelfTest = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(arg, "--emit-per-application-services", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.EmitPerApplicationServices = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(arg, "--no-host-alerts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.AlertOnStoppedHostInstances = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(arg, "--alert-on-artifact-runtime-issues", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.AlertOnArtifactRuntimeIssues = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Action<string> setter;
|
||||
if (!map.TryGetValue(arg, out setter))
|
||||
{
|
||||
throw new ArgumentException("Unknown argument: " + arg);
|
||||
}
|
||||
|
||||
if (index + 1 >= args.Length)
|
||||
{
|
||||
throw new ArgumentException("Missing value for argument: " + arg);
|
||||
}
|
||||
|
||||
setter(args[++index]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadString(string key, string fallback)
|
||||
{
|
||||
var value = ConfigurationManager.AppSettings[key];
|
||||
return string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
||||
}
|
||||
|
||||
private static int ReadInt(string key, int fallback, int min, int max)
|
||||
{
|
||||
return ParseInt(key, ConfigurationManager.AppSettings[key], min, max, fallback);
|
||||
}
|
||||
|
||||
private static bool ReadBool(string key, bool fallback)
|
||||
{
|
||||
var value = ConfigurationManager.AppSettings[key];
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool parsed;
|
||||
return bool.TryParse(value, out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
private static int ParseInt(string name, string value, int min, int max)
|
||||
{
|
||||
return ParseInt(name, value, min, max, null);
|
||||
}
|
||||
|
||||
private static int ParseInt(string name, string value, int min, int max, int? fallback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (fallback.HasValue)
|
||||
{
|
||||
return fallback.Value;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Missing integer value for " + name);
|
||||
}
|
||||
|
||||
int parsed;
|
||||
if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed))
|
||||
{
|
||||
if (fallback.HasValue)
|
||||
{
|
||||
return fallback.Value;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid integer value for " + name + ": " + value);
|
||||
}
|
||||
|
||||
if (parsed < min || parsed > max)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, "Value must be between " + min + " and " + max + ".");
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static string RequireValue(string name, string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException("Missing value for " + name);
|
||||
}
|
||||
|
||||
return value.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
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 CheckmkFormatter(options);
|
||||
|
||||
if (options.SelfTest)
|
||||
{
|
||||
Console.WriteLine(formatter.FormatSelfTest());
|
||||
return 0;
|
||||
}
|
||||
|
||||
var probe = new WmiBizTalkProbe(options);
|
||||
var result = probe.Query();
|
||||
foreach (var line in formatter.Format(result))
|
||||
{
|
||||
Console.WriteLine(line);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var fallbackOptions = options ?? new MonitoringOptions();
|
||||
var formatter = new CheckmkFormatter(fallbackOptions);
|
||||
Console.WriteLine(formatter.FormatUnknown("BizTalk suspended instance check failed: " + ex.GetType().Name + ": " + ex.Message));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
|
||||
namespace BizTalkSuspendedInstancesCheck
|
||||
{
|
||||
internal sealed class WmiBizTalkProbe
|
||||
{
|
||||
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 const int SuspendedNonResumable = 32;
|
||||
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
|
||||
private const string UnknownApplication = "(Unknown Application)";
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
public WmiBizTalkProbe(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ProbeResult Query()
|
||||
{
|
||||
var result = new ProbeResult();
|
||||
var server = string.IsNullOrWhiteSpace(_options.Server) || _options.Server == "."
|
||||
? Environment.MachineName
|
||||
: _options.Server.Trim();
|
||||
|
||||
var scope = new ManagementScope("\\\\" + server + "\\" + NamespacePath);
|
||||
scope.Connect();
|
||||
|
||||
var index = BuildApplicationIndex(scope, result);
|
||||
QueryHostInstances(scope, result);
|
||||
QueryApplicationRuntime(scope, result);
|
||||
ForEachObject(scope, "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
|
||||
{
|
||||
result.SuspendedInstances.Add(ToSuspendedInstance(item, index));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ApplicationIndex BuildApplicationIndex(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
var index = new ApplicationIndex();
|
||||
AddArtifacts(scope, result, index, "MSBTS_SendPort");
|
||||
AddArtifacts(scope, result, index, "MSBTS_ReceivePort");
|
||||
AddArtifacts(scope, result, index, "MSBTS_ReceiveLocation");
|
||||
AddArtifacts(scope, result, index, "MSBTS_Orchestration");
|
||||
return index;
|
||||
}
|
||||
|
||||
private void AddArtifacts(ManagementScope scope, ProbeResult result, ApplicationIndex index, string className)
|
||||
{
|
||||
try
|
||||
{
|
||||
ForEachObject(scope, "SELECT * FROM " + className, item =>
|
||||
{
|
||||
index.Add(item);
|
||||
});
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
{
|
||||
result.Diagnostics.Add("Application mapping skipped for " + className + ": " + ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
result.Diagnostics.Add("Application mapping skipped for " + className + ": " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryHostInstances(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
ForEachObject(scope, "SELECT * FROM MSBTS_HostInstance", item =>
|
||||
{
|
||||
result.HostInstances.Add(new HostInstanceState
|
||||
{
|
||||
InstanceName = FirstNonEmpty(
|
||||
ApplicationIndex.SafeGetString(item, "InstanceName"),
|
||||
ApplicationIndex.SafeGetString(item, "Name")),
|
||||
HostName = ApplicationIndex.SafeGetString(item, "HostName"),
|
||||
RunningServer = ApplicationIndex.SafeGetString(item, "RunningServer"),
|
||||
ServiceState = SafeGetInt32(item, "ServiceState", 0)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
var applications = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
ForEachObject(scope, "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
||||
{
|
||||
var app = GetApplication(applications, GetApplicationName(item));
|
||||
app.ReceiveLocationTotal++;
|
||||
if (SafeGetBoolean(item, "IsDisabled", false))
|
||||
{
|
||||
app.ReceiveLocationDisabled++;
|
||||
}
|
||||
});
|
||||
|
||||
ForEachObject(scope, "SELECT * FROM MSBTS_SendPort", item =>
|
||||
{
|
||||
var app = GetApplication(applications, GetApplicationName(item));
|
||||
var status = SafeGetInt32(item, "Status", 0);
|
||||
app.SendPortTotal++;
|
||||
switch (status)
|
||||
{
|
||||
case SendPortStarted:
|
||||
app.SendPortStarted++;
|
||||
break;
|
||||
case SendPortStopped:
|
||||
app.SendPortStopped++;
|
||||
break;
|
||||
case SendPortBound:
|
||||
app.SendPortBound++;
|
||||
break;
|
||||
default:
|
||||
app.SendPortUnknown++;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
ForEachObject(scope, "SELECT * FROM MSBTS_Orchestration", item =>
|
||||
{
|
||||
var app = GetApplication(applications, GetApplicationName(item));
|
||||
var status = SafeGetInt32(item, "OrchestrationStatus", 0);
|
||||
app.OrchestrationTotal++;
|
||||
switch (status)
|
||||
{
|
||||
case OrchestrationStarted:
|
||||
app.OrchestrationStarted++;
|
||||
break;
|
||||
case OrchestrationStopped:
|
||||
app.OrchestrationStopped++;
|
||||
break;
|
||||
case OrchestrationBound:
|
||||
app.OrchestrationBound++;
|
||||
break;
|
||||
case OrchestrationUnbound:
|
||||
app.OrchestrationUnbound++;
|
||||
break;
|
||||
default:
|
||||
app.OrchestrationUnknown++;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
result.Applications.AddRange(applications.Values.OrderBy(x => x.ApplicationName));
|
||||
}
|
||||
|
||||
private void ForEachObject(ManagementScope scope, string queryText, Action<ManagementObject> action)
|
||||
{
|
||||
var queryOptions = new EnumerationOptions
|
||||
{
|
||||
ReturnImmediately = true,
|
||||
Rewindable = false,
|
||||
Timeout = TimeSpan.FromSeconds(_options.QueryTimeoutSeconds),
|
||||
Context = new ManagementNamedValueCollection()
|
||||
};
|
||||
|
||||
queryOptions.Context.Add("IterationDelayMS", 10);
|
||||
|
||||
var query = new ObjectQuery(queryText);
|
||||
using (var searcher = new ManagementObjectSearcher(scope, query, queryOptions))
|
||||
using (var collection = searcher.Get())
|
||||
{
|
||||
foreach (ManagementObject item in collection)
|
||||
{
|
||||
using (item)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SuspendedInstance ToSuspendedInstance(ManagementBaseObject item, ApplicationIndex index)
|
||||
{
|
||||
var status = SafeGetInt32(item, "ServiceStatus", 0);
|
||||
return new SuspendedInstance
|
||||
{
|
||||
ApplicationName = index.Resolve(item),
|
||||
ServiceName = ApplicationIndex.SafeGetString(item, "ServiceName"),
|
||||
HostName = ApplicationIndex.SafeGetString(item, "HostName"),
|
||||
ServiceClass = ApplicationIndex.SafeGetString(item, "ServiceClass"),
|
||||
InstanceId = ApplicationIndex.SafeGetString(item, "InstanceID"),
|
||||
ErrorId = ApplicationIndex.SafeGetString(item, "ErrorID"),
|
||||
ErrorCategory = ApplicationIndex.SafeGetString(item, "ErrorCategory"),
|
||||
ErrorDescription = ApplicationIndex.SafeGetString(item, "ErrorDescription"),
|
||||
SuspendTime = ApplicationIndex.SafeGetString(item, "SuspendTime"),
|
||||
ServiceStatus = status,
|
||||
Kind = status == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable
|
||||
};
|
||||
}
|
||||
|
||||
private static int SafeGetInt32(ManagementBaseObject item, string propertyName, int fallback)
|
||||
{
|
||||
var value = ApplicationIndex.SafeGetString(item, propertyName);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int parsed;
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
private static bool SafeGetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
|
||||
{
|
||||
var value = ApplicationIndex.SafeGetString(item, propertyName);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool parsed;
|
||||
return bool.TryParse(value, out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
private static string GetApplicationName(ManagementBaseObject item)
|
||||
{
|
||||
return FirstNonEmpty(
|
||||
ApplicationIndex.SafeGetString(item, "ApplicationName"),
|
||||
ApplicationIndex.SafeGetString(item, "Application"),
|
||||
ApplicationIndex.SafeGetString(item, "BizTalkApplication"))
|
||||
?? UnknownApplication;
|
||||
}
|
||||
|
||||
private static ApplicationRuntimeState GetApplication(Dictionary<string, ApplicationRuntimeState> applications, string applicationName)
|
||||
{
|
||||
var normalized = string.IsNullOrWhiteSpace(applicationName) ? UnknownApplication : applicationName.Trim();
|
||||
ApplicationRuntimeState app;
|
||||
if (!applications.TryGetValue(normalized, out app))
|
||||
{
|
||||
app = new ApplicationRuntimeState { ApplicationName = normalized };
|
||||
applications.Add(normalized, app);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user