Gatling is a powerful open-source load and performance testing framework built on Scala and Akka. It's widely used in enterprise Java and Scala shops to simulate thousands of concurrent users and generate detailed HTML performance reports. But Gatling tests run on-demand — they don't tell you what happens to your service between test runs or in production.
Vigilmon fills that gap with continuous, 24/7 uptime monitoring. In this tutorial you'll integrate Vigilmon with your Gatling test targets so you always know whether the services you're load testing are actually reachable — before, during, and after every simulation.
Why pair Gatling with external uptime monitoring
Gatling answers: "Can my service handle N concurrent users and respond in under X milliseconds?"
Vigilmon answers: "Is my service responding right now, from the outside world, without any synthetic traffic?"
| Scenario | Gatling catches it? | Vigilmon catches it? |
|----------|--------------------|--------------------|
| API returns 500 under 500 concurrent users | Yes | Yes (if persistent) |
| Service crashes at 3am with 0 users | No | Yes |
| SSL certificate expired | No | Yes |
| Deploy broke /api/checkout but not /health | Only if you test that path | Yes, if you monitor it |
| DB becomes unreachable during business hours | Only during a test run | Yes, via TCP monitoring |
You need both.
What you'll need
- Java 11+ and Maven or Gradle (for Gatling)
- A service to performance-test
- A free Vigilmon account — no credit card required
Step 1: Add a health endpoint to your application
Give Vigilmon a dedicated /health route that verifies your application's core dependencies:
Spring Boot (Java):
@RestController
public class HealthController {
@Autowired
private DataSource dataSource;
@GetMapping("/health")
public ResponseEntity<Map<String, String>> health() {
try {
dataSource.getConnection().isValid(1);
return ResponseEntity.ok(Map.of(
"status", "ok",
"timestamp", Instant.now().toString()
));
} catch (SQLException e) {
return ResponseEntity.status(503).body(Map.of(
"status", "error",
"message", "Database unavailable"
));
}
}
}
Scala / Play Framework:
class HealthController @Inject()(cc: ControllerComponents, db: Database)
extends AbstractController(cc) {
def health() = Action {
try {
db.withConnection { conn =>
conn.isValid(1)
}
Ok(Json.obj("status" -> "ok"))
} catch {
case _: Exception =>
ServiceUnavailable(Json.obj("status" -> "error"))
}
}
}
Ktor (Kotlin):
routing {
get("/health") {
call.respond(mapOf("status" to "ok", "timestamp" to System.currentTimeMillis()))
}
}
The health endpoint should be fast (no heavy computation), lightweight (no authentication required), and honest (return 503 when dependencies fail, not always 200).
Step 2: Write a Gatling simulation
Create a simulation file that tests your API endpoints. Gatling uses a DSL in Scala:
// src/gatling/scala/ApiSimulation.scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class ApiSimulation extends Simulation {
val httpProtocol = http
.baseUrl("https://your-api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
.header("Authorization", "Bearer ${API_TOKEN}")
// Scenario: typical read-heavy workload
val readScenario = scenario("Read API")
.exec(
http("Health Check")
.get("/health")
.check(status.is(200))
.check(jsonPath("$.status").is("ok"))
)
.pause(1)
.exec(
http("List Products")
.get("/api/products?page=1&limit=20")
.check(status.is(200))
.check(responseTimeInMillis.lte(500))
)
.pause(2)
.exec(
http("Get Product Detail")
.get("/api/products/1")
.check(status.is(200))
)
// Scenario: write workload
val writeScenario = scenario("Write API")
.exec(
http("Create Order")
.post("/api/orders")
.body(StringBody("""{"productId": 1, "quantity": 2}"""))
.check(status.is(201))
)
setUp(
readScenario.inject(
rampUsers(100).during(2.minutes),
constantUsersPerSec(50).during(5.minutes),
rampUsersPerSec(50).to(200).during(2.minutes),
constantUsersPerSec(200).during(5.minutes)
),
writeScenario.inject(
rampUsers(20).during(2.minutes),
constantUsersPerSec(10).during(10.minutes)
)
)
.protocols(httpProtocol)
.assertions(
global.responseTime.percentile3.lt(500), // 95th percentile < 500ms
global.successfulRequests.percent.gt(99) // < 1% errors
)
}
Run with Maven:
mvn gatling:test -Dgatling.simulationClass=ApiSimulation
Gatling produces an HTML report in target/gatling/ with response time percentiles, throughput graphs, and error breakdowns.
Step 3: Set up Vigilmon monitors for your Gatling targets
While Gatling runs on demand, Vigilmon runs 24/7. Configure monitors for every endpoint you're performance-testing:
- Log in to vigilmon.online
- Go to Monitors → New Monitor → HTTP / HTTPS
- URL:
https://your-api.example.com/health - Check interval: 1 minute
- Expected status code:
200 - Response body contains:
"status":"ok"(optional but useful) - Save
Add monitors for your critical paths:
| Monitor | URL | Expected status |
|---------|-----|----------------|
| Health | /health | 200 |
| Products API | /api/products?page=1&limit=20 | 200 |
| Orders API | /api/orders (GET) | 200 |
For each monitor, Vigilmon probes from multiple regions every minute and alerts you immediately on failure.
Step 4: TCP monitoring for dependencies
Your Gatling simulation may stress APIs that depend on databases, caches, or queues. Add TCP monitors for those:
- Monitors → New Monitor → TCP Port
- Host:
your-db-server.example.com - Port:
5432(PostgreSQL) or3306(MySQL) or6379(Redis) - Save
If the database TCP port becomes unreachable — even when the HTTP API still responds with cached data — Vigilmon will alert you before your next Gatling run reveals the issue through cascading failures.
Step 5: Configure real-time alerts
Set up alert channels so your team knows the moment a monitored service degrades:
Slack:
- Alert Channels → Add Channel → Webhook
- Enter your Slack webhook URL
- Assign to all your monitors
Email:
- Alert Channels → Add Channel → Email
- Enter your engineering team's on-call email
- Assign to all your monitors
Sample incident payload Vigilmon sends:
{
"monitor_name": "Products API",
"status": "down",
"url": "https://your-api.example.com/api/products",
"started_at": "2024-07-01T09:15:00Z",
"duration_seconds": 180
}
Cross-referencing this with your Gatling report timeline helps you identify whether the outage was caused by load or by an independent failure.
Step 6: Run Gatling in CI with pre-flight monitoring checks
Before running a destructive load test against staging, verify the target is healthy:
# .github/workflows/performance.yml
name: Performance Tests
on:
schedule:
- cron: '0 1 * * 1' # weekly on Monday at 1am
workflow_dispatch:
jobs:
gatling:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Pre-flight health check
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
${{ secrets.STAGING_URL }}/health)
if [ "$STATUS" != "200" ]; then
echo "Pre-flight failed: service returned $STATUS. Aborting load test."
exit 1
fi
echo "Pre-flight passed. Service is healthy."
- name: Run Gatling simulation
run: mvn gatling:test -Dgatling.simulationClass=ApiSimulation
env:
STAGING_URL: ${{ secrets.STAGING_URL }}
API_TOKEN: ${{ secrets.API_TOKEN }}
- name: Upload Gatling report
uses: actions/upload-artifact@v4
if: always()
with:
name: gatling-report
path: target/gatling/
The pre-flight check prevents wasted CI time and misleading results when the service is already down before the test begins.
Step 7: Correlate Gatling reports with Vigilmon incident history
Gatling's HTML report shows you exactly when latency spiked or errors appeared. Vigilmon's incident log shows you when the service became externally unreachable. Comparing the two gives you:
- Latency spike with no Vigilmon incident: service degraded but didn't fully go down — worth investigating capacity
- Vigilmon incident matches Gatling error spike: the load test caused an actual outage
- Vigilmon incident with no Gatling test running: an independent failure in production — unrelated to load
Use this correlation to set your load thresholds realistically. If Vigilmon fires at 150 VUs but your test runs to 300, your production capacity is half of what you're testing.
Step 8: Status page for your tested services
If you're load-testing a public-facing API, give your users visibility:
- Status Pages → New Status Page
- Name it (e.g. "API Performance Status")
- Add your HTTP and TCP monitors
- Publish and share the URL
During planned load tests, you can post a maintenance notice to the status page so users know degraded performance is expected.
Gatling + Vigilmon monitoring architecture
[Gatling Simulation] ──── fires synthetic load ────► [Your API]
│
[Vigilmon (1-min checks)] ── probes from outside ───────►│
│
[Alert Channels] ◄─── incident fires if API goes down ───┘
│
[Status Page] ◄─────── reflects real-time monitor state ─┘
What's next
- Gatling Enterprise for distributed load generation across multiple AWS regions — pair with Vigilmon's multi-region monitoring for a complete geographic picture
- SSL certificate monitoring in Vigilmon for all your Gatling test target domains
- Heartbeat monitors to verify your Gatling CI jobs are actually running on schedule — Vigilmon alerts you if a scheduled test job stops reporting in
Get started free at vigilmon.online — set up your first monitor in under a minute, no credit card required.