1818import static com .google .common .collect .ImmutableList .toImmutableList ;
1919import static com .google .common .collect .Lists .partition ;
2020import static google .registry .util .ResourceUtils .readResourceUtf8 ;
21+ import static java .util .concurrent .Executors .newFixedThreadPool ;
2122
2223import com .google .cloud .tasks .v2 .Task ;
2324import com .google .common .collect .ImmutableList ;
3940import java .util .Iterator ;
4041import java .util .List ;
4142import java .util .Random ;
43+ import java .util .concurrent .ExecutorService ;
4244import java .util .function .Function ;
4345
4446/**
4547 * Simple load test action that can generate configurable QPSes of various EPP actions.
4648 *
49+ * <p>This is not an end-to-end test. It exercises the Nomulus EPP service and the database, but
50+ * does not cover the proxy.
51+ *
4752 * <p>All aspects of the load test are configured via URL parameters that are specified when the
4853 * loadtest URL is being POSTed to. The {@code clientId} and {@code tld} parameters are required.
4954 * All of the other parameters are optional, but if none are specified then no actual load testing
@@ -60,7 +65,7 @@ public class LoadTestAction implements Runnable {
6065
6166 private static final FluentLogger logger = FluentLogger .forEnclosingClass ();
6267
63- private static final int NUM_QUEUES = 10 ;
68+ private static final int NUM_QUEUES = 20 ;
6469 private static final int MAX_TASKS_PER_LOAD = 100 ;
6570 private static final int ARBITRARY_VALID_HOST_LENGTH = 40 ;
6671 private static final int MAX_DOMAIN_LABEL_LENGTH = 63 ;
@@ -72,20 +77,17 @@ public class LoadTestAction implements Runnable {
7277
7378 public static final String PATH = "/_dr/loadtest" ;
7479
80+ // Average task insertion rate with a dedicated thread enqueuing to one queue. This is used to
81+ // calculate the EPP request dispatch time. This value is based on observation and needs not to
82+ // be accurate. However, it should be low enough so that all EPP tasks are enqueued before the
83+ // first task is dispatched.
84+ private static final int TASK_INSERTIONS_PER_QUEUE_PER_MINUTE = 1000 ;
85+
7586 /** The ID of the registrar to use for load testing. */
7687 @ Inject
7788 @ Parameter ("loadtestClientId" )
7889 String registrarId ;
7990
80- /**
81- * The number of seconds to delay the execution of the first load testing tasks by. Preparatory
82- * work of creating independent hosts that will be used for later domain creation testing occurs
83- * during this period, so make sure that it is long enough.
84- */
85- @ Inject
86- @ Parameter ("delaySeconds" )
87- int delaySeconds ;
88-
8991 /**
9092 * The number of seconds that tasks will be enqueued for. Note that if system QPS cannot handle
9193 * the given load then it will take longer than this number of seconds for the test to complete.
@@ -157,9 +159,24 @@ public class LoadTestAction implements Runnable {
157159 xmlHostInfo = loadXml ("host_info" ).replace ("%host%" , EXISTING_HOST );
158160 }
159161
162+ private int eppTaskCount () {
163+ return successfulDomainCreatesPerSecond
164+ + runSeconds
165+ * (successfulHostCreatesPerSecond
166+ + failedHostCreatesPerSecond
167+ + domainInfosPerSecond
168+ + domainChecksPerSecond
169+ + hostInfosPerSecond
170+ + successfulDomainCreatesPerSecond
171+ + failedDomainCreatesPerSecond );
172+ }
173+
160174 @ Override
161175 public void run () {
162- validateAndLogRequest ();
176+ // Delay the EPP request dispatch time to account for queue-insertion time.
177+ int delaySeconds =
178+ Math .ceilDiv (eppTaskCount (), TASK_INSERTIONS_PER_QUEUE_PER_MINUTE * NUM_QUEUES ) * 60 ;
179+ validateAndLogRequest (delaySeconds );
163180 Instant initialStartSecond = clock .now ().plus (Duration .ofSeconds (delaySeconds ));
164181 ImmutableList .Builder <String > preTaskXmls = new ImmutableList .Builder <>();
165182 ImmutableList .Builder <String > hostPrefixesBuilder = new ImmutableList .Builder <>();
@@ -209,7 +226,7 @@ public void run() {
209226 logger .atInfo ().log ("Added %d total load test tasks." , taskOptions .size ());
210227 }
211228
212- private void validateAndLogRequest () {
229+ private void validateAndLogRequest (int delaySeconds ) {
213230 checkArgument (
214231 RegistryEnvironment .get () != RegistryEnvironment .PRODUCTION ,
215232 "DO NOT RUN LOADTESTS IN PROD!" );
@@ -297,9 +314,20 @@ private ImmutableList<Task> createTasks(ImmutableList<String> xmls, Instant star
297314
298315 private void enqueue (ImmutableList <Task > tasks ) {
299316 List <List <Task >> chunks = partition (tasks , MAX_TASKS_PER_LOAD );
300- // Farm out tasks to multiple queues to work around queue qps quotas.
301- for (int i = 0 ; i < chunks .size (); i ++) {
302- cloudTasksUtils .enqueue ("load" + (i % NUM_QUEUES ), chunks .get (i ));
317+ // Farm out tasks to multiple queues to work around queue qps quotas. Use multiple threads to
318+ // speed up the enqueuing.
319+ try (ExecutorService executorService = newFixedThreadPool (NUM_QUEUES )) {
320+ for (int i = 0 ; i < chunks .size (); i ++) {
321+ final int index = i ;
322+ // lgtm[java/local-variable-is-never-read] Suppress Github CodeQL's outdated warning
323+ var _ =
324+ executorService .submit (
325+ () -> cloudTasksUtils .enqueue (getQueueName (index % NUM_QUEUES ), chunks .get (index )));
326+ }
303327 }
304328 }
329+
330+ private static String getQueueName (int queueId ) {
331+ return String .format ("load%d" , queueId );
332+ }
305333}
0 commit comments