Skip to content

Commit 95aaa2e

Browse files
committed
CPU simulation simplification update
1 parent 8b6318d commit 95aaa2e

File tree

7 files changed

+151
-72
lines changed

7 files changed

+151
-72
lines changed

src/PerfProblemSimulator/Controllers/CpuController.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,24 @@ public async Task<IActionResult> TriggerHighCpu(
103103
{
104104
// Use defaults if no request body provided
105105
var durationSeconds = request?.DurationSeconds ?? 30;
106-
var targetPercentage = request?.TargetPercentage ?? 100;
106+
var level = request?.Level ?? "high";
107107

108108
// Log the incoming request (FR-010: Request logging)
109109
_logger.LogInformation(
110-
"Received CPU stress request: DurationSeconds={Duration}, Percentage={Percentage}, ClientIP={ClientIP}",
110+
"Received CPU stress request: DurationSeconds={Duration}, Level={Level}, ClientIP={ClientIP}",
111111
durationSeconds,
112-
targetPercentage,
112+
level,
113113
HttpContext.Connection.RemoteIpAddress);
114114

115115
try
116116
{
117-
var result = await _cpuStressService.TriggerCpuStressAsync(durationSeconds, cancellationToken, targetPercentage);
117+
var result = await _cpuStressService.TriggerCpuStressAsync(durationSeconds, cancellationToken, level);
118118

119119
_logger.LogInformation(
120-
"Started CPU stress simulation {SimulationId} for {Duration}s @ {Percentage}%",
120+
"Started CPU stress simulation {SimulationId} for {Duration}s @ {Level}",
121121
result.SimulationId,
122122
result.ActualParameters?["DurationSeconds"],
123-
result.ActualParameters?["TargetPercentage"]);
123+
result.ActualParameters?["Level"]);
124124

125125
return Ok(result);
126126
}

src/PerfProblemSimulator/Models/CpuStressRequest.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,19 @@ public class CpuStressRequest
2828
/// Default: 30 seconds. This is enough time to observe CPU metrics spike
2929
/// in monitoring tools like Task Manager, dotnet-counters, or Application Insights.
3030
/// </para>
31-
/// <para>
32-
/// Values exceeding the configured maximum (default: 300 seconds) will be
33-
/// automatically capped to prevent runaway CPU consumption.
34-
/// </para>
3531
/// </remarks>
3632
[Range(1, int.MaxValue, ErrorMessage = "Duration must be at least 1 second")]
3733
public int DurationSeconds { get; set; } = 30;
3834

3935
/// <summary>
40-
/// The target CPU usage percentage (1-100).
36+
/// The intensity level: "moderate" (~65% CPU) or "high" (~100% CPU).
4137
/// </summary>
4238
/// <remarks>
4339
/// <para>
44-
/// Default: 100%. Lower values use a duty cycle (work/sleep) to simulate
45-
/// partial CPU load.
40+
/// Default: "high".
41+
/// - Moderate: Uses duty cycling to target approximately 65% CPU usage
42+
/// - High: Full spin loops for maximum CPU consumption
4643
/// </para>
4744
/// </remarks>
48-
[Range(1, 100, ErrorMessage = "Target percentage must be between 1 and 100")]
49-
public int TargetPercentage { get; set; } = 100;
45+
public string Level { get; set; } = "high";
5046
}

src/PerfProblemSimulator/Services/CpuStressService.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public CpuStressService(
8484
}
8585

