From 8446a07c1a1ec49b973243c08bd052c18c28638a Mon Sep 17 00:00:00 2001 From: abishek Date: Thu, 25 Sep 2025 15:56:02 +0530 Subject: [PATCH 1/4] perf: added jitter interval to imporve perfomance --- .../Monitoring/MonitoringBackgroundService.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs index bced0e8..29658ab 100644 --- a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs +++ b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs @@ -153,6 +153,9 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) foreach (Data.Endpoint? endpoint in endpoints) { int intervalMs = endpoint.IntervalSeconds * 1000; + double jitterPercent = 0.1; // ±10% Calculate jittered interval + int jitterOffsetMs = (int)(intervalMs * ((Random.Shared.NextDouble() * 2 - 1) * jitterPercent)); + int jitteredIntervalMs = intervalMs + jitterOffsetMs; if (_endpointTimers.TryGetValue(endpoint.Id, out Timer? existingTimer)) { @@ -165,20 +168,19 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) await Task.Delay(100); // Brief delay to let current execution complete } - // Restart with new interval - existingTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(intervalMs)); + // Restart timer with jittered interval + existingTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(jitteredIntervalMs)); } else { - // Create new timer for new endpoint var timer = new Timer( callback: async _ => await ProbeEndpointAsync(endpoint.Id), state: null, - dueTime: TimeSpan.Zero, // Start immediately - period: TimeSpan.FromMilliseconds(intervalMs)); + dueTime: TimeSpan.Zero, + period: TimeSpan.FromMilliseconds(jitteredIntervalMs)); _endpointTimers.TryAdd(endpoint.Id, timer); - _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s", + _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s (jitter applied)", endpoint.Id, endpoint.Name, endpoint.IntervalSeconds); } } From cdb9b11435896c03f21a4a0b43efa2f7051f7d45 Mon Sep 17 00:00:00 2001 From: abishek Date: Thu, 25 Sep 2025 16:02:47 +0530 Subject: [PATCH 2/4] added jitter at First execution --- .../Monitoring/MonitoringBackgroundService.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs index 29658ab..41f1847 100644 --- a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs +++ b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs @@ -124,6 +124,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _logger.LogInformation("Monitoring background service stopped"); } + private static readonly double JitterPercent = 0.1; // ±10% + + private int GetJitteredIntervalMs(int baseIntervalMs) + { + double jitterOffset = (Random.Shared.NextDouble() * 2 - 1) * baseIntervalMs * JitterPercent; + return baseIntervalMs + (int)jitterOffset; + } + private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) { using IServiceScope scope = _serviceProvider.CreateScope(); @@ -152,36 +160,34 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) // Add or update timers for current endpoints foreach (Data.Endpoint? endpoint in endpoints) { - int intervalMs = endpoint.IntervalSeconds * 1000; - double jitterPercent = 0.1; // ±10% Calculate jittered interval - int jitterOffsetMs = (int)(intervalMs * ((Random.Shared.NextDouble() * 2 - 1) * jitterPercent)); - int jitteredIntervalMs = intervalMs + jitterOffsetMs; + int IntervalMs = endpoint.IntervalSeconds * 1000; + int jitteredIntervalMs = GetJitteredIntervalMs(IntervalMs); if (_endpointTimers.TryGetValue(endpoint.Id, out Timer? existingTimer)) { - // Stop timer to prevent race condition, then restart with new interval + // Stop timer to prevent race condition existingTimer.Change(Timeout.Infinite, Timeout.Infinite); - // Wait briefly if probe is currently executing to avoid immediate restart if (_probeExecuting.TryGetValue(endpoint.Id, out bool isExecuting) && isExecuting) - { - await Task.Delay(100); // Brief delay to let current execution complete - } + await Task.Delay(100); // Wait for current probe // Restart timer with jittered interval existingTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(jitteredIntervalMs)); } else { + // Randomize first execution to avoid spikes on startup + int dueTimeMs = Random.Shared.Next(0, jitteredIntervalMs); + var timer = new Timer( callback: async _ => await ProbeEndpointAsync(endpoint.Id), state: null, - dueTime: TimeSpan.Zero, + dueTime: TimeSpan.FromMilliseconds(dueTimeMs), period: TimeSpan.FromMilliseconds(jitteredIntervalMs)); _endpointTimers.TryAdd(endpoint.Id, timer); - _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s (jitter applied)", - endpoint.Id, endpoint.Name, endpoint.IntervalSeconds); + _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s ±{JitterPercent:P}, first probe in {DueTimeMs}ms", + endpoint.Id, endpoint.Name, endpoint.IntervalSeconds, JitterPercent, dueTimeMs); } } From c8c50b70a3e767e39fd7be6d28d8ae17b4c54e15 Mon Sep 17 00:00:00 2001 From: abishek Date: Thu, 25 Sep 2025 16:08:58 +0530 Subject: [PATCH 3/4] code refactored --- .../Monitoring/MonitoringBackgroundService.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs index 41f1847..93cb016 100644 --- a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs +++ b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs @@ -124,12 +124,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _logger.LogInformation("Monitoring background service stopped"); } - private static readonly double JitterPercent = 0.1; // ±10% + private static readonly double jitterPercent = 0.1; // ±10% - private int GetJitteredIntervalMs(int baseIntervalMs) + private int GetJitteredIntervalMs(int intervalMs) { - double jitterOffset = (Random.Shared.NextDouble() * 2 - 1) * baseIntervalMs * JitterPercent; - return baseIntervalMs + (int)jitterOffset; + double jitterOffset = (Random.Shared.NextDouble() * 2 - 1) * intervalMs * jitterPercent; + return intervalMs + (int)jitterOffset; } private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) @@ -160,8 +160,8 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) // Add or update timers for current endpoints foreach (Data.Endpoint? endpoint in endpoints) { - int IntervalMs = endpoint.IntervalSeconds * 1000; - int jitteredIntervalMs = GetJitteredIntervalMs(IntervalMs); + int intervalMs = endpoint.IntervalSeconds * 1000; + int jitteredIntervalMs = GetJitteredIntervalMs(intervalMs); if (_endpointTimers.TryGetValue(endpoint.Id, out Timer? existingTimer)) { @@ -187,7 +187,7 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) _endpointTimers.TryAdd(endpoint.Id, timer); _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s ±{JitterPercent:P}, first probe in {DueTimeMs}ms", - endpoint.Id, endpoint.Name, endpoint.IntervalSeconds, JitterPercent, dueTimeMs); + endpoint.Id, endpoint.Name, endpoint.IntervalSeconds, jitterPercent, dueTimeMs); } } From 6ea361cc9cf841a2d68a1efc92adf0377519e5a1 Mon Sep 17 00:00:00 2001 From: abishek Date: Fri, 26 Sep 2025 11:00:11 +0530 Subject: [PATCH 4/4] enpoint based probe jittering --- .../Monitoring/MonitoringBackgroundService.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs index 93cb016..03ed9ff 100644 --- a/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs +++ b/ThingConnect.Pulse.Server/Services/Monitoring/MonitoringBackgroundService.cs @@ -124,12 +124,16 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _logger.LogInformation("Monitoring background service stopped"); } - private static readonly double jitterPercent = 0.1; // ±10% - - private int GetJitteredIntervalMs(int intervalMs) + private static int GetJitteredIntervalMs(int intervalMs, Guid endpointId) { - double jitterOffset = (Random.Shared.NextDouble() * 2 - 1) * intervalMs * jitterPercent; - return intervalMs + (int)jitterOffset; + // Use deterministic seeding based on endpoint ID for consistent jitter patterns + int seed = endpointId.GetHashCode(); + var random = new Random(seed); + int jitterRange = (int)(intervalMs * 0.2); // 20% total range for ±10% + int jitterOffset = random.Next(0, jitterRange) - (jitterRange / 2); // -10% to +10% + int jitteredInterval = Math.Max(1000, intervalMs + jitterOffset); + + return jitteredInterval; } private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) @@ -161,7 +165,7 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) foreach (Data.Endpoint? endpoint in endpoints) { int intervalMs = endpoint.IntervalSeconds * 1000; - int jitteredIntervalMs = GetJitteredIntervalMs(intervalMs); + int jitteredIntervalMs = GetJitteredIntervalMs(intervalMs, endpoint.Id); if (_endpointTimers.TryGetValue(endpoint.Id, out Timer? existingTimer)) { @@ -186,8 +190,8 @@ private async Task RefreshEndpointsAsync(CancellationToken cancellationToken) period: TimeSpan.FromMilliseconds(jitteredIntervalMs)); _endpointTimers.TryAdd(endpoint.Id, timer); - _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s ±{JitterPercent:P}, first probe in {DueTimeMs}ms", - endpoint.Id, endpoint.Name, endpoint.IntervalSeconds, jitterPercent, dueTimeMs); + _logger.LogInformation("Started monitoring endpoint: {EndpointId} ({Name}) every {IntervalSeconds}s (±10% jitter), first probe in {DueTimeMs}ms", + endpoint.Id, endpoint.Name, endpoint.IntervalSeconds, dueTimeMs); } }