Skip to content

Commit 78ac1da

Browse files
committed
added failed requests simulator
1 parent 29afb1b commit 78ac1da

File tree

10 files changed

+922
-2
lines changed

10 files changed

+922
-2
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using PerfProblemSimulator.Models;
3+
using PerfProblemSimulator.Services;
4+
5+
namespace PerfProblemSimulator.Controllers;
6+
7+
/// <summary>
8+
/// Controller for generating failed HTTP requests (5xx responses).
9+
/// </summary>
10+
/// <remarks>
11+
/// <para>
12+
/// <strong>PURPOSE:</strong>
13+
/// This controller provides endpoints to generate HTTP 500 errors for testing
14+
/// Azure diagnostics tools like AppLens and Application Insights.
15+
/// </para>
16+
/// <para>
17+
/// <strong>USE CASES:</strong>
18+
/// <list type="bullet">
19+
/// <item>Testing AppLens error detection and analysis</item>
20+
/// <item>Validating Application Insights failure alerts</item>
21+
/// <item>Testing error rate based auto-scaling rules</item>
22+
/// <item>Training developers on error diagnosis in Azure</item>
23+
/// </list>
24+
/// </para>
25+
/// <para>
26+
/// <strong>HOW IT WORKS:</strong>
27+
/// The service makes HTTP requests to the /api/loadtest endpoint with parameters
28+
/// configured for guaranteed failure (100% error probability). The load test endpoint
29+
/// throws a random exception, resulting in an HTTP 500 response. Each request takes
30+
/// approximately 1.5 seconds, ensuring the failures appear in latency monitoring.
31+
/// </para>
32+
/// </remarks>
33+
[ApiController]
34+
[Route("api/[controller]")]
35+
[Produces("application/json")]
36+
[Tags("Failed Request Simulation")]
37+
public class FailedRequestController : ControllerBase
38+
{
39+
private readonly IFailedRequestService _failedRequestService;
40+
private readonly ILogger<FailedRequestController> _logger;
41+
42+
public FailedRequestController(
43+
IFailedRequestService failedRequestService,
44+
ILogger<FailedRequestController> logger)
45+
{
46+
_failedRequestService = failedRequestService ?? throw new ArgumentNullException(nameof(failedRequestService));
47+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
48+
}
49+
50+
/// <summary>
51+
/// Starts generating failed HTTP requests.
52+
/// </summary>
53+
/// <param name="request">Configuration specifying how many failures to generate.</param>
54+
/// <returns>Information about the started simulation.</returns>
55+
/// <remarks>
56+
/// <para>
57+
/// This endpoint starts a background process that generates HTTP 500 errors.
58+
/// Each failed request takes approximately 1.5 seconds and throws a random
59+
/// exception type (TimeoutException, NullReferenceException, etc.).
60+
/// </para>
61+
/// <para>
62+
/// <strong>Where to see the failures:</strong>
63+
/// <list type="bullet">
64+
/// <item>Azure Portal → App Service → Diagnose and Solve Problems → AppLens</item>
65+
/// <item>Application Insights → Failures blade</item>
66+
/// <item>Azure Monitor → Alerts (if error rate rules configured)</item>
67+
/// </list>
68+
/// </para>
69+
/// </remarks>
70+
/// <response code="200">Simulation started successfully</response>
71+
[HttpPost("start")]
72+
[ProducesResponseType(typeof(SimulationResult), StatusCodes.Status200OK)]
73+
public IActionResult Start([FromBody] FailedRequestRequest? request)
74+
{
75+
var requestCount = request?.RequestCount ?? 10;
76+
77+
_logger.LogWarning(
78+
"❌ Starting failed request simulation: Count={Count}",
79+
requestCount);
80+
81+
var result = _failedRequestService.Start(requestCount);
82+
return Ok(result);
83+
}
84+
85+
/// <summary>
86+
/// Stops the failed request simulation.
87+
/// </summary>
88+
/// <returns>Summary of the simulation run.</returns>
89+
/// <remarks>
90+
/// Stops generating new failed requests. Requests already in progress will complete.
91+
/// </remarks>
92+
/// <response code="200">Simulation stopped</response>
93+
[HttpPost("stop")]
94+
[ProducesResponseType(typeof(SimulationResult), StatusCodes.Status200OK)]
95+
public IActionResult Stop()
96+
{
97+
_logger.LogInformation("🛑 Stopping failed request simulation");
98+
var result = _failedRequestService.Stop();
99+
return Ok(result);
100+
}
101+
102+
/// <summary>
103+
/// Gets the current status of the failed request simulation.
104+
/// </summary>
105+
/// <returns>Current simulation status including request counts.</returns>
106+
/// <response code="200">Current status</response>
107+
[HttpGet("status")]
108+
[ProducesResponseType(typeof(FailedRequestStatus), StatusCodes.Status200OK)]
109+
public IActionResult GetStatus()
110+
{
111+
var status = _failedRequestService.GetStatus();
112+
return Ok(status);
113+
}
114+
}
115+
116+
/// <summary>
117+
/// Request model for starting a failed request simulation.
118+
/// </summary>
119+
public class FailedRequestRequest
120+
{
121+
/// <summary>
122+
/// Number of failed requests (HTTP 500s) to generate.
123+
/// </summary>
124+
/// <remarks>
125+
/// <para>
126+
/// <strong>DEFAULT: 10</strong>
127+
/// </para>
128+
/// <para>
129+
/// Each request takes approximately 1.5 seconds before failing.
130+
/// Requests are spread out with a small delay between them to ensure
131+
/// they appear as distinct data points in monitoring tools.
132+
/// </para>
133+
/// </remarks>
134+
public int RequestCount { get; set; } = 10;
135+
}

