{"id":15044107,"url":"https://github.com/jetty-project/jetty-load-generator","last_synced_at":"2025-04-05T16:10:29.111Z","repository":{"id":37958213,"uuid":"80563893","full_name":"jetty-project/jetty-load-generator","owner":"jetty-project","description":null,"archived":false,"fork":false,"pushed_at":"2025-03-21T03:03:18.000Z","size":1602,"stargazers_count":78,"open_issues_count":9,"forks_count":8,"subscribers_count":9,"default_branch":"4.0.x","last_synced_at":"2025-03-29T15:11:20.375Z","etag":null,"topics":["collector","http","http-client","http2","java","java-8","java-client","java-library","listener","load-testing","measure","responsetime","statistics"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jetty-project.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2017-01-31T21:25:09.000Z","updated_at":"2025-03-18T18:16:36.000Z","dependencies_parsed_at":"2023-12-19T15:08:57.069Z","dependency_job_id":"36aff518-6e82-415e-9d85-d180dac79d0f","html_url":"https://github.com/jetty-project/jetty-load-generator","commit_stats":{"total_commits":908,"total_committers":8,"mean_commits":113.5,"dds":0.7125550660792952,"last_synced_commit":"6830699d9568b0b73fd0dc4a7855c4605c432aff"},"previous_names":[],"tags_count":70,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetty-project%2Fjetty-load-generator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetty-project%2Fjetty-load-generator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetty-project%2Fjetty-load-generator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetty-project%2Fjetty-load-generator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jetty-project","download_url":"https://codeload.github.com/jetty-project/jetty-load-generator/tar.gz/refs/heads/4.0.x","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361695,"owners_count":20926643,"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":["collector","http","http-client","http2","java","java-8","java-client","java-library","listener","load-testing","measure","responsetime","statistics"],"created_at":"2024-09-24T20:50:05.180Z","updated_at":"2025-04-05T16:10:29.089Z","avatar_url":"https://github.com/jetty-project.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![GitHub CI](https://github.com/jetty-project/jetty-load-generator/workflows/GitHub%20CI/badge.svg)\n\n# Jetty Load Generator Project\n\nJetty's `LoadGenerator` is an API to load-test HTTP servers, based on Jetty's `HttpClient`.\n\n| Jetty Load Generator Version | Jetty Version | Java Version |    Status    |\n|:----------------------------:|:-------------:|:------------:|:------------:|\n|            4.0.x             |    12.0.x     |   Java 17+   |    Stable    |\n|            3.1.x             |    11.0.x     |   Java 11+   |    Stable    |\n|            2.1.x             |    10.0.x     |   Java 11+   |    Stable    |\n|            1.1.x             |     9.4.x     |   Java 8+    | End-Of-Life  |\n|            2.0.x             |    11.0.x     |   Java 11+   | End-Of-Life  |\n|            1.0.7             |     9.4.x     |   Java 8+    | End-Of-Life  |\n|         1.0.0-1.0.6          |     9.4.x     |   Java 11+   | End-Of-Life  |\n\nThe design of the `LoadGenerator` is based around these concepts:\n\n* Generate requests asynchronously at a constant rate, independently of responses.\n* Model [requests as web pages](#resource-apis), simulating what browsers do to download a web page.\n* Support both HTTP/1.1 and HTTP/2 (and future versions of the HTTP protocol).\n* Emit response events asynchronously, so they can be recorded, for example, in a response time histogram.\n\nYou can embed Jetty's `LoadGenerator` in Java applications -- this will give you full flexibility, or you can use it as a command-line tool -- and therefore use it in scripts.\n\nThe project artifacts are:\n\n* `jetty-load-generator-client` -- Java APIs, see [this section](#load-generator-apis)\n* `jetty-load-generator-listeners` -- useful listeners for events emitted during load-test\n* `jetty-load-generator-starter` -- command-line load test uber-jar, see [this section](#command-line-load-generation)\n\n## Recommended Load Generation Setup\n\n1. Assume that the load generator is the bottleneck. You may need several load generators on different machines to make the server even break a sweat.\n1. Establish a baseline to verify that all the parties involved in the load runs behave correctly, including network, load generators, server(s), load balancer(s), etc.\n1. Use one or more _loaders_ to generate load on the server. A loader is a load generator that imposes a load on the server, but does not record response times.\n1. Use a _probe_ to record response times. A probe is a load generator that imposes a light load on the server and records response times.\n1. Use Brendan Gregg's [USE method](http://www.brendangregg.com/usemethod.html) to analyze the results.\n\nFor example, let's say you want to plot how the server responds to increasing load.  \nYou setup, say, 4 _loaders_ and one _probe_.  \nConfigure each loader with, say, `threads=2`, `usersPerThread=50` and `requestRate=20`.  \nConfigure the probe with, say, `threads=1`, `usersPerThread=1` and `requestRate=1`.  \nThe total load on the server is therefore 81 requests/s from 401 users, from all the loaders and the probe.\nPerform a run with this configuration and record the results from the probe.  \nThen change the configuration of the loaders to increase the load (but don't change the probe configuration), say to `usersPerThread=75` and `requestRate=30`, so now the total load on the server is 121 requests/s from 601 users.  \nPerform another runs and record the results from the probe.  \nIncrement again to `usersPerThread=100` and `requestRate=40`, that is 161 requests/s from 801 users.  \n\nLoaders should not affect each other, so ideally each loader should be on a separate machine with a separate network link to the server. \nFor non-critical loads, loaders may share the same machine/link, but they will obviously steal CPU and bandwidth from each other.\nLoaders should not affect the probe, so ideally the probe should run on a separate machine with a separate network link to the server, to avoid that loaders steal CPUs and bandwidth from the probe that will therefore record bogus results.\n\nMonitor continuously each loader request rate and compare it with its response rate.  \nThe effective request rate should be close to the nominal request rate you want to impose.  \nThe response rate should be as close as possible to the request rate.  \nIf these conditions are not met, it means that the loader is over capacity, and you must reduce the load and possibly spawn a new loader.\n\n## Load Generator APIs\n\n### `Resource` APIs\n\nYou can use the `Resource` APIs to define resources that `LoadGenerator` requests to the server.\n\nA simple resource:\n\n```java\nResource resource = new Resource(\"/index.html\");\n```\n\nA web-page like `Resource` tree:\n\n```java\nResource resource = new Resource(\"/index.html\",\n        new Resource(\"/styles.css\"),\n        new Resource(\"/application.js\")\n);\n```\n\n`Resource` trees are requested to the server similarly to how a browser would do.\nIn the example above, `/index.html` will be requested and awaited; when its response arrives, `LoadGenerator` will send its children (in parallel if possible): `/styles.css` and `/application.js`.  \n\nResources can be defined in Java, Groovy files, Jetty XML files, or JSON files.\n\n### `LoadGenerator` APIs\n\n`LoadGenerator` offers a builder-style API:\n\n```java\nLoadGenerator generator = LoadGenerator.builder()\n        .scheme(scheme)\n        .host(serverHost)\n        .port(serverPort)\n        .resource(resource)\n        .httpClientTransportBuilder(transportBuilder)\n        .threads(1)\n        .usersPerThread(10)\n        .channelsPerUser(6)\n        .warmupIterationsPerThread(10)\n        .iterationsPerThread(100)\n        .runFor(2, TimeUnit.MINUTES) // Overrides iterationsPerThread()\n        .resourceListener(resourceListener)\n        .build();                \n\n// Start the load generation.\nCompletableFuture\u003cVoid\u003e complete = generator.begin();\n        \n// Now the load generator is running.\n        \n// You can wait for the CompletableFuture to complete.\n// Or you can interrupt the load generation:\ngenerator.interrupt();\n```\n\n`LoadGenerator` uses _sender_ threads to request resources to the server.\n\nEach sender thread can be configured with a number of _users_; each user is a separate `HttpClient` instance that simulates a browser, and has its own connection pool.\nEach user opens at least one TCP connection to the server -- the exact number of connections opened depends on the protocol used (HTTP/1.1 vs HTTP/2), the user channels (see below), and the resource rate.\n\nEach user may send requests in parallel through _channels_.\nA channel is either a new connection in HTTP/1.1, or a new HTTP/2 stream.\n\nEach sender thread runs an optional number of _warmup_ iterations, that are not recorded -- no events will be emitted for these warmup requests.\n\nAfter the warmup iterations, each sender thread runs the configured number of _iterations_ or, alternatively, runs for the configured time.\nThese requests will emit events that may be recorded by listeners, see below.\n\n\n### Listener APIs\n\n`LoadGenerator` emits a variety of events that you can listen to.\n\n`LoadGenerator` emits events at:\n* load generation begin, emitted when the load generation begins, to `LoadGenerator.BeginListener`\n* load generation ready, emitted when the load generation has finished the warmup, to `LoadGenerator.ReadyListener`\n* load generation end, emitted when the load generation ends (that is, the last request has been sent), to `LoadGenerator.EndListener`\n* load generation complete, emitted when the load generation completes (that is, the last response has been received), to `LoadGenerator.CompleteListener`\n\nMost interesting are events related to resources.\n\n`Resource.NodeListener` is notified every time a resource is received by `LoadGenerator`.\n`Resource.TreeListener` is notified every time a whole resource tree is received by `LoadGenerator` -- this is useful to gather \"page load\" times.\n\nFor both resource listeners, the information is carried by `Resource.Info`, that provides the timestamps (in nanoseconds) for resource send, resource received, resource content bytes, HTTP status, etc.\n\nYou can use histograms to record the response times:\n\n```java\nclass ResponseTimeListener implements Resource.NodeListener, LoadGenerator.CompleteListener {\n    private final org.HdrHistogram.Recorder recorder;\n    private org.HdrHistogram.Histogram histogram;\n    \n    // Invoked every time a resource is received.\n    @Override\n    public void onResourceNode(Resource.Info info) {\n        long responseTime = info.getResponseTime() - info.getRequestTime();\n        recorder.recordValue(responseTime);\n    }\n    \n    // Invoked at the end of the load generation.\n    @Override\n    public void onEnd(LoadGenerator generator) {\n        // Retrieve the histogram, resetting the recorder.\n        this.histogram = recorder.getIntervalHistogram();\n    }\n}\n```\n\nThe `Histogram` APIs provides count, percentiles, average, minimum and maximum values.\n\n## Command-Line Load Generation\n\nArtifact `jetty-load-generator-starter-\u003cversion\u003e-uber.jar` allows you to generate load using the command-line.\nThe uber-jar already contains all the required dependencies.\n\nTo display usage:\n\n```\njava -jar jetty-load-generator-starter-\u003cversion\u003e-uber.jar --help\n```\n\nExample:\n\n```shell\njava -jar jetty-load-generator-starter-\u003cversion\u003e-uber.jar \n        --scheme https \n        --host serverHost \n        --port serverPort\n        --resource-json-path /tmp/resource.json\n        --transport h2 # secure HTTP/2\n        --threads 1\n        --users-per-thread 10\n        --channels-per-user 6\n        --warmup-iterations 10\n        --iterations 100\n        --display-stats\n```\n\nThe `/tmp/resource.json` can be as simple as:\n\n```json\n{\n  \"path\": \"/index.html\"\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetty-project%2Fjetty-load-generator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjetty-project%2Fjetty-load-generator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetty-project%2Fjetty-load-generator/lists"}