Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 89 additions & 75 deletions gatling-tests/src/test/java/simulation/CreateProductSimulation.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static io.gatling.javaapi.core.CoreDsl.jsonPath;
import static io.gatling.javaapi.core.CoreDsl.nothingFor;
import static io.gatling.javaapi.core.CoreDsl.rampUsers;
import static io.gatling.javaapi.core.CoreDsl.rampUsersPerSec;
import static io.gatling.javaapi.core.CoreDsl.scenario;
import static io.gatling.javaapi.http.HttpDsl.header;
import static io.gatling.javaapi.http.HttpDsl.http;
Expand All @@ -18,7 +19,6 @@
import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -33,13 +33,13 @@ public class CreateProductSimulation extends BaseSimulation {
private static final Logger LOGGER = LoggerFactory.getLogger(CreateProductSimulation.class);

// Configuration parameters - can be externalized to properties
private static final int RAMP_USERS = Integer.parseInt(System.getProperty("rampUsers", "3"));
private static final int RAMP_USERS = Integer.parseInt(System.getProperty("rampUsers", "50"));
private static final int CONSTANT_USERS =
Integer.parseInt(System.getProperty("constantUsers", "15"));
Integer.parseInt(System.getProperty("constantUsers", "100"));
private static final int RAMP_DURATION_SECONDS =
Integer.parseInt(System.getProperty("rampDuration", "30"));
private static final int TEST_DURATION_SECONDS =
Integer.parseInt(System.getProperty("testDuration", "60"));
Integer.parseInt(System.getProperty("testDuration", "180"));

// Breaking down the test flow into reusable components for better readability and
// maintainability
Expand All @@ -49,13 +49,13 @@ public class CreateProductSimulation extends BaseSimulation {
.body(
StringBody(
"""
{
"productCode": "#{productCode}",
"productName": "#{productName}",
"price": #{price},
"description": "Performance test product"
}
"""))
{
"productCode": "#{productCode}",
"productName": "#{productName}",
"price": #{price},
"description": "Performance test product"
}
"""))
.asJson()
.check(status().is(201))
.check(header("location").saveAs("productLocation")))
Expand Down Expand Up @@ -140,25 +140,25 @@ public class CreateProductSimulation extends BaseSimulation {
.body(
StringBody(
"""
{
"customerId": #{customerId},
"items": [
{
"productCode": "#{productCode}",
"quantity": #{quantity},
"productPrice": #{price}
}
],
"deliveryAddress": {
"addressLine1": "123 Performance Test St",
"addressLine2": "Suite 456",
"city": "Test City",
"state": "TS",
"zipCode": "12345",
"country": "Test Country"
}
}
"""))
{
"customerId": #{customerId},
"items": [
{
"productCode": "#{productCode}",
"quantity": #{quantity},
"productPrice": #{price}
}
],
"deliveryAddress": {
"addressLine1": "123 Performance Test St",
"addressLine2": "Suite 456",
"city": "Test City",
"state": "TS",
"zipCode": "12345",
"country": "Test Country"
}
}
"""))
.asJson()
.check(status().is(201))
.check(header("location").saveAs("orderLocation")))
Expand All @@ -173,34 +173,38 @@ public class CreateProductSimulation extends BaseSimulation {
private final ScenarioBuilder productWorkflow =
scenario("E2E Product Creation Workflow")
.feed(enhancedProductFeeder())
.exec(createProduct)
.pause(Duration.ofMillis(10)) // Add pause to reduce load
.exec(getProduct)
.pause(Duration.ofMillis(10)) // Add pause to reduce load
.exec(getInventory)
.pause(Duration.ofMillis(20)) // More pause before the critical update
// Simplified workflow to avoid syntax issues
.exec(
session -> {
// Add safeguard to skip inventory update if inventory info is
// missing or invalid
if (session.contains("inventoryResponseBody")
&& session.getString("inventoryResponseBody") != null
&& !Objects.requireNonNull(
session.getString("inventoryResponseBody"))
.trim()
.isEmpty()) {
return session;
} else {
LOGGER.warn(
"Skipping inventory update due to missing inventory data");
// Return marked as failed so we don't attempt to create an
// order based on invalid inventory
return session.markAsFailed();
}
})
.exec(updateInventory)
.pause(Duration.ofMillis(10)) // Add pause to reduce load
.exec(createOrder);
exec(createProduct)
.exec(
session -> {
logRequestTime(
"Create Product Complete",
System.currentTimeMillis());
return session;
})
.pause(200) // Reduced pause time from 500 to 200 ms
.exec(getProduct)
.pause(100) // Reduced pause time from 300 to 100 ms
.exec(getInventory)
.pause(100) // Reduced pause time
// Only attempt to update inventory when a valid
// inventory response body is present in the
// session. If it's missing or empty, skip update
// entirely to avoid runtime exceptions.
.doIf(
session ->
session.contains("inventoryResponseBody")
&& session.getString(
"inventoryResponseBody")
!= null
&& !session.getString(
"inventoryResponseBody")
.trim()
.isEmpty())
.then(exec(updateInventory))
.pause(300)
.exec(createOrder));
Comment on lines +186 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix pause units to match the intended millisecond delays

