{"id":19108188,"url":"https://github.com/perfana/afterburner","last_synced_at":"2025-04-30T19:35:38.849Z","repository":{"id":33374821,"uuid":"126983828","full_name":"perfana/afterburner","owner":"perfana","description":"Service with performance issue simulations, packaged as a simple runnable Java jar file. ","archived":false,"fork":false,"pushed_at":"2025-04-27T10:47:55.000Z","size":7080,"stargazers_count":9,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-27T11:37:25.505Z","etag":null,"topics":["gatling","jmeter","performance-testing","spring-boot","wiremock"],"latest_commit_sha":null,"homepage":"https://www.perfana.io","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/perfana.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-03-27T12:36:21.000Z","updated_at":"2025-04-23T18:14:00.000Z","dependencies_parsed_at":"2023-09-22T05:28:45.340Z","dependency_job_id":"3313cc90-6767-469a-83a2-0a6079065707","html_url":"https://github.com/perfana/afterburner","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfana%2Fafterburner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfana%2Fafterburner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfana%2Fafterburner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfana%2Fafterburner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/perfana","download_url":"https://codeload.github.com/perfana/afterburner/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251770004,"owners_count":21641013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gatling","jmeter","performance-testing","spring-boot","wiremock"],"created_at":"2024-11-09T04:15:23.969Z","updated_at":"2025-04-30T19:35:38.806Z","avatar_url":"https://github.com/perfana.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# afterburner\n\nSimple self-running test jar to use in load test environments to tune and explore monitor and analysis tools.\n\n# afterburner-java\n\n# start\nStart in the afterburner-java directory:\n    \n    ../mvnw spring-boot:run\n\nTo run with profile:\n\n    ../mvnw spring-boot:run -Dspring-boot.run.profiles=employee-db\n    \nOr build or download an executable jar and run with java, so you can also change jvm settings such as the garbage collector:\n\n    java -jar target/afterburner-java-1.1-SNAPSHOT-exec.jar\n    \nWith gc log enabled:\n    \n    java -jar -Xlog:gc:file=gc.log:time,uptime target/afterburner-java-1.1-SNAPSHOT-exec.jar \n    \nDownload a ready build afterburner here: https://github.com/perfana/afterburner/releases \n\n# test\nDefault port number is 8080, so test if it is working using curl:\n    \n    curl http://localhost:8080/delay\n    \nMost examples below can be run with curl. Or check out swagger for the calls with parameters.\n    \n# swagger\nThe swagger-ui is here after starting afterburner:\n\n    http://localhost:8080/swagger-ui.html\n\n# profiles\n\nTwo profiles can be set via `spring.profiles.active`:\n* `employee-db` activates the employee database controller (only do so when the mariadb employee database is running)\n* `logstash` activates logstash in logback on `logstash:4560` (only activate when logstash is available) \n\nFor logstash the following properties (or env vars) can be set:\n* `afterburner.logback.logstash.remotehost` \n* `afterburner.logback.logstash.port`\n\n# functions\n\n## delay\nCall an endpoint that will delay a call for the given duration. Default duration is 100 milliseconds.\nThe duration can either be given in milliseconds or in [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) by starting with P.\n\nIf you increase the response time high enough, you are likely to see timeout issues.\n\nExamples:\n* `/delay?duration=PT1.5s` delay for 1500 millis\n* `/delay?duration=150` delay for 150 millis\n\n## memory\n\nCall this endpoint to increase memory usage to simulate a memory leak or to generate lots of young objects for each request (churn). \n\nIf you increase the memory usage continuously or with a high number of objects you will likely see a OutOfMemory related issues.\n\nIncreasing the number of short lived objects (objects that are alive only during a request) you can simulate a high\nmemory churn rate and see how the garbage collector behaves.\n\nExamples for memory leak:\n* `/memory/grow` grows an in memory structure with default values\n* `/memory/grow?objects=5\u0026items=5` grows structure with the given values (5 objects with 5 items each)\n* `/memory/clear` clears the memory structure and memory will be freed\n\nExamples for memory churn:\n* `/memory/churn` creates lots of objects and sleeps for a while\n* `/memory/churn?objects=1818\u0026duration=200` creates 1818 objects and sleeps for 200 milliseconds\n\n## cpu\n\nCall this endpoint to burn some cpu cycles using matrix calculations.\nYou can perform an identity check on a simple magic square. \n\nBe careful with sizes above 500, \nthe response times and cpu effort will increase significantly above these sizes.\n\nNote that the current implementation is doing the calculation within one thread (core).\nYou can exercise multiple cores by calling multiple concurrent requests.\n\nAn experimental feature is present to short-cut the matric calculation when one \nif the given matrices is an identity matrix. It will just return the other matrix as-is.\nThis also avoids the need to create a new result matrix.\nActivate by supplying an _environment_ property: `featureToggleIdentityMatrix=true`\n\nExamples:\n* `/cpu/magic-identity-check` checks identity and simple magic square multiplication with default size 10\n* `/cpu/magic-identity-check?matrixSize=100` check for size 100, currently takes around 100 milli sec on a simple laptop\n* `/cpu/magic-identity-check?matrixSize=600` check for size 600, currently takes 2.5 sec on a simple laptop\n* `/cpu/magic-identity-check?matrixSize=0` get a 500 error (might also be handy for testing)\n\n## remote call\n\nCall another remote http(s) endpoint. Use `path` to specify the path to be called on the remote\ninstance. \n\nThe base url is specified via the `afterburner.remote.call.base_url` property. The default value is\n`http://localhost:8080` so it will call itself.\n\nThe default remote client is Apache HttpClient. With the `type` request parameter you can switch to OkHttp.\n\nExamples:\n* `/remote/call?path=delay` call delay on remote afterburner via HttpClient\n* `/remote/call?path=delay\u0026type=okhttp` call delay on remote afterburner via OkHttp\n* `/remote/call?path=remote/call?path=delay` call remote afterburner to call remote afterburner to call delay\n* `/remote/call?type=okhttp\u0026path=remote/call?path=delay` same, but first call via OkHttp and second via HttpClient\n\nnote: do not forget to escape \u0026 with a backslash using bash and curl\n\nA custom metric is available, counting the total number of remote calls:\n\n* http://localhost:8080/actuator/metrics/afterburner.remote.calls\n\n## remote call async\n\nCall many remote http(s) endpoint in parallel using `@Async` method.\n\nWorks the same as the remote call above with the addition of how many times\nthe remote call should be made. All calls are `@Async` calls, using `CompletableFuture`.\n\nExample:\n* `/remote/call-many?count=10` call delay on remote afterburner via HttpClient.\n* `/remote/call-many?count=200\u0026path=/delay?duration=PT2S` call delay of 2 seconds on remote afterburner via HttpClient.\n* `/remote/call-many?count=4\u0026path=/cpu/magic-identity-check` call 4 times the magic-identity-check in parallel on remote afterburner\n\nA custom thread executor pool is created that also registers itself with micrometer.\nThis way the executor can be monitored on tasks in the queue and number of threads in use.\n\n## upload file\n\nUpload a file to the java tmp directory (be careful not to fill that directory :-).\n\nExample:\n* `curl --trace-ascii - -F 'upload=@pom.xml' http://localhost:8080/files/upload` upload the pom.xml file\n* `/files/download/pom.xml` download the pom.xml file after a succeeded upload\n\n## database connect\n\nShow latency of connecting to a database using a simple query on a Springboot template.\n\nThe default query is 'SELECT 1' and can be changed via the `afterburner.database.connect.query` property.\n\nAfterburner connects to the mysql employees test database on default port 3306 on localhost.\n\nExample:\n* `/db/connect` perform simple SELECT 1 to measure base performance to database\n\nExample output:\n   \n    {\"message\":\"{ 'db-call':'success','query-duration-nanos':447302064 }\",\"name\":\"Afterburner-One\",\"durationInMillis\":447}\n\n## remote database\n\nCall a remote MariaDB database with the MySql employee test database loaded.\nNote: only works with Spring profile `employee-db` active.\n\n* `/db/employee/find-by-name?firstName=Anneke` find employees by first name\n* `/db/employee/find-by-name?lastName=Preusig` find employees by last name\n* `/db/employee/find-by-name?firstName=Anneke\u0026lastName=Preusig` find by first and last name\n\nOther first names to try: Steen, Aamer, Guoxiang (via `SELECT DISTINCT e.first_name FROM employees.employees e`)\n\nExample output:\n\n    [{\"empNo\":10006,\"birthDate\":\"1953-04-20\",\"firstName\":\"Anneke\",\"lastName\":\"Preusig\",\"gender\":\"F\",\"hireDate\":\"1989-06-02\"},{\"empNo\":10640,\"birthDate\":\"1958-11-09\",\"firstName\":\"Anneke\",\"lastName\":\"Meszaros\" ... \n\nSee dependencies section how to set up the database component.\n\nThere is also a query with configurable long delay. Example call:\n\n* `time curl localhost:8080/db/employee/select-long-time\\?durationInSec=10`\n\nUse this end-point for instance to test overloaded connection pools and timeout behaviour.\n    \n## tcp connect\n\nShow latency to remote TCP port using a Java TCP Socket creation.\nThis closely matches the network latency to the remote site, but includes \nsome Java related overhead, such as possible gc times and Java security manager\nchecks in the Socket code.\n\nExample: \n* `/tcp/connect?host=www.google.com\\\u0026port=80\\\u0026timeout-ms=1000`\n\nExample output:\n\n    {\"message\":\"{ 'tcp-connect':'success', 'connect-duration-nanos':5281882, 'close-duration-nanos':39150, 'host':'www.google.com', 'port':80 }\",\"name\":\"Afterburner-One\",\"durationInMillis\":6}\n\n## parallel\n\nThe parallel call will determine prime numbers via a parallel stream, which uses the implicit ForkJoinPool.\n\nUse this to test behaviour of a busy FJP. \n\nExample:\n* `/parallel` runs prime number check in parallel using the common join fork pool\n* `/parallel-info` print metrics of the common join fork pool\n\n## basket validation\n\nUse this basket validation for concurrency issues with shared data in multiple threads.\n \n * `/basket/purchase` - post a basket purchase, the request will be validated on total price and products/prices\n \nGood request (10 + 20 + 30 = 60):\n\n    curl -H \"Content-Type: application/json\" -d '{ \"customer\": \"Johnny\", \"prices\": [10, 20, 30], \"products\": [\"apple\", \"banana\",\"oranges\"], \"totalPrice\": 60 }' localhost:8080/basket/purchase\n\nBad request with validation errors (20 + 30 != 40):\n\n    curl -H \"Content-Type: application/json\" -d '{ \"customer\": \"BadGuy\", \"prices\": [20, 30], \"products\": [\"sushi\", \"icescream\"], \"totalPrice\": 40 }' localhost:8080/basket/purchase\n\nPut under load and check if all validates as expected.\n\n## basket database store\n\nYou can store baskets in a database by calling:\n\n    curl -H \"Content-Type: application/json\" -d '{ \"customer\": \"Johnny\", \"prices\": [10, 20, 30], \"products\": [\"apple\", \"banana\",\"oranges\"], \"totalPrice\": 60 }' localhost:8080/basket/store\n    \nAnd retrieve all baskets via:\n\n    curl localhost:8080/basket/all\n\n## autonomous worker\n\nUse the autonomous worker to investigate the behaviour of `@Schedule`.\n\nTwo methods are in place, one with a fixedRate schedule and one with a fixedDelay schedule.\n\nSee what happens when a thread freezes or fails by setting `afterburner.autonomous.worker.stability` to false.\n\nActivate the logging of these workers with property: `logging.level.service.io.perfana.afterburner.AutonomousWorker=debug`\n\n## flaky calls and retries\n\nTo cover-up downstream flaky services you can make use of retries. For instance by using the resilience4j library.\n\nA flaky service is available here:\n\n* `flaky\\?maxRandomDelay=2000\\\u0026flakiness=25` will pick a random delay from 1 to 2000 milliseconds, and will respond with a 500 error status 25% of the time (default is 50% flakiness).\n\nIf you set the socket or read timeout of the incoming call to 1000 milliseconds, you will also get timeouts 50% of the calls.\n\nTo wrap a flaky call like this with retries, use the following call:\n\n* `localhost:8080/remote/call-retry\\?path=flaky\\?maxRandomDelay=2000` do a remote call to the baseUrl with the given path and also retry 10 times \nwhen failures like timeouts and 500's occur\n\nIt will take longer, but it will succeed more often.\n\nThe retry metrics are available via actuator:\n\n* http://localhost:8080/actuator/retries\n* http://localhost:8080/actuator/retryevents/afterburner-retry\n\n## connection timeouts and retries\n\nTo test connections timeouts when a service is unavailable (e.g. network hickup or remote restart), use this \n\"traffic-light\" endpoint:\n\n* `curl -v localhost:5599`\n\nIt return a green light during 5 seconds and then goes offline for 5 seconds.\n\nTo test this with retries:\n\n* `curl localhost:8080/remote/call-traffic-light`\n\nCheck the retry metrics via:\n\n* http://localhost:8080/actuator/retryevents/traffic-light-retry\n\nChange the traffic light port with property: `afterburner.trafficlight.port`\n\n## circuit breaker\n\nTo test resiliencej4 circuit breaker, call the following url *many* times in fast succession\n(here making use of [wrk2](https://github.com/giltene/wrk2): \n\n* wrk -t10 -d120s -c10 -R20 http://localhost:8080/remote/call-circuit-breaker\\?path=delay\\?duration=7000\n\nThe log will show:\n\n    13-04-2021 16:25:51.124+0200 ERROR [ttp-nio-8080-exec-24] ---   n.s.a.e.RestExceptionHandler::ndleTimeoutException - TimeoutException for uri=/remote/call-circuit-breaker;client=0:0:0:0:0:0:0:1 with message For [delay?duration=7000]: Read timed out\n\nand some time later:\n\n    13-04-2021 16:26:15.237+0200 ERROR [http-nio-8080-exec-4] ---   n.s.a.e.RestExceptionHandler::cuitBreakerException - CircuitBreakerException for uri=/remote/call-circuit-breaker;client=0:0:0:0:0:0:0:1 with message For [delay?duration=7000]: CircuitBreaker 'afterburner-circuit-breaker' is OPEN and does not permit further calls\n\n## security filter\n\nThe `secured-delay` has a BasicAuthenticationFilter enabled with BCrypt check.\n\nCheck what impact this has on latency and cpu usage.\n\nExample call:\n\n* `time curl -u pipo:test123 localhost:8080/secured-delay`\n\n# load test\nTo run a gatling load test, go to the `afterburner-loadtest-gatling` directory and run:\n\n    mvn events-gatling:test\n    \nTo run a jmeter load test, go to the `afterburner-loadtest-jmeter` directory and run:\n\n    mvn clean verify\n     \n# properties\n* `-Dspring.application.name=Angry-Afterburner` provide a name for the instance\n* `-Dafterburner.remote.call.base_url=https://my.remote.site:1234` connect remote calls to this base url\n* `-Dafterburner.autonomous.worker.stability=false` default the autonomous worker is stable, make it unstable by setting this to false\n* `export AFTERBURNER_REMOTE_CALL_BASE_URL=https://my.remote.site:1234` provide base url via environment variable\n* `--server.port=8090` use different port (default 8080)\n* `export SERVER_PORT=8090` use different port via env variable\n\n## dependencies\n\n# tracing\n\nRun a Jaeger instance to see the tracing.\n\nFor example via docker:\n\n    docker run -d --name jaeger \\\n      -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \\\n      -p 5775:5775/udp \\\n      -p 6831:6831/udp \\\n      -p 6832:6832/udp \\\n      -p 5778:5778 \\\n      -p 16686:16686 \\\n      -p 14268:14268 \\\n      -p 14250:14250 \\\n      -p 9411:9411 \\\n      jaegertracing/all-in-one:1.29\n\nUse `spring.zipkin.enabled=true` to enable sending data to Jaeger.\nAlso set property `spring.sleuth.sampler.probability` higher than 0, \nset to 1 to capture all traces, or 0.1 for 10% of the traces.\n      \nThen see traces here: http://localhost:16686/ \n\n# prometheus\n\nThe prometheus endpoint: http://localhost:8080/actuator/prometheus\n\n# database\n\nClone https://github.com/datacharmer/test_db into /path/to/git/\n\n    git clone https://github.com/datacharmer/test_db.git\n\nRun MariaDB with the MySql employees database:\n\n    docker run -d --name mariadbtest \\\n      -e MYSQL_ROOT_PASSWORD=nabrander123 \\\n      -v /path/to/git/test_db:/db \\\n      -p 3306:3306 \\\n      mariadb:10.6.5\n\nThen ssh into this docker:\n    \n    docker exec -it mariadbtest /bin/bash\n\nAnd load the test database:\n    \n    cd /db\n    mysql -t -p  \u003c employees.sql\n\nAnd provide password: `nabrander123`\n\n# docker\n\nTo create a docker image, from the `afterburner-java` directory locally (choose `amd64` or `arm64`):\n\n    ../mvnw -Plocal-docker -Ddocker-arch=amd64 clean package jib:dockerBuild \n\nor to push docker remotely (we use the multi-arch build for both `amd64` and `arm64`):\n\n     ../mvnw -Pmulti-arch-docker,pyroscope clean package jib:build\n\n(add `-DskipTests` to speed it up, if you know what you are doing 😉)\n\nNote: if you jib multiple times with same (SNAPSHOT) label, use `docker pull \u003cimage-path\u003e` to get most recent version\non a remote location.\n\nTo start the docker:\n\n    docker run --rm -d -p 8080:8080 --name afterburner perfana/afterburner-jdk:2.2.0-jdk11\n\nTo start with the pyroscope agent (make sure to build with profile `pyroscope`: `-Ppyroscope`):\n\n    docker run -rm -d -p 8080:8080 \\\n      -e JAVA_TOOL_OPTIONS=\"-javaagent:/pyroscope.jar\" \\\n      --name afterburner perfana/afterburner-jdk:2.2.0-jdk11\n\n##### credits\n* fire favicon from [freefavicon](http://www.freefavicon.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfana%2Fafterburner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fperfana%2Fafterburner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfana%2Fafterburner/lists"}