View Javadoc

1   package com.atlassian.asap.performance;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.JwtBuilder;
5   import com.atlassian.asap.api.SigningAlgorithm;
6   import com.atlassian.asap.api.client.http.AuthorizationHeaderGenerator;
7   import com.atlassian.asap.api.exception.AuthenticationFailedException;
8   import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
9   import com.atlassian.asap.api.exception.InvalidTokenException;
10  import com.atlassian.asap.api.server.http.RequestAuthenticator;
11  import com.atlassian.asap.core.client.http.AuthorizationHeaderGeneratorImpl;
12  import com.atlassian.asap.core.server.http.RequestAuthenticatorImpl;
13  import com.atlassian.asap.core.validator.JwtValidator;
14  import com.atlassian.asap.core.validator.JwtValidatorImpl;
15  import com.google.common.base.Stopwatch;
16  import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
17  
18  import java.net.URI;
19  import java.time.Instant;
20  import java.time.temporal.ChronoUnit;
21  import java.util.concurrent.ExecutorService;
22  import java.util.concurrent.Executors;
23  import java.util.concurrent.TimeUnit;
24  
25  import static com.atlassian.asap.core.client.SimpleClientRunner.AUDIENCE_SYSPROP;
26  import static com.atlassian.asap.core.client.SimpleClientRunner.ISSUER_SYSPROP;
27  import static com.atlassian.asap.core.client.SimpleClientRunner.KEYID_SYSPROP;
28  import static com.atlassian.asap.core.client.SimpleClientRunner.PRIVATE_KEY_SYSPROP;
29  
30  /**
31   * A main program that runs basic performance checking of the ASAP library, both the client side (signing
32   * and serialising tokens) and the server side (parsing and verifying signatures).
33   */
34  public class Benchmark {
35      private static final SigningAlgorithm ALGORITHM = SigningAlgorithm.valueOf(System.getProperty("asap.client.algorithm", "RS256"));
36  
37      /**
38       * A high value for repetitions is recommended, as cryptographic operations are highly influenced by runtime optimizations.
39       * A high repetition value allows sufficient time for the effect of such optimizations to plateau
40       */
41      private static final long REPETITIONS = Long.getLong("asap.benchmark.repetitions", 200000L);
42  
43      private static final int NUM_THREADS = Integer.getInteger("asap.benchmark.threads", 1);
44  
45      private static final String ISSUER = System.getProperty(ISSUER_SYSPROP, "issuer1");
46      private static final String KEY_ID = System.getProperty(KEYID_SYSPROP, "issuer1/rsa-key-for-tests");
47      private static final String AUDIENCE = System.getProperty(AUDIENCE_SYSPROP, "audience");
48  
49      private static final URI PRIVATE_KEY_BASE_URL = URI.create(System.getProperty(PRIVATE_KEY_SYSPROP,
50              "classpath:/privatekeys/"));
51      private static final String PUBLIC_KEY_BASE_URL = System.getProperty("asap.public.key.repo.url",
52              "https://s3-ap-southeast-2.amazonaws.com/keymaker.syd.dev.atlassian.io/");
53  
54      /**
55       * Main function to run the benchmark test.
56       *
57       * @param args command line arguments
58       * @throws Exception if something did not go as planned
59       */
60      public static void main(String[] args) throws Exception {
61          Jwt jwtPrototype = JwtBuilder.newJwt()
62                  .algorithm(ALGORITHM)
63                  .issuer(ISSUER)
64                  .audience(AUDIENCE)
65                  .keyId(KEY_ID)
66                  .expirationTime(Instant.now().plus(59, ChronoUnit.MINUTES)) // allow enough time for benchmark
67                  .build();
68  
69          System.out.println("============== Benchmark Start ===============");
70          clientTest(REPETITIONS, jwtPrototype, PRIVATE_KEY_BASE_URL);
71          serverValidTest(REPETITIONS, jwtPrototype, PUBLIC_KEY_BASE_URL, PRIVATE_KEY_BASE_URL);
72          System.out.println("============== Benchmark End ================");
73  
74      }
75  
76      private static void clientTest(long repetitions, Jwt jwtPrototype, URI privateKeyBaseUrl)
77              throws InterruptedException {
78          System.out.printf("Client test using key %s and %s algorithm with %d repetitions and %d threads...%n", KEY_ID, ALGORITHM, repetitions, NUM_THREADS);
79          ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS);
80  
81          AuthorizationHeaderGenerator authorizationHeaderGenerator =
82                  AuthorizationHeaderGeneratorImpl.createDefault(privateKeyBaseUrl);
83          Runnable runnableJob = () -> {
84              try {
85                  SummaryStatistics stats = new SummaryStatistics();
86                  for (long i = 0; i < repetitions; i++) {
87                      long startTime = System.nanoTime();
88                      // for a more realistic test, we create a new Jwt object each time
89                      Jwt jwt = JwtBuilder.copyJwt(jwtPrototype).build();
90                      authorizationHeaderGenerator.generateAuthorizationHeader(jwt);
91                      stats.addValue(System.nanoTime() - startTime);   // calculate stats with resolution of nanos
92                  }
93                  System.out.printf("Client-side token generation min/avg/max/stddev = %f/%f/%f/%f ms%n",
94                          nanosToMillis(stats.getMin()), nanosToMillis(stats.getMean()),
95                          nanosToMillis(stats.getMax()), nanosToMillis(stats.getStandardDeviation()));
96              } catch (InvalidTokenException | CannotRetrieveKeyException e) {
97                  e.printStackTrace();
98                  System.exit(1);
99              }
100         };
101 
102         Stopwatch stopwatch = Stopwatch.createUnstarted();
103         stopwatch.start();
104         for (int job = 0; job < NUM_THREADS; job++) {
105             executorService.execute(runnableJob);
106         }
107         executorService.shutdown();
108         executorService.awaitTermination(1, TimeUnit.HOURS);
109         stopwatch.stop();
110 
111         System.out.printf("Elapsed time: %d ms. Throughput: %f requests/sec%n",
112                 stopwatch.elapsed(TimeUnit.MILLISECONDS),
113                 1000.0 * repetitions * NUM_THREADS / stopwatch.elapsed(TimeUnit.MILLISECONDS));
114 
115     }
116 
117     private static void serverValidTest(long repetitions, Jwt jwtPrototype, String publicKeyBaseUrl, URI privateKeyBaseUrl)
118             throws CannotRetrieveKeyException, InvalidTokenException, AuthenticationFailedException {
119         System.out.printf("Server test using key %s and %s algorithm with %d repetitions...%n", KEY_ID, ALGORITHM, repetitions);
120         String firstAudience = jwtPrototype.getClaims().getAudience().iterator().next();
121         JwtValidator jwtValidator = JwtValidatorImpl.createDefault(firstAudience, publicKeyBaseUrl);
122         RequestAuthenticator requestAuthenticator = new RequestAuthenticatorImpl(jwtValidator);
123         Stopwatch stopwatch = Stopwatch.createUnstarted();
124         AuthorizationHeaderGenerator authorizationHeaderGenerator =
125                 AuthorizationHeaderGeneratorImpl.createDefault(privateKeyBaseUrl);
126         SummaryStatistics stats = new SummaryStatistics();
127 
128         // generate an authorisation header before starting the timer
129         final String authorizationHeader = authorizationHeaderGenerator.generateAuthorizationHeader(jwtPrototype);
130 
131         stopwatch.start();
132         for (long i = 0; i < repetitions; i++) {
133             long startTime = System.nanoTime();
134             requestAuthenticator.authenticateRequest(authorizationHeader);
135             stats.addValue(TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS));
136 
137         }
138         stopwatch.stop();
139 
140         System.out.printf("Elapsed time: %d ms. Throughput: %f requests/sec%n",
141                 stopwatch.elapsed(TimeUnit.MILLISECONDS),
142                 1000.0 * repetitions / stopwatch.elapsed(TimeUnit.MILLISECONDS));
143         // the latency between server receiving a verification request and then completing the verification with a public key retrieved from key server.
144         System.out.printf("Server-side Token Verification Latency min/avg/max/stddev = %f/%f/%f/%f ms%n",
145                 stats.getMin(), stats.getMean(), stats.getMax(), stats.getStandardDeviation());
146     }
147 
148     private static double nanosToMillis(double nanos) {
149         return nanos / 1_000_000;
150     }
151 }