jmeter-java-dsl

repository·master·Indexed 19 days ago

https://github.com/abstracta/jmeter-java-dsl

A Java API for writing programmatic, Git-friendly performance tests using Apache JMeter as the underlying engine. It provides a fluent DSL to define thread groups, samplers, and writers, with native support for JUnit 5. Key features include auto-stop conditions to fail tests early based on metrics, tools for migrating JMX files via jmx2dsl, and debugging utilities such as debugPostProcessor, dummySampler, and showInGui().

Tokens
53.2K
Snippets
152
Records
188
Agent score
68%

What's inside jmeter-java-dsl

  1. Real-time metrics visualization and historic data storage

    master

    By default, jmeter-java-dsl only provides summary data in the console during test execution. To achieve professional-grade analysis, you can integrate external tools to handle two main requirements:

    1. Real-time metrics visualization: View live performance data as the test runs (similar to the JMeter GUI experience).
    2. Historic data storage: Persist test run data in a database to allow for long-term review and comparison between different test runs.

    Supported integration targets include InfluxDB, Graphite, Elasticsearch, Prometheus, and Datadog (see specific guides for setup instructions).

  2. Calculate maxThreads for rpsThreadGroup

    master

    It is highly recommended to always specify .maxThreads() to prevent the engine from spawning an uncontrolled number of threads, which can exhaust CPU and Memory.

    To calculate a safe value for maxThreads, use the formula:

    maxThreads = T * R

    Where:

    • T: The maximum RPS you want to achieve.
    • R: The maximum expected response time (or iteration time if using .counting(RpsThreadGroup.EventType.ITERATIONS)) in seconds.
  3. How to use Java lambdas for response processing

    master

    Java lambdas are generally more performant and easier to maintain (due to type safety and IDE autocompletion) than Groovy scripts. However, they are less portable and will not work out of the box with remote engines (like BlazeMeterEngine) or when saving a test plan as a JMX file for standalone JMeter execution.

    To maintain the benefits of Java code while ensuring portability, you should replace lambdas with public static classes that implement the appropriate script interface.

    // Instead of a lambda:
    jsr223PostProcessor(s -> {
      if ("429".equals(s.prev.getResponseCode())) {
        s.prev.setSuccessful(true);
      }
    })
    
    // Use a public static class:
    public static class StatusSuccessProcessor implements PostProcessorScript {
      @Override
      public void runScript(PostProcessorVars s) {
        if ("429".equals(s.prev.getResponseCode())) {
          s.prev.setSuccessful(true);
        }
      }
    }
    
    // And pass the class to the processor:
    jsr223PostProcessor(StatusSuccessProcessor.class)
  4. Access the current loop index in forLoopController

    master

    JMeter automatically generates a variable representing the current iteration index of the loop (starting at 0). You can use this variable in child elements (like URL paths or parameters) to vary requests based on the loop number.

    By default, if no name is specified for the controller, the variable name follows the pattern: __jm__for__idx.

  5. Understand the scoping and behavior of vars()

    master

    The behavior of vars() depends on where it is placed in your test plan hierarchy:

    1. As a direct child of testPlan: It uses JMeter's User Defined Variables (UDV). Variables declared here are available to all thread groups, but cross-variable references (e.g., var2=${var1}) are not supported.

    2. Anywhere else (e.g., inside a threadGroup): It uses a JSR223 sampler. This provides more flexible and intuitive behavior:

      • Scoped to the location: Defining a variable in one thread has no effect on other threads or thread groups.
      • Lazy evaluation: Values are set when they are actually needed, not just at the start of the test.
      • Cross-variable references: Supports resolving references (e.g., if var1=test and var2=${var1}, var2 will resolve to test).
  6. Use `holdsFor(Duration)` to avoid stopping on intermittent spikes

    master

    To prevent the test plan from stopping due to short-lived or intermittent metric fluctuations, use the holdsFor(Duration) method at the end of your condition. This ensures the test plan only stops if the condition remains true for the entire specified duration.

    // Only stop if the error rate is greater than 0 for a continuous 30-second period
    autoStop()
      .when(errors().total().greaterThan(0).holdsFor(Duration.ofSeconds(30)))
  7. Automatic file uploading in AzureEngine

    master

    The AzureEngine automatically handles the uploading of files used in the following methods:

    • csvDataSet(TestResource)
    • httpSampler with bodyFile or bodyFilePart methods.

    You do not need to manually upload these referenced files; they work out of the box.

    testPlan(
        threadGroup(100, Duration.ofMinutes(5)),
          csvDataSet(new TestResource("users.csv")),
          httpSampler(SAMPLE_LABEL, "https://myservice/users/${USER}")
        )
    ).runIn(new AzureEngine(System.getenv("AZURE_CREDS"))
        .testTimeout(Duration.ofMinutes(10)));
  8. Use `every(Duration)` to reset metric aggregations

    master

    By default, autoStop evaluates conditions for every sample. For certain aggregations like mean, perSecond, and percent, historical data can skew results (e.g., a sudden spike in latency might not move the overall average significantly).

    To prevent metrics from getting "stuck" due to historical values, use the every(Duration) method. This tells JMeter DSL to evaluate the condition and reset the aggregation only at the specified intervals.

    Example pattern: errors().perSecond().every(Duration.ofSeconds(5))

    // Example: Evaluate error rate per second, but reset the aggregation every 5 seconds
    autoStop()
      .when(errors().perSecond().every(Duration.ofSeconds(5)).greaterThan(0))
  9. Perform response correlation in JMeter Java DSL

    master

    Correlation is the process of extracting a value from a response (such as a generated ID, a session token, or a CSRF token) and using it in a subsequent request. In jmeter-java-dsl, this is achieved by attaching JMeter extractors to a request and then referencing the extracted value as a variable in later requests using the ${variableName} syntax.

    // Example pattern (conceptual):
    // 1. Extract value from request A
    requestA.extractJsonPath("$.token", "myToken");
    
    // 2. Use value in request B
    requestB.header("Authorization", "Bearer ${myToken}");
  10. Important limitations for HTTP connection settings

    master

    When configuring HTTP connections, be aware of the following constraints:

    1. JVM-wide scope: Both resetConnectionsBetweenIterations() and connectionTtl() apply at the JVM level due to JMeter limitations. This means they affect all requests in the test plan and any other tests running in the same JVM instance.
    2. HttpClientImpl.JAVA behavior: If you use clientImpl(HttpClientImpl.JAVA), the resetConnectionsBetweenIterations() and connectionTtl() settings will be ignored. Connection reuse will instead follow the default behavior of the underlying JVM implementation.
  11. When to use parallelController

    master

    While parallelController is powerful, consider these alternatives for specific use cases:

    • Downloading embedded resources: If you are trying to download resources (like images or CSS) from an HTML response, use the downloadEmbeddedResources() method on an httpSampler instead.
    • Independent test parts: If you need different parts of your test plan to run in parallel, it is generally better to use separate threadGroup definitions rather than a parallelController.
  12. Access the whileController iteration index

    master

    When using a whileController, you can track the current iteration number using a JMeter variable. If you provide a name to the whileController, the variable is named __jm__<loopName>__idx. The index starts at 0.

    Example: If the controller is named items, the variable is __jm__items__idx.

    // Using the auto-generated index variable to limit iterations
    whileController("items", "${__groovy(vars.getObject('__jm__items__idx') < 4)}",
        httpSampler("http://my.service/items")
          .post("{\"name\": \"My Item\"}", Type.APPLICATION_JSON)
    )