Skip to content

Commit fcb90b8

Browse files
Added load endpoint (#418)
Co-authored-by: oleksandrh <[email protected]>
1 parent 162a83f commit fcb90b8

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityController.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,39 @@ public ResponseEntity<String> recreateAndPopulateLogs(@RequestParam(name = "coun
155155
}
156156
}
157157

158+
@PostMapping("/io-intensive-load")
159+
public ResponseEntity<String> createIOIntensiveLoad(@RequestParam(name = "duration", defaultValue = "5") int durationMinutes,
160+
@RequestParam(name = "threads", defaultValue = "6") int numThreads,
161+
@RequestParam(name = "limit", defaultValue = "400000") int limit) {
162+
logger.warn("Received request to create I/O INTENSIVE LOAD for {} minutes with {} threads and {} limit - This will MAX OUT disk I/O operations!",
163+
durationMinutes, numThreads, limit);
164+
if (durationMinutes <= 0) {
165+
return ResponseEntity.badRequest().body("Duration must be a positive integer.");
166+
}
167+
if (durationMinutes > 60) {
168+
return ResponseEntity.badRequest().body("Duration too high for I/O intensive load - maximum 60 minutes to prevent storage overload.");
169+
}
170+
if (numThreads <= 0) {
171+
return ResponseEntity.badRequest().body("Number of threads must be a positive integer.");
172+
}
173+
if (numThreads > 20) {
174+
return ResponseEntity.badRequest().body("Too many threads for I/O intensive load - maximum 20 to prevent system crash.");
175+
}
176+
if (limit <= 0) {
177+
return ResponseEntity.badRequest().body("Limit must be a positive integer.");
178+
}
179+
if (limit > 1000000) {
180+
return ResponseEntity.badRequest().body("Limit too high for I/O intensive load - maximum 1,000,000 to prevent excessive resource usage.");
181+
}
182+
try {
183+
dataService.createIOIntensiveLoad(durationMinutes, numThreads, limit);
184+
return ResponseEntity.ok("Successfully completed I/O INTENSIVE LOAD for " + durationMinutes + " minutes with " + numThreads + " threads and " + limit + " limit - Disk I/O was maxed out!");
185+
} catch (Exception e) {
186+
logger.error("Error during I/O intensive load", e);
187+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during I/O intensive load: " + e.getMessage());
188+
}
189+
}
190+
158191
private void performObservableOperation(String operationName) {
159192
Span span = otelTracer.spanBuilder(operationName)
160193
.setSpanKind(SpanKind.CLIENT)

src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityDataService.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Locale;
2727
import java.util.concurrent.TimeUnit;
2828
import java.util.Random;
29+
import java.util.concurrent.atomic.AtomicInteger;
2930

3031
@Service
3132
public class ClinicActivityDataService {
@@ -200,4 +201,106 @@ private String csv(String value) {
200201
String escaped = value.replace("\"", "\"\"").replace("\\", "\\\\");
201202
return '"' + escaped + '"';
202203
}
204+
205+
public void createIOIntensiveLoad(int durationMinutes, int numThreads, int limit) {
206+
logger.warn("Starting I/O INTENSIVE load test for {} minutes with {} threads and {} limit - This will MAX OUT disk I/O operations!",
207+
durationMinutes, numThreads, limit);
208+
long startTime = System.currentTimeMillis();
209+
long endTime = startTime + (durationMinutes * 60 * 1000L);
210+
211+
try {
212+
AtomicInteger globalOperationCount = new AtomicInteger(0);
213+
List<Thread> threads = new ArrayList<>();
214+
215+
logger.info("Creating {} I/O intensive threads with {} record limit per query...", numThreads, limit);
216+
217+
// Create I/O intensive threads
218+
for (int t = 0; t < numThreads; t++) {
219+
final int threadId = t;
220+
Thread ioThread = new Thread(() -> {
221+
try {
222+
executeIOIntensiveThread(threadId, endTime, globalOperationCount, limit);
223+
} catch (Exception e) {
224+
logger.error("Error in I/O intensive thread {}", threadId, e);
225+
}
226+
});
227+
228+
ioThread.setName("IOIntensiveThread-" + threadId);
229+
threads.add(ioThread);
230+
}
231+
232+
// Start all threads
233+
logger.info("Starting all {} I/O intensive threads...", numThreads);
234+
for (Thread thread : threads) {
235+
thread.start();
236+
}
237+
238+
// Wait for all threads to complete
239+
for (Thread thread : threads) {
240+
try {
241+
thread.join();
242+
} catch (InterruptedException e) {
243+
Thread.currentThread().interrupt();
244+
logger.warn("Interrupted while waiting for I/O thread: {}", thread.getName());
245+
}
246+
}
247+
248+
long actualEndTime = System.currentTimeMillis();
249+
logger.warn("Completed I/O INTENSIVE load test in {} ms with {} threads and {} limit. Total operations: {}",
250+
(actualEndTime - startTime), numThreads, limit, globalOperationCount.get());
251+
252+
} catch (Exception e) {
253+
logger.error("Error during I/O intensive load test", e);
254+
throw new RuntimeException("Error during I/O intensive load test: " + e.getMessage(), e);
255+
}
256+
}
257+
258+
private void executeIOIntensiveThread(int threadId, long endTime, AtomicInteger globalOperationCount, int limit) {
259+
Random random = new Random();
260+
Faker faker = new Faker(new Locale("en-US"));
261+
int localOperationCount = 0;
262+
263+
logger.info("I/O Thread {} starting I/O intensive operations with {} record limit...", threadId, limit);
264+
265+
while (System.currentTimeMillis() < endTime) {
266+
try {
267+
// LARGE SEQUENTIAL SCAN - Forces full table scan I/O
268+
jdbcTemplate.queryForList(
269+
"SET work_mem = '512MB';" +
270+
"SELECT id, activity_type, numeric_value, event_timestamp, payload " +
271+
"FROM clinic_activity_logs " +
272+
"WHERE LENGTH(payload) > 100 " +
273+
"ORDER BY random()" +
274+
"LIMIT " + limit);
275+
276+
277+
localOperationCount++;
278+
int currentGlobalCount = globalOperationCount.incrementAndGet();
279+
280+
// Log progress every 100 operations per thread
281+
if (localOperationCount % 100 == 0) {
282+
long remainingTime = (endTime - System.currentTimeMillis()) / 1000;
283+
logger.info("I/O Thread {} completed {} operations (Global: {}). Time remaining: {}s",
284+
threadId, localOperationCount, currentGlobalCount, remainingTime);
285+
}
286+
287+
// No sleep - continuous I/O operations for maximum I/O pressure
288+
// But avoid overwhelming the system with a tiny yield
289+
if (localOperationCount % 50 == 0) {
290+
Thread.yield();
291+
}
292+
293+
} catch (Exception e) {
294+
logger.error("Error in I/O operation for thread {}", threadId, e);
295+
try {
296+
Thread.sleep(10); // Brief pause on error
297+
} catch (InterruptedException ie) {
298+
Thread.currentThread().interrupt();
299+
break;
300+
}
301+
}
302+
}
303+
304+
logger.info("I/O Thread {} completed {} total I/O operations", threadId, localOperationCount);
305+
}
203306
}

0 commit comments

Comments
 (0)