JMeter Annotation Cheatsheet

Quick Reference for Annotations, Metric Types, Scripting & Certificate Monitoring

1. What is JMeter & When to Use It

What is Apache JMeter?

  • Open-source load testing tool by Apache Foundation
  • Originally for web apps, now supports HTTP, HTTPS, SOAP, REST, FTP, JDBC, LDAP, JMS, TCP, SMTP
  • Java-based, extensible via plugins
  • GUI for test design, CLI (non-GUI) for execution
  • Supports distributed testing across multiple machines

When to Use JMeter

ScenarioUse JMeter?Why
Load testing REST APIsYesNative HTTP sampler, assertions, correlation
Stress/spike testingYesThread groups with ramp-up/scheduling
Performance regression in CI/CDYesCLI mode + JTL reports + threshold assertions
Database load testingYesJDBC sampler for SQL queries
Message queue testingYesJMS publisher/subscriber samplers
Browser-based UI testingNo*Use Selenium/Playwright (*JMeter doesn't render JS)
Simple API smoke testsMaybePostman/Newman may be simpler
Real user monitoring (RUM)NoUse browser tools / Lighthouse
Synthetic monitoring (scheduled)YesRun on cron, push metrics to Grafana
Certificate expiry monitoringYesJSR223 + SSL socket (see Section 8)

JMeter vs Alternatives

ToolStrengthBest For
JMeterFeature-rich, extensible, GUIEnterprise load testing, JDBC/JMS
k6Dev-friendly (JS), lightweightAPI load testing, CI/CD integration
GatlingScala DSL, good reportsHigh-concurrency HTTP testing
LocustPython, distributedQuick scripted load tests
wrk/heyUltra simple CLIQuick benchmark, no logic needed
ArtilleryYAML config, serverlessCloud-native API testing

Key JMeter Components Overview

Test Plan
├── Thread Group (virtual users)
│   ├── Config Elements (HTTP defaults, CSV data, cookies)
│   ├── Pre-Processors (JSR223, user params)
│   ├── Timers (constant, gaussian, throughput)
│   ├── Samplers (HTTP, JDBC, JMS, JSR223, TCP)
│   │   ├── Post-Processors (JSON extractor, regex, JSR223)
│   │   └── Assertions (response, duration, JSON, JSR223)
│   └── Logic Controllers (if, loop, transaction, random)
├── Listeners (summary, aggregate, backend)
└── setUp / tearDown Thread Groups

2. JMeter Test Plan Structure

Hierarchy: Test Plan → Thread Group → Samplers → Listeners

Key elements: Thread Groups, Samplers (HTTP, JDBC, JMS, etc.), Logic Controllers, Timers, Assertions, Pre/Post Processors, Listeners

Execution Order within a Thread Group

Configuration Elements
Pre-Processors
Timers
Sampler
Post-Processors
Assertions
Listeners

⚠️ Important

  • Configuration Elements are processed at the start of each iteration
  • Pre-Processors run before the sampler in the same scope
  • Post-Processors run after the sampler completes
  • Listeners collect results but can impact performance

3. Common Annotations / Properties

// Thread Group properties
${__threadNum}              // Current thread number
${__threadGroupName}        // Thread group name
${__machineIP()}            // Machine IP
${__machineName()}          // Machine hostname
${__time(yyyy-MM-dd)}       // Current date
${__time(HH:mm:ss)}        // Current time
${__UUID()}                 // Generate UUID
${__Random(1,100)}          // Random number
${__RandomString(10,abc)}   // Random string

// Variables
${__setProperty(name,val,)}  // Set global property
${__property(name)}          // Read global property
${__V(var_${i})}             // Dynamic variable resolution

// Logging in JSR223
log.info("Message: " + vars.get("myVar"))
log.warn("Warning!")
log.error("Error: " + SampleResult.getResponseCode())

4. JSR223 / Groovy Scripting

Pre-Processor (JSR223 PreProcessor)

// Set variables before sampler runs
vars.put("token", "Bearer abc123")
vars.put("timestamp", System.currentTimeMillis().toString())

// Access properties
def baseUrl = props.get("base.url") ?: "https://api.example.com"
vars.put("baseUrl", baseUrl)

// Generate dynamic data
import groovy.json.JsonOutput
def payload = JsonOutput.toJson([
    name: "test-${__threadNum}",
    timestamp: new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'")
])
vars.put("requestBody", payload)

Post-Processor (JSR223 PostProcessor)

// Extract from response
def response = prev.getResponseDataAsString()
import groovy.json.JsonSlurper
def json = new JsonSlurper().parseText(response)
vars.put("userId", json.data.id.toString())
vars.put("accessToken", json.token)

// Log response details
log.info("Status: ${prev.getResponseCode()}, Time: ${prev.getTime()}ms")

// Set assertion result dynamically
if (prev.getResponseCode() != "200") {
    prev.setSuccessful(false)
    prev.setResponseMessage("Expected 200, got ${prev.getResponseCode()}")
}

Assertion (JSR223 Assertion)

import groovy.json.JsonSlurper
def json = new JsonSlurper().parseText(prev.getResponseDataAsString())

if (json.status != "success") {
    AssertionResult.setFailure(true)
    AssertionResult.setFailureMessage("Expected status 'success', got '${json.status}'")
}

// Check response time
if (prev.getTime() > 2000) {
    AssertionResult.setFailure(true)
    AssertionResult.setFailureMessage("Response time ${prev.getTime()}ms exceeded 2000ms threshold")
}

Timer (JSR223 Timer)

// Dynamic think time based on scenario
def scenario = vars.get("scenario")
switch(scenario) {
    case "peak": return 500
    case "normal": return 2000
    default: return 1000
}

5. Metric Types — Which to Use When

Metric TypeJMeter ElementUse CaseExample
ThroughputSummary Report, Aggregate ReportRequests/sec capacityAPI endpoint load test
Response TimeSummary Report (Avg/Med/90th/95th/99th)Latency analysisPage load SLA validation
Error RateSummary Report (Error %)Reliability testingError threshold < 1%
Concurrent UsersActive Threads Over TimeScalability testingFind breaking point
Connect TimeResponse Times Over TimeNetwork/TLS overheadSSL handshake monitoring
Bytes ThroughputBytes Throughput Over TimeBandwidth testingFile download load test
Hits/secHits per SecondServer capacityCDN/proxy saturation
LatencyResponse Time GraphTime to first byte (TTFB)Backend vs network split
TPSTransactions per SecondBusiness transaction rateCheckout flow capacity
PercentilesAggregate Report (p90/p95/p99)SLA compliance99th percentile < 500ms

Detailed Use Cases

Load Testing (Capacity)

  • Primary: Throughput (req/s), Error Rate
  • Secondary: Response Time p95, Active Threads
  • Goal: Find max throughput before error rate > threshold

Stress Testing (Breaking Point)

  • Primary: Error Rate, Response Time p99
  • Secondary: Throughput, Server Resources (via PerfMon)
  • Goal: Find when system degrades/fails

Soak/Endurance Testing

  • Primary: Memory usage over time, Error Rate trend
  • Secondary: Response Time drift, Thread count
  • Goal: Detect memory leaks, connection pool exhaustion

Spike Testing

  • Primary: Recovery time, Error Rate during spike
  • Secondary: Queue depth, Response Time during/after
  • Goal: Validate auto-scaling and recovery

API Benchmarking

  • Primary: Response Time (avg, median, p95, p99)
  • Secondary: Throughput, Error Rate
  • Goal: Compare performance across versions/releases

6. Backend Listener — InfluxDB / Grafana Integration

Backend Listener for InfluxDB

<!-- Backend Listener for InfluxDB -->
<BackendListener guiclass="BackendListenerGui" testclass="BackendListener">
  <elementProp name="arguments">
    <stringProp name="influxdbUrl">http://influxdb:8086/write?db=jmeter</stringProp>
    <stringProp name="application">my-api</stringProp>
    <stringProp name="measurement">jmeter</stringProp>
    <stringProp name="summaryOnly">false</stringProp>
    <stringProp name="samplersRegex">.*</stringProp>
    <stringProp name="testTitle">Load Test ${__time(yyyy-MM-dd HH:mm)}</stringProp>
  </elementProp>
  <stringProp name="classname">
    org.apache.jmeter.visualizers.backend.influxdb.InfluxdbBackendListenerClient
  </stringProp>
</BackendListener>

Annotate Grafana from JMeter

// JSR223 Sampler at test start/end
import groovy.json.JsonOutput
def annotation = JsonOutput.toJson([
    text: "Load Test Started - ${vars.get('testTitle')}",
    tags: ["jmeter", "load-test", "${vars.get('environment')}"]
])

def post = new URL("http://grafana:3000/api/annotations").openConnection()
post.setRequestMethod("POST")
post.setRequestProperty("Content-Type", "application/json")
post.setRequestProperty("Authorization", "Bearer ${props.get('grafana.token')}")
post.setDoOutput(true)
post.getOutputStream().write(annotation.getBytes("UTF-8"))
def responseCode = post.getResponseCode()
log.info("Grafana annotation created: ${responseCode}")

7. Grafana Annotations from JMeter

Annotating Test Phases

// Mark test phases in Grafana
def createAnnotation(String text, List<String> tags) {
    import groovy.json.JsonOutput
    def body = JsonOutput.toJson([
        dashboardId: 0,  // 0 = global annotation
        time: System.currentTimeMillis(),
        text: text,
        tags: tags
    ])

    def conn = new URL("${props.get('grafana.url')}/api/annotations").openConnection()
    conn.setRequestMethod("POST")
    conn.setRequestProperty("Content-Type", "application/json")
    conn.setRequestProperty("Authorization", "Bearer ${props.get('grafana.api.key')}")
    conn.setDoOutput(true)
    conn.getOutputStream().write(body.getBytes("UTF-8"))
    return conn.getResponseCode()
}

// Usage in setUp Thread Group:
createAnnotation("Ramp-up Started: ${__threadGroupName}", ["jmeter", "ramp-up"])

// Usage in tearDown Thread Group:
createAnnotation("Test Complete: ${__threadGroupName}", ["jmeter", "complete"])

Annotation Use Cases

🏁 Test start/end markers

Mark when load tests begin and complete

📈 Ramp-up/steady-state/ramp-down

Identify different test phases in dashboards

🚨 Error threshold breaches

Alert when error rate exceeds acceptable limits

🚀 Deployment correlation

Mark when deployments happen to correlate with metrics

8. Certificate Expiry Monitoring with JMeter

Use Case

Monitor SSL/TLS certificate expiry dates across multiple endpoints. Alert when certificates are approaching expiration.

JSR223 Sampler — Certificate Expiry Checker

import javax.net.ssl.*
import java.security.cert.X509Certificate
import java.text.SimpleDateFormat
import groovy.json.JsonOutput

def host = vars.get("target_host") ?: "example.com"
def port = (vars.get("target_port") ?: "443").toInteger()
def warningDays = (vars.get("warning_days") ?: "30").toInteger()
def criticalDays = (vars.get("critical_days") ?: "7").toInteger()

try {
    // Create SSL connection and extract certificate
    def sslCtx = SSLContext.getInstance("TLS")
    def tm = [
        checkClientTrusted: { chain, type -> },
        checkServerTrusted: { chain, type -> },
        getAcceptedIssuers: { null }
    ] as X509TrustManager
    sslCtx.init(null, [tm] as TrustManager[], null)

    def factory = sslCtx.getSocketFactory()
    def socket = factory.createSocket(host, port) as SSLSocket
    socket.startHandshake()

    def certs = socket.getSession().getPeerCertificates()
    def x509 = certs[0] as X509Certificate
    socket.close()

    // Calculate days until expiry
    def expiry = x509.getNotAfter()
    def now = new Date()
    def daysUntilExpiry = ((expiry.time - now.time) / (1000 * 60 * 60 * 24)).intValue()
    def sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    // Set result variables
    vars.put("cert_subject", x509.getSubjectDN().toString())
    vars.put("cert_issuer", x509.getIssuerDN().toString())
    vars.put("cert_expiry_date", sdf.format(expiry))
    vars.put("cert_days_remaining", daysUntilExpiry.toString())
    vars.put("cert_serial", x509.getSerialNumber().toString(16))

    // Build response
    def result = [
        host: host, port: port,
        subject: x509.getSubjectDN().toString(),
        issuer: x509.getIssuerDN().toString(),
        notBefore: sdf.format(x509.getNotBefore()),
        notAfter: sdf.format(expiry),
        daysRemaining: daysUntilExpiry,
        serial: x509.getSerialNumber().toString(16),
        status: daysUntilExpiry <= criticalDays ? "CRITICAL" :
                daysUntilExpiry <= warningDays ? "WARNING" : "OK"
    ]

    SampleResult.setResponseData(
        JsonOutput.prettyPrint(JsonOutput.toJson(result)), "UTF-8")
    SampleResult.setResponseCode("200")
    SampleResult.setResponseMessage(
        "Certificate expires in ${daysUntilExpiry} days")

    // Mark as failed if critical
    if (daysUntilExpiry <= criticalDays) {
        SampleResult.setSuccessful(false)
        SampleResult.setResponseMessage(
            "CRITICAL: Certificate expires in ${daysUntilExpiry} days!")
        log.error("CRITICAL: ${host} cert expires in ${daysUntilExpiry} days!")
    } else if (daysUntilExpiry <= warningDays) {
        log.warn("WARNING: ${host} cert expires in ${daysUntilExpiry} days")
    } else {
        SampleResult.setSuccessful(true)
        log.info("OK: ${host} cert expires in ${daysUntilExpiry} days")
    }

} catch (Exception e) {
    SampleResult.setSuccessful(false)
    SampleResult.setResponseCode("500")
    SampleResult.setResponseMessage("Certificate check failed: ${e.message}")
    log.error("Certificate check failed for ${host}: ${e.message}")
}

Multi-host Monitoring Setup (CSV Data Set Config)

# hosts.csv
target_host,target_port,warning_days,critical_days
api.example.com,443,30,7
web.example.com,443,30,7
mail.example.com,993,30,7
internal.example.com,8443,14,3

Grafana Dashboard Integration

  • Push cert expiry days as a metric via Backend Listener
  • Create Grafana alert: cert_days_remaining < 14
  • Annotation when cert status changes to WARNING/CRITICAL

9. Common Assertions

// Response Code Assertion
// GUI: Add > Assertions > Response Assertion
// Fields to Test: Response Code, Pattern: 200

// Duration Assertion
// GUI: Add > Assertions > Duration Assertion
// Duration (ms): 2000

// JSON Assertion (JSR223)
import groovy.json.JsonSlurper
def json = new JsonSlurper().parseText(prev.getResponseDataAsString())
assert json.data != null : "data field is null"
assert json.data.size() > 0 : "data array is empty"
assert json.status == "OK" : "status is not OK: ${json.status}"

// JSON Path Assertion
// GUI: Add > Assertions > JSON Assertion
// JSON Path: $.data.id
// Expected Value: \d+  (regex)

// Size Assertion
// GUI: Add > Assertions > Size Assertion
// Response Size: > 100 bytes

// XPath Assertion (for XML/SOAP)
// GUI: Add > Assertions > XPath Assertion
// XPath: //status[text()='success']

// Compare Assertion (response vs baseline)
// Useful for regression testing

10. Useful CLI Commands

# Run test in non-GUI mode (production)
jmeter -n -t test.jmx -l results.jtl -e -o report/

# Run with properties
jmeter -n -t test.jmx -Jthreads=100 -Jrampup=60 -Jduration=300

# Generate HTML report from existing results
jmeter -g results.jtl -o report/

# Run distributed test
jmeter -n -t test.jmx -R server1,server2,server3

# Heap size (for large tests)
export JVM_ARGS="-Xms2g -Xmx4g"
jmeter -n -t test.jmx -l results.jtl

# Plugin manager CLI
jmeter -Jplugin_manager.command=install \
  -Jplugin_manager.plugins="jpgc-graphs-basic"

# Convert JTL to CSV
jmeter -g results.jtl -o /tmp/report \
  -Jjmeter.reportgenerator.overall_granularity=1000

11. Best Practices

  • ▸Always run in non-GUI mode for actual tests (-n flag)
  • ▸Use CSV Data Set Config for parameterization (not hardcoded values)
  • ▸Disable all listeners during load tests (use Backend Listener instead)
  • ▸Use Constant Throughput Timer for realistic load patterns
  • ▸Correlation: extract tokens/IDs with JSON/RegEx extractors, don't hardcode
  • ▸Think time: add realistic delays between requests
  • ▸Use Transaction Controllers to group related requests as one business transaction
  • ▸Assertions cost performance: keep them minimal in high-load scenarios
  • ▸Distribute tests across multiple JMeter slaves for high concurrency
  • ▸Version control your .jmx files (they're XML)