Skip to content

Commit 8d08b56

Browse files
author
rhamlett_microsoft
committed
Health probes paused during slow request simulations to preserve CLR profiling accuracy.
1 parent 6d0bc29 commit 8d08b56

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/PerfProblemSimulator/Services/LatencyProbeService.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class LatencyProbeService : IHostedService, IDisposable
4747
private readonly ILogger<LatencyProbeService> _logger;
4848
private readonly IConfiguration _configuration;
4949
private readonly IServer _server;
50+
private readonly ISimulationTracker _simulationTracker;
5051

5152
private Thread? _probeThread;
5253
private CancellationTokenSource? _cts;
@@ -74,13 +75,15 @@ public LatencyProbeService(
7475
IHttpClientFactory httpClientFactory,
7576
ILogger<LatencyProbeService> logger,
7677
IConfiguration configuration,
77-
IServer server)
78+
IServer server,
79+
ISimulationTracker simulationTracker)
7880
{
7981
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
8082
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
8183
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
8284
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
8385
_server = server ?? throw new ArgumentNullException(nameof(server));
86+
_simulationTracker = simulationTracker ?? throw new ArgumentNullException(nameof(simulationTracker));
8487
}
8588

8689
/// <inheritdoc />
@@ -160,6 +163,24 @@ private void ProbeLoop(object? state)
160163
{
161164
try
162165
{
166+
// Pause probing during SlowRequest simulation to avoid spamming the CLR Profile/Trace
167+
// with hundreds of "Slow" health probe requests, allowing the user to focus on
168+
// the actual test requests.
169+
if (_simulationTracker.GetActiveCountByType(Models.SimulationType.SlowRequest) > 0)
170+
{
171+
// Broadcast a "Paused" measurement so the dashboard chart keeps ticking
172+
BroadcastLatency(new LatencyMeasurement
173+
{
174+
Timestamp = DateTimeOffset.UtcNow,
175+
LatencyMs = 0,
176+
IsPaused = true
177+
});
178+
179+
// Wait for next probe interval
180+
Thread.Sleep(ProbeIntervalMs);
181+
continue;
182+
}
183+
163184
var result = MeasureLatency(httpClient, cancellationToken);
164185

165186
// Broadcast to all connected clients
@@ -387,8 +408,11 @@ public class LatencyMeasurement
387408
/// <summary>
388409
/// Whether the request failed with an error.
389410
/// </summary>
390-
public bool IsError { get; init; }
391-
411+
public bool IsError { get; init; }
412+
/// <summary>
413+
/// Whether the probe was skipped (paused) to preserve CLR profile.
414+
/// </summary>
415+
public bool IsPaused { get; init; }
392416
/// <summary>
393417
/// Error message if IsError is true.
394418
/// </summary>

src/PerfProblemSimulator/wwwroot/js/dashboard.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,24 @@ function updateCharts() {
484484
* This shows the impact of thread pool starvation on request processing time.
485485
*/
486486
function handleLatencyUpdate(measurement) {
487+
const timestamp = new Date(measurement.timestamp);
488+
489+
// Special handling for PAUSED state
490+
if (measurement.isPaused) {
491+
addLatencyToHistory(timestamp, null, false, false);
492+
493+
// Update display to show PAUSED status
494+
const currentEl = document.getElementById('latencyCurrent');
495+
if (currentEl) {
496+
currentEl.textContent = "PAUSED";
497+
currentEl.className = 'latency-value';
498+
}
499+
500+
// Don't log anything for paused heartbeats
501+
updateLatencyChart();
502+
return;
503+
}
504+
487505
// Log significant latency events to the dashboard log
488506
if (measurement.isError) {
489507
logEvent('error', `⚠️ Health Probe Error: ${measurement.errorMessage || 'Unknown error'} (${formatLatency(measurement.latencyMs)})`);
@@ -499,7 +517,6 @@ function handleLatencyUpdate(measurement) {
499517
console.log('Latency update:', measurement);
500518
}
501519

502-
const timestamp = new Date(measurement.timestamp);
503520
const latencyMs = measurement.latencyMs;
504521
const isTimeout = measurement.isTimeout;
505522
const isError = measurement.isError;
@@ -597,25 +614,31 @@ function addLatencyToHistory(timestamp, latencyMs, isTimeout, isError) {
597614
function updateLatencyDisplay(currentLatency, isTimeout, isError) {
598615
const history = state.latencyHistory;
599616

617+
// Ignore updates if the current value is null (paused)
618+
if (currentLatency === null) return;
619+
600620
// Current latency with color coding (check if element exists)
601621
const currentEl = document.getElementById('latencyCurrent');
602622
if (currentEl) {
603623
currentEl.textContent = formatLatency(currentLatency);
604624
currentEl.className = `latency-value ${getLatencyClass(currentLatency, isTimeout)}`;
605625
}
606626

627+
// Filter out nulls for calculations
628+
const validValues = history.values.filter(v => v !== null);
629+
607630
// Calculate average
608631
const avgEl = document.getElementById('latencyAverage');
609-
if (avgEl && history.values.length > 0) {
610-
const avg = history.values.reduce((a, b) => a + b, 0) / history.values.length;
632+
if (avgEl && validValues.length > 0) {
633+
const avg = validValues.reduce((a, b) => a + b, 0) / validValues.length;
611634
avgEl.textContent = formatLatency(avg);
612635
avgEl.className = `latency-value ${getLatencyClass(avg, false)}`;
613636
}
614637

615638
// Calculate max
616639
const maxEl = document.getElementById('latencyMax');
617-
if (maxEl && history.values.length > 0) {
618-
const max = Math.max(...history.values);
640+
if (maxEl && validValues.length > 0) {
641+
const max = Math.max(...validValues);
619642
maxEl.textContent = formatLatency(max);
620643

621644
// precise timeout check for max value could be complex,
@@ -679,6 +702,7 @@ function updateLatencyChart() {
679702

680703
// Map data points to colors based on latency
681704
const pointColors = history.values.map((v, i) => {
705+
if (v === null) return 'transparent'; // Handle paused/null values
682706
if (history.isTimeout[i]) return '#d13438';
683707
if (v > 1000) return '#d13438';
684708
if (v > 150) return '#ffb900';
@@ -693,6 +717,8 @@ function updateLatencyChart() {
693717
borderColor: ctx => {
694718
const index = ctx.p0DataIndex;
695719
const latency = history.values[index];
720+
if (latency === null || history.values[index+1] === null) return 'transparent'; // Gap
721+
696722
const isTimeout = history.isTimeout[index];
697723
if (isTimeout) return '#d13438';
698724
if (latency > 1000) return '#d13438';

0 commit comments

Comments
 (0)