1313import com .azure .core .http .policy .HttpPipelinePolicy ;
1414import com .azure .core .http .vertx .VertxAsyncHttpClientBuilder ;
1515import com .azure .core .http .vertx .VertxAsyncHttpClientProvider ;
16+ import com .azure .core .util .logging .ClientLogger ;
1617import io .netty .handler .ssl .SslContext ;
1718import io .netty .handler .ssl .SslContextBuilder ;
1819import io .netty .handler .ssl .util .InsecureTrustManagerFactory ;
2324import javax .net .ssl .SSLContext ;
2425import javax .net .ssl .SSLException ;
2526import javax .net .ssl .X509TrustManager ;
27+ import java .lang .reflect .Method ;
2628import java .net .URI ;
2729import java .security .KeyManagementException ;
2830import java .security .NoSuchAlgorithmException ;
2931import java .security .SecureRandom ;
3032import java .util .Collections ;
33+ import java .util .LinkedList ;
34+ import java .util .List ;
3135import java .util .concurrent .CompletableFuture ;
36+ import java .util .concurrent .ExecutionException ;
37+ import java .util .concurrent .ExecutorService ;
38+ import java .util .concurrent .Executors ;
39+ import java .util .concurrent .Semaphore ;
40+ import java .util .concurrent .TimeUnit ;
3241
3342import static com .azure .perf .test .core .PerfStressOptions .HttpClientType .JDK ;
3443import static com .azure .perf .test .core .PerfStressOptions .HttpClientType .NETTY ;
4150 * @param <TOptions> the performance test options to use while running the test.
4251 */
4352public abstract class ApiPerfTestBase <TOptions extends PerfStressOptions > extends PerfTestBase <TOptions > {
53+ ClientLogger LOGGER = new ClientLogger (ApiPerfTestBase .class );
4454 private final reactor .netty .http .client .HttpClient recordPlaybackHttpClient ;
4555 private final URI testProxy ;
4656 private final TestProxyPolicy testProxyPolicy ;
@@ -225,6 +235,106 @@ public Mono<Void> runAllAsync(long endNanoTime) {
225235 .then ();
226236 }
227237
238+ public CompletableFuture <Void > runAllAsyncWithCompletableFuture (long endNanoTime ) {
239+ completedOperations = 0 ;
240+ lastCompletionNanoTime = 0 ;
241+ long startNanoTime = System .nanoTime ();
242+ Semaphore semaphore = new Semaphore (options .getParallel ()); // Use configurable limit
243+
244+ List <CompletableFuture <Void >> futures = new LinkedList <>();
245+ while (System .nanoTime () < endNanoTime ) {
246+ try {
247+ semaphore .acquire ();
248+ // Each runTestAsyncWithCompletableFuture() call runs independently
249+ CompletableFuture <Void > testFuture = runTestAsyncWithCompletableFuture ()
250+ .thenAccept (result -> {
251+ completedOperations += result ;
252+ lastCompletionNanoTime = System .nanoTime () - startNanoTime ;
253+ })
254+ .whenComplete ((res , ex ) -> semaphore .release ());
255+ futures .add (testFuture );
256+ } catch (InterruptedException e ) {
257+ Thread .currentThread ().interrupt ();
258+ throw new RuntimeException (e );
259+ }
260+ }
261+
262+ // Remove all completed CompletableFutures from the list
263+ futures .removeIf (CompletableFuture ::isDone );
264+ // Combine all futures so we can wait for all to complete
265+ return CompletableFuture .allOf (futures .toArray (new CompletableFuture <?>[0 ]));
266+ }
267+
268+ @ Override
269+ public Runnable runAllAsyncWithExecutorService (long endNanoTime ) {
270+ completedOperations = 0 ;
271+ lastCompletionNanoTime = 0 ;
272+ final ExecutorService executor = Executors .newFixedThreadPool (options .getParallel ());
273+
274+ return () -> {
275+ try {
276+ while (System .nanoTime () < endNanoTime ) {
277+ long startNanoTime = System .nanoTime ();
278+
279+ try {
280+ Runnable task = runTestAsyncWithExecutorService ();
281+ executor .submit (() -> {
282+ task .run ();
283+ completedOperations ++;
284+ lastCompletionNanoTime = System .nanoTime () - startNanoTime ;
285+ }).get (); // Wait for the task to complete
286+ } catch (InterruptedException | ExecutionException e ) {
287+ e .printStackTrace ();
288+ }
289+ }
290+ } finally {
291+ executor .shutdown ();
292+ try {
293+ if (!executor .awaitTermination (options .getDuration (), TimeUnit .SECONDS )) {
294+ executor .shutdownNow ();
295+ }
296+ } catch (InterruptedException e ) {
297+ executor .shutdownNow ();
298+ Thread .currentThread ().interrupt ();
299+ }
300+ }
301+ };
302+ }
303+
304+ @ Override
305+ public Runnable runAllAsyncWithVirtualThread (long endNanoTime ) {
306+ completedOperations = 0 ;
307+ lastCompletionNanoTime = 0 ;
308+
309+ ExecutorService virtualThreadExecutor ;
310+ try {
311+ Method method = Executors .class .getMethod ("newVirtualThreadPerTaskExecutor" );
312+ virtualThreadExecutor = (ExecutorService ) method .invoke (null );
313+ } catch (Exception e ) {
314+ // Skip virtual thread tests and report 0 completed operations rather than fallback
315+ return () -> {
316+ completedOperations = 0 ;
317+ lastCompletionNanoTime = 0 ;
318+ };
319+ }
320+
321+ return () -> {
322+ while (System .nanoTime () < endNanoTime ) {
323+ long startNanoTime = System .nanoTime ();
324+ virtualThreadExecutor .execute (() -> {
325+ try {
326+ runTestAsyncWithVirtualThread ();
327+ completedOperations ++;
328+ lastCompletionNanoTime = System .nanoTime () - startNanoTime ;
329+ } catch (Exception e ) {
330+ LOGGER .logThrowableAsError (e );
331+ }
332+ });
333+ }
334+ virtualThreadExecutor .shutdown ();
335+ };
336+ }
337+
228338 /**
229339 * Indicates how many operations were completed in a single run of the test. Good to be used for batch operations.
230340 *
@@ -240,6 +350,31 @@ public Mono<Void> runAllAsync(long endNanoTime) {
240350 */
241351 abstract Mono <Integer > runTestAsync ();
242352
353+ /**
354+ * Indicates how many operations were completed in a single run of the async test using CompletableFuture.
355+ *
356+ * @return the number of successful operations completed.
357+ */
358+ CompletableFuture <Integer > runTestAsyncWithCompletableFuture () {
359+ throw new UnsupportedOperationException ("runAllAsyncWithCompletableFuture is not supported." );
360+ }
361+
362+ /**
363+ * Indicates how many operations were completed in a single run of the async test using ExecutorService.
364+ *
365+ * @return the number of successful operations completed.
366+ */
367+ Runnable runTestAsyncWithExecutorService () {
368+ throw new UnsupportedOperationException ("runAllAsyncWithExecutorService is not supported." );
369+ }
370+
371+ /**
372+ * Indicates how many operations were completed in a single run of the async test using Virtual Threads.
373+ */
374+ Runnable runTestAsyncWithVirtualThread () {
375+ throw new UnsupportedOperationException ("runAllAsyncWithVirtualThread is not supported." );
376+ }
377+
243378 /**
244379 * Stops playback tests.
245380 *
@@ -327,6 +462,12 @@ private Mono<Void> runSyncOrAsync() {
327462 return Mono .defer (() -> {
328463 if (options .isSync ()) {
329464 return Mono .fromFuture (CompletableFuture .supplyAsync (() -> runTest ())).then ();
465+ } else if (options .isCompletableFuture ()) {
466+ return Mono .fromFuture (CompletableFuture .supplyAsync (() -> runTestAsyncWithCompletableFuture ())).then ();
467+ } else if (options .isExecutorService ()) {
468+ return Mono .fromRunnable (runTestAsyncWithExecutorService ());
469+ } else if (options .isVirtualThread ()) {
470+ return Mono .fromRunnable (this ::runTestAsyncWithVirtualThread );
330471 } else {
331472 return runTestAsync ().then ();
332473 }
0 commit comments