Skip to content

Commit 0d20dc0

Browse files
author
rhamlett_microsoft
committed
Load test fixes
1 parent 90d8be6 commit 0d20dc0

2 files changed

Lines changed: 53 additions & 57 deletions

File tree

src/PerfProblemSimulator/Services/LoadTestService.cs

Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
* DEPENDENCIES:
3737
* - Models/LoadTestRequest.cs - Input parameters
3838
* - Models/LoadTestResult.cs - Output format
39-
* - System.Security.Cryptography - For SHA256 hash work
4039
* - System.Diagnostics - For Stopwatch timing
40+
* - System.Threading - For Thread.SpinWait CPU work
4141
*
4242
* =============================================================================
4343
*/
@@ -46,8 +46,6 @@
4646
using PerfProblemSimulator.Hubs;
4747
using PerfProblemSimulator.Models;
4848
using System.Diagnostics;
49-
using System.Security.Cryptography;
50-
using System.Text;
5149

5250
namespace PerfProblemSimulator.Services;
5351

@@ -499,37 +497,36 @@ public async Task<LoadTestResult> ExecuteWorkAsync(LoadTestRequest request, Canc
499497
* =================================================================
500498
*
501499
* Instead of doing all CPU work at once then sleeping, we interleave:
502-
* - Do ~50ms worth of CPU work (hash iterations)
500+
* - Do CPU work for cpuWorkMs milliseconds (spin loop)
503501
* - Touch the memory buffer (keeps it active, prevents GC optimization)
504502
* - Sleep briefly (~50ms)
505503
* - Repeat until total duration reached
506504
*
507-
* This creates ~50% CPU utilization per active thread, allowing
508-
* CPU to scale with concurrency without immediately hitting 100%.
505+
* This creates tunable CPU utilization per active thread:
506+
* - workIterations / 100 = ms of CPU work per cycle
507+
* - workIterations = 1000 → 10ms work + 50ms sleep = ~17% CPU per thread
508+
* - workIterations = 5000 → 50ms work + 50ms sleep = ~50% CPU per thread
509509
*
510510
* TUNING:
511-
* - workIterations controls CPU intensity per cycle
511+
* - workIterations controls CPU intensity (divide by 100 for ms per cycle)
512512
* - Higher workIterations = more CPU per cycle
513-
* - 0 workIterations = pure thread blocking (minimal CPU)
513+
* - 0 workIterations = pure thread blocking (0% CPU)
514514
*/
515-
const int CycleMs = 100; // Each cycle is ~100ms (50ms work + 50ms sleep)
516515
const int SleepPerCycleMs = 50;
517516

518-
// Calculate iterations per cycle to spread workIterations across duration
519-
var totalCycles = Math.Max(1, totalDurationMs / CycleMs);
520-
var iterationsPerCycle = request.WorkIterations > 0
521-
? Math.Max(100, request.WorkIterations / totalCycles)
522-
: 0;
517+
// Calculate CPU work time: workIterations / 100 = ms per cycle
518+
// Examples: 1000 → 10ms, 5000 → 50ms, 10000 → 100ms
519+
var cpuWorkMsPerCycle = request.WorkIterations / 100;
523520

524521
while (stopwatch.ElapsedMilliseconds < totalDurationMs)
525522
{
526523
cancellationToken.ThrowIfCancellationRequested();
527524

528-
// CPU work phase
529-
if (iterationsPerCycle > 0)
525+
// CPU work phase (spin loop for precise duration)
526+
if (cpuWorkMsPerCycle > 0)
530527
{
531-
PerformCpuWork(iterationsPerCycle);
532-
totalCpuWorkDone += iterationsPerCycle;
528+
PerformCpuWork(cpuWorkMsPerCycle);
529+
totalCpuWorkDone += cpuWorkMsPerCycle; // Track total ms of CPU work
533530
}
534531

535532
// Keep memory active (prevents GC from collecting early)
@@ -670,55 +667,54 @@ private void CheckAndThrowTimeoutException(Stopwatch stopwatch)
670667

671668
/*
672669
* =========================================================================
673-
* HELPER: Perform CPU Work
670+
* HELPER: Perform CPU Work (Time-Based)
674671
* =========================================================================
675672
*
676673
* ALGORITHM:
677-
* data = "LoadTest-" + randomBytes
678-
* for i in range(iterations):
679-
* hash = SHA256(data)
680-
* data = hash # Feed output back as input
674+
* start = now()
675+
* while (now() - start < workMs):
676+
* spinWait() # Busy loop consuming CPU
681677
*
682-
* This creates consistent, non-optimizable CPU work.
678+
* This creates consistent, measurable CPU consumption for a specified duration.
683679
*/
684680

685681
/// <summary>
686-
/// Performs CPU-intensive work by computing SHA256 hashes.
682+
/// Performs CPU-intensive work using a spin loop for the specified duration.
687683
/// </summary>
688-
/// <param name="iterations">Number of hash iterations to perform.</param>
689-
private void PerformCpuWork(int iterations)
684+
/// <param name="workMs">Milliseconds of CPU work to perform.</param>
685+
private void PerformCpuWork(int workMs)
690686
{
691687
/*
692688
* IMPLEMENTATION NOTES:
693689
*
694-
* We start with a seed value and repeatedly hash it.
695-
* Each hash output becomes the input for the next iteration.
696-
* This prevents the compiler from optimizing away the work.
690+
* We use a spin loop (busy wait) to consume CPU for a precise duration.
691+
* This is more predictable than hash iterations because:
692+
* - Hash speed varies by CPU, making iteration counts unreliable
693+
* - Time-based approach gives consistent CPU utilization
697694
*
698-
* SHA256 CHOICE:
699-
* - Consistent performance across platforms
700-
* - Available in all languages' standard libraries
701-
* - Sufficient CPU load without being excessive
695+
* SPIN LOOP vs HASH:
696+
* - Spin loop gives precise time control
697+
* - Creates visible CPU load in monitoring tools
698+
* - SpinWait(1000) per iteration prevents compiler optimization
702699
*
703700
* PORTING:
704-
* Replace SHA256 with your language's equivalent:
705-
* - PHP: hash('sha256', $data, true)
706-
* - Node.js: crypto.createHash('sha256').update(data).digest()
707-
* - Java: MessageDigest.getInstance("SHA-256").digest(data)
708-
* - Python: hashlib.sha256(data).digest()
701+
* Implement busy waiting in your language:
702+
* - PHP: while (microtime(true) * 1000 < endTime) { spin; }
703+
* - Node.js: while (Date.now() < endTime) { spin; }
704+
* - Java: while (System.currentTimeMillis() < endTime) { Thread.onSpinWait(); }
705+
* - Python: while time.time() * 1000 < end_time: pass
709706
*/
710-
using var sha256 = SHA256.Create();
711-
var data = Encoding.UTF8.GetBytes($"LoadTest-{Guid.NewGuid()}");
707+
if (workMs <= 0) return;
712708

713-
for (var i = 0; i < iterations; i++)
709+
var sw = Stopwatch.StartNew();
710+
while (sw.ElapsedMilliseconds < workMs)
714711
{
715-
data = sha256.ComputeHash(data);
712+
// SpinWait burns CPU cycles without yielding to OS scheduler
713+
// 1000 iterations is ~1-2 microseconds, creates tight loop
714+
Thread.SpinWait(1000);
716715
}
717-
718-
// Use the result to prevent optimization
719-
// (compiler can't remove work if result is used)
720-
_ = data.Length;
721716
}
717+
722718
/*
723719
* =========================================================================
724720
* HELPER: Build Result
@@ -871,7 +867,7 @@ private void UpdateMaxResponseTime(long responseTimeMs)
871867
* Use atomic increment/decrement for concurrent request tracking
872868
*
873869
* 4. CPU Work:
874-
* Repeated SHA256 hashing with output fed back as input
870+
* Time-based spin loop (workIterations / 100 = ms per cycle)
875871
*
876872
* 5. Memory Allocation:
877873
* Allocate buffer and write pattern to force actual allocation

src/PerfProblemSimulator/wwwroot/azure-monitoring-guide.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,16 @@
227227
margin: 1rem 0;
228228
}
229229

230-
.table-wrapper {
231-
overflow-x: auto;
232-
margin: 1rem 0;
230+
.api-table.compact {
231+
font-size: 0.85rem;
232+
}
233+
234+
.api-table.compact th, .api-table.compact td {
235+
padding: 0.5rem 0.5rem;
233236
}
234237

235-
.table-wrapper .api-table {
236-
margin: 0;
237-
min-width: 700px;
238+
.api-table.compact th {
239+
white-space: normal;
238240
}
239241

240242
.api-table th, .api-table td {
@@ -1028,8 +1030,7 @@ <h3>Request Body Parameters Reference</h3>
10281030

10291031
<h3>Tuning Parameters for Different Scenarios</h3>
10301032
<p>Complete request body examples for various test scenarios:</p>
1031-
<div class="table-wrapper">
1032-
<table class="api-table">
1033+
<table class="api-table compact">
10331034
<thead>
10341035
<tr>
10351036
<th>Scenario</th>
@@ -1089,7 +1090,6 @@ <h3>Tuning Parameters for Different Scenarios</h3>
10891090
</tr>
10901091
</tbody>
10911092
</table>
1092-
</div>
10931093
</section>
10941094

10951095
<!-- Recommended Monitoring Setup Section -->

0 commit comments

Comments
 (0)