HTTP Benchmark Details

Overview

Run the following command to create the Java version of the example benchmark program:

  • jbang tulip-cli@wfouche init Java

This command creates various files and directories:

├── benchmark_config.json
├── io
│   └── tulip
│       ├── App.java
│       └── HttpUser.java
├── run_bench.cmd
└── run_bench.sh

Source Code

App.java

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS io.github.wfouche.tulip:tulip-runtime:2.1.5
//DEPS org.springframework.boot:spring-boot-starter-web:3.4.2
//DEPS org.slf4j:slf4j-api:2.0.16
//DEPS ch.qos.logback:logback-core:1.5.16
//DEPS ch.qos.logback:logback-classic:1.5.16
//SOURCES HttpUser.java
//JAVA 21

package io.tulip;

import io.github.wfouche.tulip.api.TulipApi;

public class App {
   public static void main(String[] args) {
      TulipApi.runTulip("benchmark_config.json");
   }
}

Configuration Data

{
    "actions": {
        "description": "Spring RestClient Benchmark [Java]",
        "output_filename": "benchmark_output.json",
        "report_filename": "benchmark_report.html",
        "user_class": "io.tulip.HttpUser",
        "user_params": {
            "protocol": "http",
            "host": "jsonplaceholder.typicode.com",
            "connectTimeoutMillis": 500,
            "readTimeoutMillis": 2000,
            "debug": true
        },
        "user_actions": {
            "1": "GET:posts",
            "2": "GET:comments",
            "3": "GET:todos"
        }
    },
    "workflows": {
        "api-user": {
            "-": {
                "1": 0.40,
                "3": 0.60
            },
            "1": {
                "2": 1.0
            },
            "2": {
                "-": 1.0
            },
            "3": {
                "-": 1.0
            }
        }
    },
    "benchmarks": {
        "onStart": {
            "save_stats": false,
            "scenario_actions": [ {"id": 0} ]
        },
         "REST1": {
            "enabled": true,
            "aps_rate": 10.0,
            "scenario_actions": [
                {
                    "id": 1
                }
            ],
            "time": {
                "pre_warmup_duration": 30,
                "warmup_duration": 10,
                "benchmark_duration": 30,
                "benchmark_iterations": 3
            }
        },
        "REST2": {
            "enabled": true,
            "aps_rate": 10.0,
            "scenario_actions": [
                {
                    "id": 1, "weight": 10
                },
                {
                    "id": 2, "weight": 40
                },
                {
                    "id": 3, "weight": 50
                }
            ],
            "time": {
                "pre_warmup_duration": 30,
                "warmup_duration": 10,
                "benchmark_duration": 30,
                "benchmark_iterations": 3
            }
        },
        "REST3": {
            "enabled": true,
            "aps_rate": 10.0,
            "scenario_workflow": "api-user",
            "time": {
                "pre_warmup_duration": 30,
                "warmup_duration": 10,
                "benchmark_duration": 30,
                "benchmark_iterations": 3
            }
        },
        "REST3.max": {
            "enabled": true,
            "aps_rate": 0.0,
            "scenario_workflow": "api-user",
            "time": {
                "pre_warmup_duration": 30,
                "warmup_duration": 10,
                "benchmark_duration": 30,
                "benchmark_iterations": 3
            }
        },
        "onStop": {
            "save_stats": false,
            "scenario_actions": [ {"id": 100} ]
        }
    },
    "contexts": {
        "Context-1": {
            "enabled": true,
            "num_users": 128,
            "num_threads": 8
        }
    }
}

HttpUser.java

package io.tulip;