pause(long) in the Gatling Java DSL interprets the argument in seconds. The new pause(200), pause(100), and pause(300) calls therefore inject multi-minute sleeps (200 s, 100 s, 300 s) instead of the 200/100/300 ms noted in the comments, completely flattening scenario throughput. Please switch to the Duration overloads so the waits really are in milliseconds.

-                                    .pause(200) // Reduced pause time from 500 to 200 ms
+                                    .pause(Duration.ofMillis(200)) // Reduced pause time from 500 to 200 ms
...
-                                    .pause(100) // Reduced pause time from 300 to 100 ms
+                                    .pause(Duration.ofMillis(100)) // Reduced pause time from 300 to 100 ms
...
-                                    .pause(300)
+                                    .pause(Duration.ofMillis(300))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.pause(200) // Reduced pause time from 500 to 200 ms
.exec(getProduct)
.pause(100) // Reduced pause time from 300 to 100 ms
.exec(getInventory)
.pause(100) // Reduced pause time
// Only attempt to update inventory when a valid
// inventory response body is present in the
// session. If it's missing or empty, skip update
// entirely to avoid runtime exceptions.
.doIf(
session ->
session.contains("inventoryResponseBody")
&& session.getString(
"inventoryResponseBody")
!= null
&& !session.getString(
"inventoryResponseBody")
.trim()
.isEmpty())
.then(exec(updateInventory))
.pause(300)
.exec(createOrder));
// Reduced pause time from 500 to 200 ms
.pause(Duration.ofMillis(200))
.exec(getProduct)
// Reduced pause time from 300 to 100 ms
.pause(Duration.ofMillis(100))
.exec(getInventory)
// Reduced pause time
.pause(Duration.ofMillis(100))
// Only attempt to update inventory when a valid
// inventory response body is present in the session.
.doIf(
session ->
session.contains("inventoryResponseBody")
&& session.getString("inventoryResponseBody") != null
&& !session.getString("inventoryResponseBody")
.trim()
.isEmpty())
.then(exec(updateInventory))
// Restore intended 300 ms pause
.pause(Duration.ofMillis(300))
.exec(createOrder));
🤖 Prompt for AI Agents
In gatling-tests/src/test/java/simulation/CreateProductSimulation.java around
lines 186 to 207, the Pause calls use pause(long) which interprets values as
seconds, causing multi-minute delays; change these to the Duration overloads
(e.g., Duration.ofMillis(200), Duration.ofMillis(100), Duration.ofMillis(300))
so the pauses are milliseconds as intended, and add/import java.time.Duration if
not already imported; keep the same relative placement of pauses and chain
calls.