8686
/// <inheritdoc />
87-
public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, CancellationToken cancellationToken, int targetPercentage = 100)
87+
public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, CancellationToken cancellationToken, string level = "high")
8888
{
8989
// ==========================================================================
9090
// STEP 1: Validate the duration (no upper limits - app is meant to break)
@@ -93,7 +93,12 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
9393
? DefaultDurationSeconds
9494
: durationSeconds;
9595

96-
var actualPercentage = Math.Clamp(targetPercentage, 1, 100);
96+
// Normalize level to lowercase and default to "high" if invalid
97+
var normalizedLevel = (level?.ToLowerInvariant()) switch
98+
{
99+
"moderate" => "moderate",
100+
_ => "high"
101+
};
97102

98103
var simulationId = Guid.NewGuid();
99104
var startedAt = DateTimeOffset.UtcNow;
@@ -111,17 +116,17 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
111116
{
112117
["DurationSeconds"] = actualDuration,
113118
["ProcessorCount"] = processorCount,
114-
["TargetPercentage"] = actualPercentage
119+
["Level"] = normalizedLevel
115120
};
116121

117122
// Register this simulation with the tracker
118123
_simulationTracker.RegisterSimulation(simulationId, SimulationType.Cpu, parameters, cts);
119124

120125
_logger.LogInformation(
121-
"Starting CPU stress simulation {SimulationId}: {Duration}s @ {Percentage}% across {ProcessorCount} cores",
126+
"Starting CPU stress simulation {SimulationId}: {Duration}s @ {Level} across {ProcessorCount} cores",
122127
simulationId,
123128
actualDuration,
124-
actualPercentage,
129+
normalizedLevel,
125130
processorCount);
126131

127132
// ==========================================================================
@@ -132,7 +137,7 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
132137
// This is important because the caller (HTTP request) shouldn't be blocked
133138
// waiting for the entire duration.
134139

135-
_ = Task.Run(() => ExecuteCpuStress(simulationId, actualDuration, actualPercentage, cts.Token), cts.Token);
140+
_ = Task.Run(() => ExecuteCpuStress(simulationId, actualDuration, normalizedLevel, cts.Token), cts.Token);
136141

137142
// ==========================================================================
138143
// STEP 4: Return the result immediately
@@ -145,7 +150,7 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
145150
SimulationId = simulationId,
146151
Type = SimulationType.Cpu,
147152
Status = "Started",
148-
Message = $"CPU stress started on {processorCount} cores for {actualDuration} seconds at {actualPercentage}%. " +
153+
Message = $"CPU stress started on {processorCount} cores for {actualDuration} seconds ({normalizedLevel} intensity). " +
149154
"Observe CPU metrics in Task Manager, dotnet-counters, or Application Insights. " +
150155
"High CPU like this is typically caused by spin loops, inefficient algorithms, or infinite loops.",
151156
ActualParameters = parameters,
@@ -165,8 +170,8 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
165170
/// </para>
166171
/// <para>
167172
/// This method uses dedicated threads with spin loops to consume available CPU.
168-
/// If targetPercentage is 100, it runs a tight loop.
169-
/// If targetPercentage is less than 100, it uses a duty cycle (work/sleep) to simulate load.
173+
/// For "high" level, it runs a tight loop for ~100% usage.
174+
/// For "moderate" level, it uses a duty cycle (work/sleep) to simulate ~65% load.
170175
/// </para>
171176
/// <para>
172177
/// <strong>Why Dedicated Threads Instead of Parallel.For?</strong>
@@ -175,8 +180,11 @@ public Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, Cancell
175180
/// thread pool remains available for the dashboard and metrics collection.
176181
/// </para>
177182
/// </remarks>
178-
private void ExecuteCpuStress(Guid simulationId, int durationSeconds, int targetPercentage, CancellationToken cancellationToken)
183+
private void ExecuteCpuStress(Guid simulationId, int durationSeconds, string level, CancellationToken cancellationToken)
179184
{
185+
// Convert level to internal percentage: moderate = 65%, high = 100%
186+
var targetPercentage = level == "moderate" ? 65 : 100;
187+
180188
try
181189
{
182190
// Calculate the end time using Stopwatch for high precision

src/PerfProblemSimulator/Services/ICpuStressService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public interface ICpuStressService
2626
/// <param name="cancellationToken">
2727
/// Token to request early cancellation of the stress operation.
2828
/// </param>
29-
/// <param name="targetPercentage">
30-
/// Target CPU usage percentage (1-100). Default is 100.
29+
/// <param name="level">
30+
/// Intensity level: "moderate" (~65% CPU) or "high" (~100% CPU). Default is "high".
3131
/// </param>
3232
/// <returns>
3333
/// A result containing the simulation ID, actual parameters used, and timing information.
@@ -43,8 +43,9 @@ public interface ICpuStressService
4343
/// <list type="bullet">
4444
/// <item><term>DurationSeconds</term><description>Actual duration used (may be capped)</description></item>
4545
/// <item><term>ProcessorCount</term><description>Number of cores being stressed</description></item>
46+
/// <item><term>Level</term><description>The intensity level used</description></item>
4647
/// </list>
4748
/// </para>
4849
/// </remarks>
49-
Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, CancellationToken cancellationToken, int targetPercentage = 100);
50+
Task<SimulationResult> TriggerCpuStressAsync(int durationSeconds, CancellationToken cancellationToken, string level = "high");
5051
}

src/PerfProblemSimulator/wwwroot/css/dashboard.css

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -553,31 +553,100 @@ body {
553553
}
554554

555555
.btn-danger {
556-
background: var(--color-danger);
556+
background: linear-gradient(135deg, #8b0000 0%, #4a0000 100%);
557557
color: white;
558+
border: 2px solid #ff0000;
559+
animation: pulse-danger 2s infinite;
558560
}
559561

560562
.btn-danger:hover:not(:disabled) {
561-
background: #a52a2d;
563+
background: linear-gradient(135deg, #a00000 0%, #600000 100%);
564+
border-color: #ff4444;
565+
}
566+
567+
@keyframes pulse-danger {
568+
0%, 100% { box-shadow: 0 0 5px rgba(255, 0, 0, 0.3); }
569+
50% { box-shadow: 0 0 15px rgba(255, 0, 0, 0.6); }
570+
}
571+
572+
/* --------------------------------------------------------------------------
573+
Button Groups & Trigger Buttons (PHP-style)
574+
-------------------------------------------------------------------------- */
575+
.btn-group {
576+
display: flex;
577+
gap: 0.5rem;
578+
margin-top: 0.5rem;
579+
}
580+
581+
.btn-trigger {
582+
color: white;
583+
flex: 1;
584+
font-size: 0.8rem;
585+
padding: 0.5rem 0.75rem;
586+
}
587+
588+
.btn-cpu {
589+
background: var(--color-cpu);
590+
}
591+
592+
.btn-cpu:hover:not(:disabled) {
593+
background: var(--color-primary-dark);
594+
}
595+
596+
.btn-memory {
597+
background: var(--color-success);
598+
flex: 1;
599+
}
600+
601+
.btn-memory:hover:not(:disabled) {
602+
background: #0b5e0b;
603+
}
604+
605+
.btn-release {
606+
background: var(--color-secondary);
607+
color: white;
608+
flex: 1;
609+
}
610+
611+
.btn-release:hover:not(:disabled) {
612+
background: #5a6268;
613+
}
614+
615+
.btn-threadblock {
616+
background: var(--color-threads);
617+
}
618+
619+
.btn-threadblock:hover:not(:disabled) {
620+
background: #5c2d91;
621+
}
622+
623+
.btn-stop {
624+
background: #d13438 !important;
625+
color: white !important;
626+
padding: 0.5rem 0.75rem;
627+
font-size: 0.8rem;
628+
flex: 0 0 auto;
629+
border: none;
630+
box-shadow: 0 0 0 3px white;
631+
}
632+
633+
.btn-stop:hover:not(:disabled) {
634+
background: #a52a2d !important;
562635
}
563636

564637
.btn-crash {
565638
background: linear-gradient(135deg, #8b0000 0%, #4a0000 100%);
566639
color: white;
567640
border: 2px solid #ff0000;
568641
animation: pulse-danger 2s infinite;
642+
width: 100%;
569643
}
570644

571645
.btn-crash:hover:not(:disabled) {
572646
background: linear-gradient(135deg, #a00000 0%, #600000 100%);
573647
border-color: #ff4444;
574648
}
575649

576-
@keyframes pulse-danger {
577-
0%, 100% { box-shadow: 0 0 5px rgba(255, 0, 0, 0.3); }
578-
50% { box-shadow: 0 0 15px rgba(255, 0, 0, 0.6); }
579-
}
580-
581650
.cpu-group {
582651
border: 2px solid rgba(0, 120, 212, 0.3);
583652
background: rgba(0, 120, 212, 0.05);
@@ -611,6 +680,7 @@ body {
611680
color: white;
612681
border: 2px solid #ffa500;
613682
animation: pulse-slow 3s infinite;
683+
flex: 1;
614684
}
615685

616686
.btn-slowrequest:hover:not(:disabled) {

src/PerfProblemSimulator/wwwroot/index.html

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,19 @@ <h3>🔥 CPU Stress</h3>
8686
<p>Creates parallel spin loops to max out all CPU cores.</p>
8787
<div class="control-inputs">
8888
<label>Duration (s):
89-
<input type="number" id="cpuDuration" value="10" min="1" title="Duration in seconds (minimum 1 second)">
89+
<input type="number" id="cpuDuration" value="30" min="1" title="Duration in seconds (minimum 1 second)">
9090
</label>
91-
<label>Target Load:
92-
<select id="cpuTarget" title="Target CPU load range" class="wide-input">
93-
<option value="55">50% - 60%</option>
94-
<option value="65">60% - 70%</option>
95-
<option value="75" selected>70% - 80%</option>
96-
<option value="90">80% - 90%</option>
97-
<option value="100">90% - 100%</option>
91+
<label>Intensity:
92+
<select id="cpuLevel" class="wide-input">
93+
<option value="moderate">Moderate</option>
94+
<option value="high" selected>High</option>
9895
</select>
9996
</label>
10097
</div>
101-
<button class="btn btn-danger" id="btnTriggerCpu">🔥 Trigger High CPU</button>
98+
<div class="btn-group">
99+
<button class="btn btn-trigger btn-cpu" id="btnTriggerCpu">🔥 Trigger CPU Stress</button>
100+
<button class="btn btn-stop" id="btnStopCpu">⬛ Stop</button>
101+
</div>
102102
</div>
103103

104104
<div class="control-group memory-group">
@@ -109,8 +109,10 @@ <h3>📊 Memory Pressure</h3>
109109
<input type="number" id="memorySize" value="512" min="1" title="Memory size in megabytes (minimum 1 MB). Allocations are cumulative until released.">
110110
</label>
111111
</div>
112-
<button class="btn btn-warning" id="btnAllocateMemory">📊 Allocate</button>
113-
<button class="btn btn-secondary" id="btnReleaseMemory">🗑️ Release</button>
112+
<div class="btn-group">
113+
<button class="btn btn-trigger btn-memory" id="btnAllocateMemory">📈 Allocate</button>
114+
<button class="btn btn-trigger btn-release" id="btnReleaseMemory">🗑️ Release</button>
115+
</div>
114116
</div>
115117

116118
<div class="control-group thread-group">
@@ -124,7 +126,9 @@ <h3>🧵 Thread Pool Starvation</h3>
124126
<input type="number" id="threadConcurrent" value="100" min="1" title="Number of concurrent blocking operations (minimum 1)">
125127
</label>
126128
</div>
127-
<button class="btn btn-danger" id="btnTriggerThreadBlock">🧵 Block Threads</button>
129+
<div class="btn-group">
130+
<button class="btn btn-trigger btn-threadblock" id="btnTriggerThreadBlock">🧵 Block Threads</button>
131+
</div>
128132
</div>
129133

130134
<div class="control-group slowrequest-group">
@@ -142,8 +146,10 @@ <h3>🐌 Slow Requests</h3>
142146
<input type="number" id="slowRequestMax" value="10" min="1" max="100" title="Total requests to generate">
143147
</label>
144148
</div>
145-
<button class="btn btn-slowrequest" id="btnStartSlowRequests">🐌 Start Slow Requests</button>
146-
<button class="btn btn-secondary" id="btnStopSlowRequests">⏹️ Stop</button>
149+
<div class="btn-group">
150+
<button class="btn btn-trigger btn-slowrequest" id="btnStartSlowRequests">🐌 Start Slow Requests</button>
151+
<button class="btn btn-stop" id="btnStopSlowRequests">⬛ Stop</button>
152+
</div>
147153
</div>
148154

149155
<div class="control-group crash-group">
@@ -159,13 +165,10 @@ <h3>💥 Application Crash</h3>
159165
<option value="OutOfMemory">Out of Memory</option>
160166
</select>
161167
</label>
162-
<!-- Delay option commented out - not serving a useful purpose
163-
<label>Delay (s):
164-
<input type="number" id="crashDelay" value="0" min="0" title="Optional delay before crash (0 = immediate)">
165-
</label>
166-
-->
167168
</div>
168-
<button class="btn btn-crash" id="btnTriggerCrash">💀 Trigger Crash</button>
169+
<div class="btn-group">
170+
<button class="btn btn-trigger btn-danger" id="btnTriggerCrash">💥 Trigger Crash</button>
171+
</div>
169172
<p class="crash-warning">⚠️ This will TERMINATE the app!</p>
170173
</div>
171174
</div>

0 commit comments

Comments
 (0)