import io.github.wfouche.tulip.api.*;
import java.util.concurrent.ThreadLocalRandom;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpUser extends TulipUser {

    public HttpUser(int userId, int threadId) {
        super(userId, threadId);
    }

    public boolean onStart() {
        // Initialize the shared RestClient object only once
        if (getUserId() == 0) {
            logger.info("Java");
            logger.info("Initializing static data");
            var connectTimeout = Integer.valueOf(
              getUserParamValue("connectTimeoutMillis"));
            var readTimeout = Integer.valueOf(
              getUserParamValue("readTimeoutMillis"));
            var factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(connectTimeout);
            factory.setReadTimeout(readTimeout);
            var url = getUserParamValue("protocol")
                + "://" + getUserParamValue("host");
            restClient = RestClient.builder()
                .requestFactory(factory)
                .baseUrl(url)
                .build();
            debug = Boolean.valueOf(getUserParamValue("debug"));
            logger.info("debug = " + debug);
            if (debug) {
                logger.info(url);
            }
        }
        return true;
    }

    // Action 1: GET /posts/{id}
    public boolean action1() {
        int id = ThreadLocalRandom.current().nextInt(100)+1;
        return serviceCall("/posts/{id}", id);
    }

    // Action 2: GET /comments/{id}
    public boolean action2() {
        int id = ThreadLocalRandom.current().nextInt(500)+1;
        return serviceCall("/comments/{id}", id);
    }

    // Action 3: GET /todos/{id}
    public boolean action3() {
        int id = ThreadLocalRandom.current().nextInt(200)+1;
        return serviceCall("/todos/{id}", id);
    }

    public boolean onStop() {
        return true;
    }

    private boolean serviceCall(String uri, int id) {
        boolean rc;
        try {
            String rsp = restClient.get()
                .uri(uri, id)
                .retrieve()
                .body(String.class);
            rc = (rsp != null && rsp.length() > 2);
        } catch (RestClientException e) {
            rc = false;
        }
        return rc;
    }

    // RestClient object
    private static RestClient restClient;

    // Debug flag
    private static boolean debug = false;

    // Logger
    private static final Logger logger = LoggerFactory.getLogger(HttpUser.class);

}

Benchmark Scripts

run_bench.sh
#!/bin/bash
rm -f benchmark_report.html
export JBANG_JAVA_OPTIONS="-server -Xms2g -Xmx2g -XX:+UseZGC -XX:+ZGenerational"
jbang run io/tulip/App.java    (1)
echo ""
#w3m -dump -cols 205 benchmark_report.html
lynx -dump -width 205 benchmark_report.html
#jbang run asciidoc@wfouche benchmark_config.adoc
#jbang export fatjar io/tulip/App.java
1 Command that starts the benchmark
run_bench.cmd
if exist benchmark_report.html del benchmark_report.html
set JBANG_JAVA_OPTIONS=-server -Xms2g -Xmx2g -XX:+UseZGC -XX:+ZGenerational
call jbang run io\tulip\App.java    (1)
@echo off
echo.
REM w3m.exe -dump -cols 205 benchmark_report.html
REM lynx.exe -dump -width 205 benchmark_report.html
start benchmark_report.html
REM jbang run asciidoc@wfouche benchmark_config.adoc
REM start benchmark_config.html
REM jbang export fatjar io\tulip\App.java
1 Command that starts the benchmark

Configuration Report

Description

Spring RestClient Benchmark [Java]

Filename

benchmark_config.json

Actions

id value

description

Spring RestClient Benchmark [Java]

output_filename

benchmark_output.json

report_filename

benchmark_report.html

user_class

io.tulip.HttpUser

user_params

id value

protocol

http

host

jsonplaceholder.typicode.com

connectTimeoutMillis

500

readTimeoutMillis

2000

debug

True

user_actions

id value

1

GET:posts

2

GET:comments

3

GET:todos

Workflows

api-user

Workflow Diagram Specification
wfd0
{
    "-": {
        "1": 0.4,
        "3": 0.6
    },
    "1": {
        "2": 1.0
    },
    "2": {
        "-": 1.0
    },
    "3": {
        "-": 1.0
    }
}

Benchmarks

REST1

id value

enabled

True

aps_rate

10.0

worker_thread_queue_size

0

scenario_actions

id weight

1

-

time

pre_warmup_duration

30 seconds

warmup_duration

10 seconds

benchmark_duration

30 seconds

benchmark_iterations

3 seconds

REST2

id value

enabled

True

aps_rate

10.0

worker_thread_queue_size

0

scenario_actions

id weight

1

10

2

40

3

50

time

pre_warmup_duration

30 seconds

warmup_duration

10 seconds

benchmark_duration

30 seconds

benchmark_iterations

3 seconds

REST3

id value

enabled

True

aps_rate

10.0

worker_thread_queue_size

0

scenario_workflow

time

pre_warmup_duration

30 seconds

warmup_duration

10 seconds

benchmark_duration

30 seconds

benchmark_iterations

3 seconds

REST3.max

id value

enabled

True

aps_rate

0.0

worker_thread_queue_size

0

scenario_workflow

time

pre_warmup_duration

30 seconds

warmup_duration

10 seconds

benchmark_duration

30 seconds

benchmark_iterations

3 seconds

Contexts

Context-1

id value

num_users

128

num_threads

8

enabled

True