Lightweight health and telemetry pulse aggregation.
PulseGuard collects two kinds of data:
- Pulse Checks (synthetic/endpoint health) – single HTTP GET requests executed per configured target.
- Agent Checks (resource / performance metrics) – queries against external telemetry stores for one or more applications.
Each configuration instance is stored (table storage attributes visible in the code) and executed by hosted background services.
PulseConfiguration fields of interest:
- Group / Name: identity / partition.
- Location: target URL (HTTP GET).
- Type: one of the below enum values.
- Timeout / DegrationTimeout: execution & optional degraded-state threshold (ms).
- IgnoreSslErrors: opt-in to bypass certificate validation (flag placed on request options).
- ComparisonValue: auxiliary value used by some check types (JSON / Contains).
- Headers: semicolon separated custom headers (Key:Value;Key:Value).
Result model: PulseReport(State, Message, Error) where State ∈ Healthy | Unhealthy | TimedOut.
Expected response body: JSON matching HealthApiResponse:
{
"state": "Healthy | Degraded | Unhealthy | TimedOut | ...",
"message": "optional",
"dependencies": [ { "name": "...", "state": "..." }, ... ]
}
Processing:
- Body deserialized; extra properties stripped by re-serialization.
- If state == Healthy the raw payload is discarded (saves storage) and report marked Healthy.
- Any deserialization issue -> Fail with original (capped) body.
Success condition: HTTP status 2xx.
Failure: non-success status. Body (if readable) captured into Error for diagnostics.
Use when you want the target endpoint JSON to contain (as a structural subset) a specific JSON fragment.
ComparisonValuemust be valid JSON (subset template).- Entire response body must be JSON.
- Implementation builds JObjects, computes set intersection and compares. Fail reasons: empty body, invalid JSON, or subset mismatch.
Plain substring presence check on the response body (case-sensitive string.Contains).
ComparisonValueis the required substring. Fail reasons: empty body or substring not found.
Simplified textual health probe.
- Expects the body to be exactly a
PulseStatestoken (e.g. "Healthy", "Unhealthy", etc.). - If value parses and is Healthy -> success (body dropped). Otherwise failure with derived message. Fail reasons: null body or unknown token.
Expected response body: JSON matching StatusApiResponse:
{
"status": "Healthy | ...",
"details": { /* optional */ },
"entries": { /* optional */ }
}
Processing identical pattern to HealthApi: deserialize, strip extras, evaluate root status.
Fail reasons: null body, deserialization error.
- Core execution: a single GET request using shared
HttpClient. - Timeout & degraded logic (if implemented in hosting layer) will convert to
TimedOutviaPulseReport.TimedOut. - Healthy responses intentionally omit large payload storage for efficiency.
PulseAgentConfiguration fields:
- Sqid: grouping key.
- Type: one of the AgentCheckType values (string persisted, enum used at runtime).
- Location: endpoint / workspace specific URI or identifier (see per type).
- ApplicationName: logical application (used for correlation & query filtering).
- Headers: optional custom headers (POST queries / data-plane auth if needed).
Result model: PulseAgentReport(Options, CpuPercentage, Memory, InputOutput) – null metric values indicate unavailable / failed extraction.
Purpose: Pull recent (last 10 minutes) CPU, Memory %, IO metrics via the Application Insights Logs API. Mechanics:
- Issues a POST with a Kusto query against
performanceCountersselecting the latest minute sample. - CPU:
% Processor Time Normalized. - Memory: calculated as
Private Bytes / Available Bytes * 100. - IO:
IO Data Bytes/sec. - LargerThanZero helper nulls non-positive results. Failure Modes:
- Null / unreadable response.
- JSON deserialization issues (response must map to
ApplicationInsightsQueryResponse). - Empty tables / rows.
On failure returns
PulseAgentReport.Fail(all metrics null) per configuration.
Configuration Notes:
Locationmust be the Application Insights query endpoint (e.g.https://api.applicationinsights.io/v1/apps/{appId}/query).- Provide required API key header (e.g.
x-api-key:{key}) inHeadersor use an injected auth handler.
Purpose: Query a Log Analytics workspace for multiple applications at once. Mechanics:
- Builds a dynamic Kusto query over
AppPerformanceCountersfor all configuredApplicationNamevalues. - Workspace Id taken from first configuration's
Location. - Uses
DefaultAzureCredentialandLogsQueryClient(Managed Identity / VS / Azure CLI chain). - Produces a row per application with CPU, Memory %, IO (same calculations as above) and maps back to the correct configuration by
AppRoleName. Failure Modes: - Exception during auth/query (logged as warning).
- No tables / rows returned.
Returns a collection of
PulseAgentReportorFaillist when unsuccessful.
Configuration Notes:
Locationmust be the Workspace Id (GUID string).- Ensure the executing environment identity has
Log Analytics Readeron the workspace.
Purpose: Monitor Azure Web App deployment history and status. Mechanics:
- Queries Azure App Service deployment records via ARM API using
ArmClient. - Retrieves deployments within a 2-hour rolling window.
- Extracts deployment metadata: author, status (NotDeployed / InProgress / Succeeded / PartiallySucceeded / Failed), start/end times.
- Attempts to enrich deployment data with commit ID, build number, and deployment type from deployment message JSON.
- Returns
DeploymentAgentReportper deployment containing timestamp, status, and optional enrichments. Failure Modes: - Web App resource not found (logged as error).
- Missing or incomplete deployment data (skipped).
- Deserialization failures on deployment message (logged as warning, enrichments omitted).
Configuration Notes:
SubscriptionIdmust contain the Azure subscription ID.Locationmust be the resource group name.ApplicationNamemust be the Web App name.- Ensure the executing environment identity has
Readerrole on the Web App resource.
Purpose: Monitor Azure DevOps environment deployment records. Mechanics:
- Queries Azure DevOps Distributed Task API for environment deployment records within a 2-hour rolling window.
- Filters by
BuildDefinitionIdto track specific pipeline deployments. - Enriches deployment data by calling the Builds API to retrieve author, commit ID, and build number.
- Returns
DeploymentAgentReportper deployment with result status, timestamps, and metadata. Failure Modes: - API call failures (non-success status codes logged as errors).
- Missing deployment or build data (skipped or partial data returned).
- Deserialization failures (logged as errors).
Configuration Notes:
Locationmust be the Azure DevOps project name.ApplicationNamemust be the team name.SubscriptionIdmust be the environment ID (numeric).BuildDefinitionIdmust be set to filter deployments by pipeline.- Provide Personal Access Token (PAT) with
Readaccess to Environments, Deployments, and Builds inHeaders(e.g.,Authorization:Basic <base64-encoded-PAT>).
Webhooks allow external systems to be notified when pulse events occur.
Webhook entity fields:
- Id: unique identifier.
- Secret: authentication token for the webhook endpoint.
- Group / Name: filters which pulses trigger this webhook.
- Location: target URL to POST notifications.
- Enabled: activation flag.
- Type: one of
WebhookTypevalues (All / StateChange / ThresholdBreach).
- All: Triggered for any webhook type.
- StateChange: Triggered only when pulse state transitions (Healthy ↔ Unhealthy).
- ThresholdBreach: Triggered when consecutive failures reach the configured
AlertThreshold.
The AlertThreshold (configured in PulseOptions) defines the number of consecutive unhealthy pulse results that trigger a critical alert and webhook notification for ThresholdBreach webhooks.
- When a pulse fails, its failure counter (
PulseCounter.Value) increments. - When
ValueequalsAlertThreshold, a critical log event is emitted and webhooks of typeThresholdBreachare invoked. - The webhook payload includes the pulse configuration, failure count, and timestamp of the first failure in the sequence.
- Use this to implement escalation policies or integrate with incident management systems.
Configuration Example:
{
"Pulse": {
"AlertThreshold": 3
}
}Both configuration types serialize headers as: Name:Value;Another-Header:OtherValue (no trailing semicolon). They are added with TryAddWithoutValidation.
- Extend
PulseCheckTypeenum & fast string helper. - Implement a subclass of
PulseCheckoverridingCreateReport. - Register it inside
PulseCheckFactoryswitch. - Handle any serialization context updates if new models are introduced.
- Extend
AgentCheckTypeenum & fast string helper. - Implement an
AgentChecksubclass returningIReadOnlyList<PulseAgentReport>. - Add case to
AgentCheckFactory. - Define configuration expectations (Location meaning, required headers / auth).
- Fail fast with clear diagnostic message.
- Log deserialization and transport issues with category-specific event ids.
- Avoid storing large healthy payloads to reduce storage cost.
- Optional SSL ignore flag for scenarios with self-signed certs (use sparingly in production).
- Auth to external telemetry (AI / Log Analytics) is supplied via headers (API key) or
DefaultAzureCredential.
ComparisonValue:
{"status":"ok","version":"1.0"}
Target response may contain superset:
{"status":"ok","version":"1.0","uptime":12345,"extra":"ignored"}
Check passes because comparison subset matches intersection.
PulseConfiguration (StatusCode):
Group=Core
Name=Homepage
Location=https://example.com/
Type=StatusCode
Timeout=5000
Enabled=true
Headers=User-Agent:PulseGuard
PulseAgentConfiguration (ApplicationInsights):
Sqid=cluster-a
Type=ApplicationInsights
Location=https://api.applicationinsights.io/v1/apps/<appId>/query
ApplicationName=api-service-a
Enabled=true
Headers=x-api-key:XXXX
PulseAgentConfiguration (WebAppDeployment):
Sqid=web-deployments
Type=WebAppDeployment
SubscriptionId=<subscription-id>
Location=my-resource-group
ApplicationName=my-web-app
Enabled=true
PulseAgentConfiguration (DevOpsDeployment):
Sqid=pipeline-deployments
Type=DevOpsDeployment
Location=MyProject
ApplicationName=MyTeam
SubscriptionId=123
BuildDefinitionId=456
Enabled=true
Headers=Authorization:Basic <base64-encoded-PAT>
- Factories isolate enum → implementation mapping; missing mapping yields
ArgumentOutOfRangeExceptionensuring misconfiguration surfaces early. - Fast string helpers provide O(1) enum parsing without reflection; keep them updated when extending.