From 65245b6d62825dfdde26a71fc7e701bc60b789af Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 6 Mar 2026 00:32:03 +0100 Subject: [PATCH] prevent duplicate events , fix download durations --- .../Monitoring/Core/MonitoringService.cs | 3 ++- .../Monitoring/Tracking/EnrollmentTracker.cs | 15 +++++++++++++-- src/Agent/AutopilotMonitor.Agent/buildcounter.txt | 2 +- .../Functions/IngestEventsFunction.cs | 8 +++++++- .../Services/TableStorageService.Metrics.cs | 14 ++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs index f12d39d4..7f02a0bf 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs @@ -412,7 +412,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core imeLogPatterns, _configuration.ImeLogPathOverride, imeMatchLogPath: imeMatchLogPath, - espAndHelloTracker: _espAndHelloTracker + espAndHelloTracker: _espAndHelloTracker, + isBootstrapMode: _configuration.UseBootstrapTokenAuth ); _enrollmentTracker.Start(); } diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs index 68a3f3c6..7a9ad40b 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs @@ -35,6 +35,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking private bool _hasAutoSwitchedToAppsPhase; // Track if we've already auto-switched to apps phase for current ESP phase private string _enrollmentType = "v1"; // "v1" = Autopilot Classic/ESP, "v2" = Windows Device Preparation private bool _isWaitingForHello = false; // Track if we're waiting for Hello to complete before sending enrollment_complete + private readonly bool _isBootstrapMode; // Agent started via bootstrap token (pre-MDM) private bool _enrollmentStartDeviceInfoCollected = false; // Re-collect enrollment-dependent info once at first ESP phase private bool _finalDeviceInfoCollected = false; // Ensure final device info is emitted only once private string _lastEmittedSummaryHash; // Track last emitted state-breakdown to avoid redundant summary events @@ -145,8 +146,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking bool simulationMode = false, double speedFactor = 50, string imeMatchLogPath = null, - Collectors.EspAndHelloTracker espAndHelloTracker = null) + Collectors.EspAndHelloTracker espAndHelloTracker = null, + bool isBootstrapMode = false) { + _isBootstrapMode = isBootstrapMode; _sessionId = sessionId; _tenantId = tenantId; _emitEvent = emitEvent; @@ -1295,6 +1298,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking /// /// Re-collects enrollment-dependent device info (AAD join, Autopilot profile, ESP config) /// that were likely empty when the agent started before enrollment (bootstrap scenario). + /// Only runs in bootstrap mode — in normal mode CollectAll() already has complete data. /// Called once when the first ESP phase is detected. /// private void CollectDeviceInfoAtEnrollmentStart() @@ -1303,7 +1307,14 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking return; _enrollmentStartDeviceInfoCollected = true; - _logger.Info("EnrollmentTracker: first ESP phase detected — re-collecting enrollment-dependent device info"); + + if (!_isBootstrapMode) + { + _logger.Debug("EnrollmentTracker: skipping enrollment-start device info re-collection (not bootstrap mode)"); + return; + } + + _logger.Info("EnrollmentTracker: first ESP phase detected — re-collecting enrollment-dependent device info (bootstrap mode)"); Task.Run(() => { try diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index eb08bc0b..bfd03aba 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -240 +242 diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs index 886ddaa8..cce26cf2 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs @@ -873,7 +873,13 @@ namespace AutopilotMonitor.Functions.Functions if (evt.Data.ContainsKey("doDownloadMode") && int.TryParse(evt.Data["doDownloadMode"]?.ToString(), out var doMode)) summary.DoDownloadMode = doMode; if (evt.Data.ContainsKey("doDownloadDuration")) - summary.DoDownloadDuration = evt.Data["doDownloadDuration"]?.ToString() ?? string.Empty; + { + var doDurStr = evt.Data["doDownloadDuration"]?.ToString() ?? string.Empty; + summary.DoDownloadDuration = doDurStr; + // Use DO's measured duration for throughput calculation (more accurate than timestamp diff) + if (TimeSpan.TryParse(doDurStr, out var doDurTs) && doDurTs.TotalSeconds >= 1) + summary.DownloadDurationSeconds = Math.Max(summary.DownloadDurationSeconds, (int)doDurTs.TotalSeconds); + } if (evt.Data.ContainsKey("doBytesFromLanPeers") && long.TryParse(evt.Data["doBytesFromLanPeers"]?.ToString(), out var doLan)) summary.DoBytesFromLanPeers = doLan; if (evt.Data.ContainsKey("doBytesFromGroupPeers") && long.TryParse(evt.Data["doBytesFromGroupPeers"]?.ToString(), out var doGroup)) diff --git a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Metrics.cs b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Metrics.cs index 6f2d8faf..9803d10c 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Metrics.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Metrics.cs @@ -169,6 +169,20 @@ namespace AutopilotMonitor.Functions.Services } } } + + // Preserve DownloadDurationSeconds and DownloadBytes from prior batch if current batch has no value + if (summary.DownloadDurationSeconds == 0) + { + var existingDlDuration = existing.Value.GetInt32("DownloadDurationSeconds"); + if (existingDlDuration.HasValue && existingDlDuration.Value > 0) + summary.DownloadDurationSeconds = existingDlDuration.Value; + } + if (summary.DownloadBytes == 0) + { + var existingDlBytes = existing.Value.GetInt64("DownloadBytes"); + if (existingDlBytes.HasValue && existingDlBytes.Value > 0) + summary.DownloadBytes = existingDlBytes.Value; + } } catch (Azure.RequestFailedException ex) when (ex.Status == 404) {