/**
* Prepares the inventory update request by generating a new inventory quantity
Expand Down Expand Up @@ -323,29 +327,39 @@ public CreateProductSimulation() {
runHealthChecks();

LOGGER.info(
"Running with warm-up phase of {} seconds with a single user to initialize Kafka",
"Running with warm-up phase of {} seconds with multiple users to initialize Kafka",
KAFKA_INIT_DELAY_SECONDS);

// Global assertions to validate overall service performance
this.setUp(
productWorkflow
// Small pause between steps to simulate realistic user behavior
.pause(Duration.ofMillis(500))
.injectOpen(
// Initial single user for Kafka initialization
atOnceUsers(1),
// Wait for Kafka initialization to complete
nothingFor(Duration.ofSeconds(KAFKA_INIT_DELAY_SECONDS)),
// Ramp up users phase for gradual load increase
rampUsers(RAMP_USERS)
.during(Duration.ofSeconds(RAMP_DURATION_SECONDS)),
// Constant load phase to test system stability
constantUsersPerSec(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS))))
// Configure simulation with robust load profile
setUp(
productWorkflow.injectOpen(
// Initial users for Kafka initialization and baseline - increased
// from 3 to 10
atOnceUsers(10),

// Wait for Kafka initialization to complete
nothingFor(Duration.ofSeconds(KAFKA_INIT_DELAY_SECONDS)),

// Initial ramp using users - increased for more load
rampUsers(RAMP_USERS)
.during(Duration.ofSeconds(RAMP_DURATION_SECONDS)),

// Constant load phase with shorter duration but higher throughput
constantUsersPerSec(CONSTANT_USERS / 2)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),

// Ramp up to peak load more quickly
rampUsersPerSec(CONSTANT_USERS / 2)
.to(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),

// Sustained peak load phase
constantUsersPerSec(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 2))))
Comment on lines +348 to +358
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent zero-rate injection when halving constantUsers

Line 348 currently divides the user rate with integer math, so a legitimate override like -DconstantUsers=1 yields CONSTANT_USERS / 2 == 0. Gatling’s constantUsersPerSec(0) and rampUsersPerSec(0) throw an IllegalArgumentException at runtime, so any low-rate configuration now fails outright. Please keep these stages in the positive domain by switching to floating-point division (or otherwise guarding against zero).

-                                constantUsersPerSec(CONSTANT_USERS / 2)
+                                constantUsersPerSec(CONSTANT_USERS / 2.0)
...
-                                rampUsersPerSec(CONSTANT_USERS / 2)
+                                rampUsersPerSec(CONSTANT_USERS / 2.0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constantUsersPerSec(CONSTANT_USERS / 2)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),
// Ramp up to peak load more quickly
rampUsersPerSec(CONSTANT_USERS / 2)
.to(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),
// Sustained peak load phase
constantUsersPerSec(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 2))))
constantUsersPerSec(CONSTANT_USERS / 2.0)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),
// Ramp up to peak load more quickly
rampUsersPerSec(CONSTANT_USERS / 2.0)
.to(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 4)),
// Sustained peak load phase
constantUsersPerSec(CONSTANT_USERS)
.during(Duration.ofSeconds(TEST_DURATION_SECONDS / 2))))
🤖 Prompt for AI Agents
In gatling-tests/src/test/java/simulation/CreateProductSimulation.java around
lines 348 to 358, dividing CONSTANT_USERS by 2 uses integer division which
yields zero for small values (e.g. 1) and causes Gatling to throw
IllegalArgumentException; change the math to floating-point (e.g. divide by 2.0
or cast to double) or clamp the result to a positive minimum (Math.max(1.0,
CONSTANT_USERS / 2.0)) so constantUsersPerSec(...) and rampUsersPerSec(...)
never receive 0.

.protocols(httpProtocol)
.assertions(
// Add global performance SLA assertions
global().responseTime().mean().lt(1500), // Mean response time under 1.5s
global().responseTime().mean().lt(2000), // Mean response time under 2s
global().responseTime()
.percentile(95)
.lt(5000), // 95% of responses under 5s
Expand Down
Loading