From cfc412d5fffc08300783ecbdcc0fe9826d0ce79c Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Tue, 14 Apr 2026 10:15:01 +0200 Subject: [PATCH] added SLA telemetry --- .../AutopilotMonitor.Agent/buildcounter.txt | 2 +- .../Functions/Metrics/SlaMetricsFunction.cs | 38 ++++++++++++++++--- .../tenant/sections/SectionSlaTargets.tsx | 35 ++++++++++++++--- .../autopilot-monitor-web/app/sla/page.tsx | 31 +++++++++++++-- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 56f639a0..be1c55a3 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -951 +952 diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/SlaMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/SlaMetricsFunction.cs index f3fffe6d..e6d66825 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/SlaMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/SlaMetricsFunction.cs @@ -1,6 +1,7 @@ using System.Net; using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; +using Microsoft.ApplicationInsights; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -15,13 +16,16 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { private readonly ILogger _logger; private readonly SlaMetricsService _slaMetricsService; + private readonly TelemetryClient _telemetryClient; public SlaMetricsFunction( ILogger logger, - SlaMetricsService slaMetricsService) + SlaMetricsService slaMetricsService, + TelemetryClient telemetryClient) { _logger = logger; _slaMetricsService = slaMetricsService; + _telemetryClient = telemetryClient; } [Function("GetSlaMetrics")] @@ -31,21 +35,34 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { _logger.LogInformation("SLA metrics requested"); + string tenantId = TenantHelper.GetTenantId(req); + int months = 3; + bool fresh = false; + try { - string tenantId = TenantHelper.GetTenantId(req); - - // Parse query parameters var qs = System.Web.HttpUtility.ParseQueryString(req.Url.Query ?? ""); - int months = 3; if (int.TryParse(qs.Get("months"), out var parsedMonths)) months = parsedMonths; - bool fresh = qs.Get("fresh") == "1"; + fresh = qs.Get("fresh") == "1"; var metrics = await _slaMetricsService.ComputeSlaMetricsAsync(tenantId, months, fresh); + _telemetryClient.TrackEvent("SlaMetricsRequested", new Dictionary + { + ["TenantId"] = tenantId, + ["Months"] = months.ToString(), + ["Fresh"] = fresh.ToString(), + ["FromCache"] = metrics.FromCache.ToString(), + ["ComputeDurationMs"] = metrics.ComputeDurationMs.ToString(), + ["SuccessRate"] = metrics.CurrentMonth.SuccessRate.ToString("F4"), + ["P95DurationMinutes"] = metrics.CurrentMonth.P95DurationMinutes.ToString("F2"), + ["ViolatorCount"] = metrics.Violators.Count.ToString(), + ["TotalCompleted"] = metrics.CurrentMonth.TotalCompleted.ToString() + }); + var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(metrics); return response; @@ -54,6 +71,15 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { _logger.LogError(ex, "Error computing SLA metrics"); + _telemetryClient.TrackEvent("SlaMetricsFailed", new Dictionary + { + ["TenantId"] = tenantId, + ["Months"] = months.ToString(), + ["Fresh"] = fresh.ToString(), + ["Error"] = ex.GetType().Name, + ["Message"] = ex.Message + }); + var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError); await errorResponse.WriteAsJsonAsync(new { diff --git a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionSlaTargets.tsx b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionSlaTargets.tsx index be6e5474..6969c331 100644 --- a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionSlaTargets.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionSlaTargets.tsx @@ -2,6 +2,7 @@ import { useTenantConfig } from "../../TenantConfigContext"; import SaveResetBar from "../../components/SaveResetBar"; +import { trackEvent } from "@/lib/appInsights"; export function SectionSlaTargets() { const { @@ -95,7 +96,10 @@ export function SectionSlaTargets() { setSlaNotifyOnSuccessRateBreach(e.target.checked)} + onChange={(e) => { + trackEvent('sla_channel_toggled', { channel: 'success_rate_breach', enabled: e.target.checked }); + setSlaNotifyOnSuccessRateBreach(e.target.checked); + }} className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600" />
@@ -128,7 +132,10 @@ export function SectionSlaTargets() { setSlaNotifyOnDurationBreach(e.target.checked)} + onChange={(e) => { + trackEvent('sla_channel_toggled', { channel: 'duration_breach', enabled: e.target.checked }); + setSlaNotifyOnDurationBreach(e.target.checked); + }} className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600" />
@@ -144,7 +151,10 @@ export function SectionSlaTargets() { setSlaNotifyOnAppInstallBreach(e.target.checked)} + onChange={(e) => { + trackEvent('sla_channel_toggled', { channel: 'app_install_breach', enabled: e.target.checked }); + setSlaNotifyOnAppInstallBreach(e.target.checked); + }} className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600" />
@@ -160,7 +170,10 @@ export function SectionSlaTargets() { setSlaNotifyOnConsecutiveFailures(e.target.checked)} + onChange={(e) => { + trackEvent('sla_channel_toggled', { channel: 'consecutive_failures', enabled: e.target.checked }); + setSlaNotifyOnConsecutiveFailures(e.target.checked); + }} className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600" />
@@ -189,8 +202,18 @@ export function SectionSlaTargets() {
{ + trackEvent('sla_targets_saved', { + hasSuccessRateTarget: slaTargetSuccessRate != null, + hasDurationTarget: slaTargetMaxDurationMinutes != null, + hasAppInstallTarget: slaTargetAppInstallSuccessRate != null, + }); + return handleSaveSlaTargets(); + }} + onReset={() => { + trackEvent('sla_targets_reset'); + return handleResetSlaTargets(); + }} saving={savingSection === "slaTargets"} />
diff --git a/src/Web/autopilot-monitor-web/app/sla/page.tsx b/src/Web/autopilot-monitor-web/app/sla/page.tsx index 45745dbb..51b2a494 100644 --- a/src/Web/autopilot-monitor-web/app/sla/page.tsx +++ b/src/Web/autopilot-monitor-web/app/sla/page.tsx @@ -10,6 +10,7 @@ import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch" import { SlaGauge } from "@/components/charts/SlaGauge"; import AppLineChart from "@/components/charts/AppLineChart"; import { chartColors } from "@/components/charts/chartTheme"; +import { trackEvent } from "@/lib/appInsights"; import Link from "next/link"; interface SlaSnapshot { @@ -125,6 +126,10 @@ export default function SlaPage() { console.error('Error loading SLA metrics:', err); addNotification('error', 'Error', 'Failed to load SLA metrics', 'sla-fetch-error'); } + trackEvent('sla_load_failed', { + months, + error: err instanceof Error ? err.message : 'unknown', + }); } finally { setLoading(false); setRefreshing(false); @@ -133,6 +138,11 @@ export default function SlaPage() { useEffect(() => { fetchMetrics(); }, [fetchMetrics]); + useEffect(() => { + if (tenantId) trackEvent('sla_page_viewed', { months }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tenantId]); + const hasTargets = metrics && (metrics.targetSuccessRate != null || metrics.targetMaxDurationMinutes != null || metrics.targetAppInstallSuccessRate != null); @@ -174,7 +184,11 @@ export default function SlaPage() {