content: package io.activej.http;
  
  import io.activej.async.callback.Callback;
  import io.activej.config.Config;
  import io.activej.config.ConfigModule;
  import io.activej.dns.DnsClient;
  import io.activej.dns.IDnsClient;
  import io.activej.eventloop.Eventloop;
  import io.activej.inject.annotation.Inject;
  import io.activej.inject.annotation.Named;
  import io.activej.inject.annotation.Provides;
  import io.activej.inject.module.Module;
  import io.activej.launcher.Launcher;
  import io.activej.promise.Promise;
  import io.activej.promise.SettablePromise;
  import io.activej.reactor.nio.NioReactor;
  import io.activej.service.ServiceGraphModule;
  import org.jetbrains.annotations.Nullable;
  
  import java.time.Duration;
  
  import static io.activej.config.converter.ConfigConverters.*;
  import static io.activej.inject.module.Modules.combine;
  import static java.lang.Math.min;
  
  public class HttpServerWorkloadBenchmark extends Launcher {
  	private static final int KEEP_ALIVE = 30;
  	private static final int TOTAL_REQUESTS = 1_000_000;
  	private static final int WARMUP_ROUNDS = 3;
  	private static final int BENCHMARK_ROUNDS = 5;
  	private static final int ACTIVE_REQUESTS_MAX = 300;
  	private static final int ACTIVE_REQUESTS_MIN = 200;
  
  	private String address;
  	private int totalRequests;
  	private int warmupRounds;
  	private int measureRounds;
  	private int activeRequestsMax;
  	private int activeRequestsMin;
  
  	@Provides
  	@Named("server")
  	NioReactor serverReactor() {return Eventloop.create();}
  
  	@Provides
  	@Named("client")
  	NioReactor clientReactor() {return Eventloop.create();}
  
  	@Inject
  	@Named("server")
  	NioReactor serverReactor;
  
  	@Inject
  	@Named("client")
  	NioReactor clientReactor;
  
  	@Inject
  	Config config;
  
  	@Inject
  	HttpServer server;
  
  	@Inject
  	IHttpClient client;
  
  	@Provides
  	HttpServer server() {
  		return HttpServer.builder(serverReactor,
  				request -> HttpResponse.ok200()
  					.withPlainText("Response!!")
  					.toPromise())
  			.withListenAddresses(config.get(ofList(ofInetSocketAddress()), "address"))
  			.build();
  	}
  
  	@Provides
  	IDnsClient dnsClient() {
  		return DnsClient.create(clientReactor, HttpUtils.inetAddress("8.8.8.8"));
  	}
  
  	@Provides
  	IHttpClient client(IDnsClient dnsClient) {
  		return HttpClient.builder(clientReactor, dnsClient)
  			.withKeepAliveTimeout(Duration.ofSeconds(config.get(ofInteger(),
  				"client.keepAlive", KEEP_ALIVE)))
  			.build();
  	}
  
  	@Provides
  	Config config() {
  		return Config.create()
  			.with("address", "0.0.0.0:9001")
  			.with("client.address", "http://127.0.0.1:9001/")
  			.overrideWith(Config.ofSystemProperties("config"));
  	}
  
  	@Override
  	protected Module getModule() {
  		return combine(
  			ServiceGraphModule.create(),
  			ConfigModule.builder()
  				.withEffectiveConfigLogger()
  				.build());
  	}
  
  	@Override
  	protected void onStart() {
  		this.address = config.get("client.address");
  		this.totalRequests = config.get(ofInteger(), "benchmark.totalRequests", TOTAL_REQUESTS);
  		this.warmupRounds = config.get(ofInteger(), "benchmark.warmupRounds", WARMUP_ROUNDS);
  		this.measureRounds = config.get(ofInteger(), "benchmark.measureRounds", BENCHMARK_ROUNDS);
  		this.activeRequestsMax = config.get(ofInteger(), "benchmark.activeRequestsMax", ACTIVE_REQUESTS_MAX);
  		this.activeRequestsMin = config.get(ofInteger(), "benchmark.activeRequestsMin", ACTIVE_REQUESTS_MIN);
  	}
  
  	@Override
  	protected void run() throws Exception {
  		long timeAllRounds = 0;
  		long bestTime = -1;
  		long worstTime = -1;
  
  		System.out.println("Warming up ...");
  		for (int i = 0; i < warmupRounds; i++) {
  			long roundTime = round();
  			long rps = totalRequests * 1000L / roundTime;
  			System.out.println("Round: " + (i + 1) + "; Round time: " + roundTime + "ms; RPS : " + rps);
  		}
  
  		System.out.println("Start benchmarking GET Request");
  		for (int i = 0; i < measureRounds; i++) {
  			long roundTime = round();
  			timeAllRounds += roundTime;
  
  			if (bestTime == -1 || roundTime < bestTime) {
  				bestTime = roundTime;
  			}
  
  			if (worstTime == -1 || roundTime > worstTime) {
  				worstTime = roundTime;
  			}
  
  			long rps = totalRequests * 1000L / roundTime;
  			System.out.println("Round: " + (i + 1) + "; Round time: " + roundTime + " ms; RPS : " + rps);
  		}
  
  		double avgTime = (double) timeAllRounds / measureRounds;
  		long requestsPerSecond = (long) (totalRequests / avgTime * 1000);
  		System.out.println(
  			"Time: " + timeAllRounds + "ms; Average time: " + avgTime + " ms; Best time: " +
  			bestTime + "ms; Worst time: " + worstTime + "ms; Requests per second: " + requestsPerSecond);
  	}
  
  	private long round() throws Exception {
  		return clientReactor.submit(this::roundGet).get();
  	}
  
  	int sent;
  	int completed;
  
  	private Promise<Long> roundGet() {
  		SettablePromise<Long> promise = new SettablePromise<>();
  
  		Callback<HttpResponse> callback = new Callback<>() {
  			@Override
  			public void accept(HttpResponse result, @Nullable Exception e) {
  				completed++;
  				int active = sent - completed;
  
  				if (e != null) {
  					promise.setException(new FailedRequestException());
  					return;
  				}
  
  				if (completed == totalRequests) {
  					promise.set(null);
  					return;
  				}
  
  				if (active <= activeRequestsMin) {
  					for (int i = 0; i < min(activeRequestsMax - active, totalRequests - sent); i++, sent++) {
  						doGet(this);
  					}
  				}
  			}
  		};
  		sent = 0;
  		completed = 0;
  		long start = System.currentTimeMillis();
  
  		for (int i = 0; i < min(activeRequestsMin, totalRequests); i++) {
  			doGet(callback);
  			sent++;
  		}
  
  		return promise.map($ -> System.currentTimeMillis() - start);
  	}
  
  	private void doGet(Callback<HttpResponse> callback) {
  		client.request(HttpRequest.get(address).build()).subscribe(callback);
  	}
  
  	public static void main(String[] args) throws Exception {
  		Launcher benchmark = new HttpServerWorkloadBenchmark();
  		benchmark.launch(args);
  	}
  
  	private static class FailedRequestException extends Exception {}
  }
  