src/PerfProblemSimulator/Controllers/ThreadBlockController.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,19 @@ namespace PerfProblemSimulator.Controllers;
3333
public class ThreadBlockController : ControllerBase
3434
{
3535
private readonly IThreadBlockService _threadBlockService;
36+
private readonly ISimulationTracker _simulationTracker;
3637
private readonly ILogger<ThreadBlockController> _logger;
3738

3839
/// <summary>
3940
/// Initializes a new instance of the <see cref="ThreadBlockController"/> class.
4041
/// </summary>
4142
public ThreadBlockController(
4243
IThreadBlockService threadBlockService,
44+
ISimulationTracker simulationTracker,
4345
ILogger<ThreadBlockController> logger)
4446
{
4547
_threadBlockService = threadBlockService ?? throw new ArgumentNullException(nameof(threadBlockService));
48+
_simulationTracker = simulationTracker ?? throw new ArgumentNullException(nameof(simulationTracker));
4649
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4750
}
4851

@@ -130,4 +133,32 @@ public async Task<IActionResult> TriggerSyncOverAsync(
130133
ErrorResponse.SimulationError("Failed to start thread blocking simulation."));
131134
}
132135
}
136+
137+
/// <summary>
138+
/// Stops all active thread pool starvation simulations.
139+
/// </summary>
140+
/// <returns>Number of simulations that were stopped.</returns>
141+
/// <remarks>
142+
/// <para>
143+
/// This endpoint cancels all active thread blocking simulations. Already-blocked threads
144+
/// will eventually complete their current delay, but no new blocking operations will start.
145+
/// </para>
146+
/// </remarks>
147+
/// <response code="200">Thread blocking simulations stopped.</response>
148+
[HttpPost("stop")]
149+
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
150+
public IActionResult Stop()
151+
{
152+
_logger.LogInformation("Stopping all thread blocking simulations");
153+
154+
var cancelled = _simulationTracker.CancelByType(SimulationType.ThreadBlock);
155+
156+
_logger.LogInformation("Stopped {Count} thread blocking simulations", cancelled);
157+
158+
return Ok(new
159+
{
160+
message = $"Stopped {cancelled} thread blocking simulation(s)",
161+
cancelledCount = cancelled
162+
});
163+
}
133164
}

src/PerfProblemSimulator/Models/SimulationType.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,13 @@ public enum SimulationType
7474
/// Designed to eventually trigger 230s Azure App Service timeout under extreme load.
7575
/// Diagnosis tools: Azure Load Testing, Application Insights, Azure Monitor.
7676
/// </summary>
77-
LoadTest
77+
LoadTest,
78+
79+
/// <summary>
80+
/// Failed request simulation that generates HTTP 5xx responses.
81+
/// Uses the load test endpoint with 100% error probability to create guaranteed failures.
82+
/// Designed to produce HTTP 500 errors visible in AppLens and Application Insights.
83+
/// Diagnosis tools: AppLens, Application Insights failures blade, Azure Monitor alerts.
84+
/// </summary>
85+
FailedRequest
7886
}

src/PerfProblemSimulator/Program.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,18 @@
202202
// causing immediate problems.
203203
builder.Services.AddSingleton<ILoadTestService, LoadTestService>();
204204

205+
// FailedRequestService - Singleton service for generating HTTP 5xx errors
206+
// Educational Note: Singleton lifetime is required because the service maintains
207+
// state about running simulations. This service generates failed requests by calling
208+
// the load test endpoint with 100% error probability, producing HTTP 500 errors that
209+
// appear in AppLens and Application Insights for training on error diagnosis.
210+
builder.Services.AddHttpClient("FailedRequest")
211+
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
212+
{
213+
ConnectTimeout = TimeSpan.FromSeconds(60)
214+
});
215+
builder.Services.AddSingleton<IFailedRequestService, FailedRequestService>();
216+
205217
// MetricsCollector - Singleton service for collecting system metrics
206218
// Educational Note: This service runs on a DEDICATED THREAD (not the thread pool)
207219
// so it remains responsive even during thread pool starvation scenarios.

0 commit comments

Comments
 (0)