View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.benchmark;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.net.SocketAddress;
32  import java.net.URI;
33  import java.nio.ByteBuffer;
34  import java.nio.channels.ByteChannel;
35  import java.nio.file.Paths;
36  import java.util.List;
37  import java.util.concurrent.CountDownLatch;
38  import java.util.concurrent.TimeUnit;
39  import java.util.concurrent.atomic.AtomicLong;
40  import java.util.concurrent.locks.Lock;
41  
42  import javax.net.ssl.SSLContext;
43  
44  import org.apache.commons.cli.CommandLine;
45  import org.apache.commons.cli.CommandLineParser;
46  import org.apache.commons.cli.DefaultParser;
47  import org.apache.commons.cli.Options;
48  import org.apache.hc.core5.http.Header;
49  import org.apache.hc.core5.http.HttpConnection;
50  import org.apache.hc.core5.http.HttpHost;
51  import org.apache.hc.core5.http.HttpRequest;
52  import org.apache.hc.core5.http.HttpResponse;
53  import org.apache.hc.core5.http.HttpVersion;
54  import org.apache.hc.core5.http.impl.Http1StreamListener;
55  import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
56  import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
57  import org.apache.hc.core5.http.protocol.HttpCoreContext;
58  import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
59  import org.apache.hc.core5.http.protocol.RequestExpectContinue;
60  import org.apache.hc.core5.http.protocol.RequestUserAgent;
61  import org.apache.hc.core5.http2.HttpVersionPolicy;
62  import org.apache.hc.core5.http2.config.H2Config;
63  import org.apache.hc.core5.http2.frame.FramePrinter;
64  import org.apache.hc.core5.http2.frame.FrameType;
65  import org.apache.hc.core5.http2.frame.RawFrame;
66  import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
67  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
68  import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
69  import org.apache.hc.core5.http2.protocol.H2RequestContent;
70  import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
71  import org.apache.hc.core5.io.CloseMode;
72  import org.apache.hc.core5.reactor.Command;
73  import org.apache.hc.core5.reactor.IOEventHandler;
74  import org.apache.hc.core5.reactor.IOReactorConfig;
75  import org.apache.hc.core5.reactor.IOSession;
76  import org.apache.hc.core5.ssl.SSLContextBuilder;
77  import org.apache.hc.core5.ssl.SSLContexts;
78  import org.apache.hc.core5.util.Timeout;
79  
80  /**
81   * Main program of the HTTP benchmark.
82   *
83   * @since 4.0
84   */
85  public class HttpBenchmark {
86  
87      private final BenchmarkConfig config;
88  
89      public static void main(final String[] args) throws Exception {
90  
91          final Options options = CommandLineUtils.getOptions();
92          final CommandLineParser parser = new DefaultParser();
93          final CommandLine cmd = parser.parse(options, args);
94  
95          if (args.length == 0 || cmd.hasOption('h') || cmd.getArgs().length != 1) {
96              CommandLineUtils.showUsage(options);
97              System.exit(1);
98          }
99  
100         final BenchmarkConfig config = CommandLineUtils.parseCommandLine(cmd);
101 
102         if (config.getUri() == null) {
103             CommandLineUtils.showUsage(options);
104             System.exit(1);
105         }
106 
107         final HttpBenchmark httpBenchmark = new HttpBenchmark(config);
108         final Results results = httpBenchmark.execute();
109         System.out.println();
110         ResultFormatter.print(System.out, results);
111     }
112 
113     public HttpBenchmark(final BenchmarkConfig config) {
114         super();
115         this.config = config != null ? config : BenchmarkConfig.custom().build();
116     }
117 
118     public Results execute() throws Exception {
119         final HttpProcessorBuilder builder = HttpProcessorBuilder.create()
120                 .addAll(
121                         H2RequestContent.INSTANCE,
122                         H2RequestTargetHost.INSTANCE,
123                         H2RequestConnControl.INSTANCE,
124                         new RequestUserAgent("HttpCore-AB/5.0"));
125         if (this.config.isUseExpectContinue()) {
126             builder.add(RequestExpectContinue.INSTANCE);
127         }
128 
129         final SSLContext sslContext;
130         if ("https".equals(config.getUri().getScheme())) {
131             final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
132             sslContextBuilder.setProtocol("SSL");
133             if (config.isDisableSSLVerification()) {
134                 sslContextBuilder.loadTrustMaterial(null, (chain, authType) -> true);
135             } else if (config.getTrustStorePath() != null) {
136                 sslContextBuilder.loadTrustMaterial(
137                         new File(config.getTrustStorePath()),
138                         config.getTrustStorePassword() != null ? config.getTrustStorePassword().toCharArray() : null);
139             }
140             if (config.getIdentityStorePath() != null) {
141                 sslContextBuilder.loadKeyMaterial(
142                         Paths.get(config.getIdentityStorePath()),
143                         config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null,
144                         config.getIdentityStorePassword() != null ? config.getIdentityStorePassword().toCharArray() : null);
145             }
146             sslContext = sslContextBuilder.build();
147         } else {
148             sslContext = SSLContexts.createSystemDefault();
149         }
150 
151         final HttpVersionPolicy versionPolicy;
152         if (config.isForceHttp2()) {
153             versionPolicy = HttpVersionPolicy.FORCE_HTTP_2;
154         } else {
155             if (sslContext != null) {
156                 versionPolicy = HttpVersionPolicy.NEGOTIATE;
157             } else {
158                 versionPolicy = HttpVersionPolicy.FORCE_HTTP_1;
159             }
160         }
161 
162         final Stats stats = new Stats();
163         try (final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap()
164                 .setHttpProcessor(builder.build())
165                 .setTlsStrategy(new BasicClientTlsStrategy(sslContext))
166                 .setVersionPolicy(versionPolicy)
167                 .setH2Config(H2Config.custom()
168                         .setPushEnabled(false)
169                         .build())
170                 .setIOSessionDecorator(ioSession -> new IOSession() {
171 
172                     @Override
173                     public String getId() {
174                         return ioSession.getId();
175                     }
176 
177                     @Override
178                     public Lock getLock() {
179                         return ioSession.getLock();
180                     }
181 
182                     @Override
183                     public void enqueue(final Command command, final Command.Priority priority) {
184                         ioSession.enqueue(command, priority);
185                     }
186 
187                     @Override
188                     public boolean hasCommands() {
189                         return ioSession.hasCommands();
190                     }
191 
192                     @Override
193                     public Command poll() {
194                         return ioSession.poll();
195                     }
196 
197                     @Override
198                     public ByteChannel channel() {
199                         return ioSession.channel();
200                     }
201 
202                     @Override
203                     public SocketAddress getRemoteAddress() {
204                         return ioSession.getRemoteAddress();
205                     }
206 
207                     @Override
208                     public SocketAddress getLocalAddress() {
209                         return ioSession.getLocalAddress();
210                     }
211 
212                     @Override
213                     public int getEventMask() {
214                         return ioSession.getEventMask();
215                     }
216 
217                     @Override
218                     public void setEventMask(final int ops) {
219                         ioSession.setEventMask(ops);
220                     }
221 
222                     @Override
223                     public void setEvent(final int op) {
224                         ioSession.setEvent(op);
225                     }
226 
227                     @Override
228                     public void clearEvent(final int op) {
229                         ioSession.clearEvent(op);
230                     }
231 
232                     @Override
233                     public void close() {
234                         ioSession.close();
235                     }
236 
237                     @Override
238                     public Status getStatus() {
239                         return ioSession.getStatus();
240                     }
241 
242                     @Override
243                     public int read(final ByteBuffer dst) throws IOException {
244                         final int bytesRead = ioSession.read(dst);
245                         if (bytesRead > 0) {
246                             stats.incTotalBytesRecv(bytesRead);
247                         }
248                         return bytesRead;
249                     }
250 
251                     @Override
252                     public int write(final ByteBuffer src) throws IOException {
253                         final int bytesWritten = ioSession.write(src);
254                         if (bytesWritten > 0) {
255                             stats.incTotalBytesSent(bytesWritten);
256                         }
257                         return bytesWritten;
258                     }
259 
260                     @Override
261                     public boolean isOpen() {
262                         return ioSession.isOpen();
263                     }
264 
265                     @Override
266                     public Timeout getSocketTimeout() {
267                         return ioSession.getSocketTimeout();
268                     }
269 
270                     @Override
271                     public void setSocketTimeout(final Timeout timeout) {
272                         ioSession.setSocketTimeout(timeout);
273                     }
274 
275                     @Override
276                     public long getLastReadTime() {
277                         return ioSession.getLastReadTime();
278                     }
279 
280                     @Override
281                     public long getLastWriteTime() {
282                         return ioSession.getLastWriteTime();
283                     }
284 
285                     @Override
286                     public long getLastEventTime() {
287                         return ioSession.getLastEventTime();
288                     }
289 
290                     @Override
291                     public void updateReadTime() {
292                         ioSession.updateReadTime();
293                     }
294 
295                     @Override
296                     public void updateWriteTime() {
297                         ioSession.updateWriteTime();
298                     }
299 
300                     @Override
301                     public void close(final CloseMode closeMode) {
302                         ioSession.close(closeMode);
303                     }
304 
305                     @Override
306                     public IOEventHandler getHandler() {
307                         return ioSession.getHandler();
308                     }
309 
310                     @Override
311                     public void upgrade(final IOEventHandler handler) {
312                         ioSession.upgrade(handler);
313                     }
314 
315                 })
316                 .setStreamListener(new Http1StreamListener() {
317 
318                     @Override
319                     public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
320                         if (config.getVerbosity() >= 3) {
321                             System.out.println(">> " + request.getMethod() + " " + request.getRequestUri());
322                             final Header[] headers = request.getHeaders();
323                             for (final Header header : headers) {
324                                 System.out.println(">> " + header);
325                             }
326                             System.out.println();
327                         }
328                     }
329 
330                     @Override
331                     public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
332                         if (config.getVerbosity() >= 3) {
333                             System.out.println("<< " + response.getCode() + " " + response.getReasonPhrase());
334                             final Header[] headers = response.getHeaders();
335                             for (final Header header : headers) {
336                                 System.out.println("<< " + header);
337                             }
338                             System.out.println();
339                         }
340                     }
341 
342                     @Override
343                     public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
344                     }
345 
346                 })
347                 .setStreamListener(new H2StreamListener() {
348 
349                     private final FramePrinter framePrinter = new FramePrinter();
350 
351                     @Override
352                     public void onHeaderInput(
353                             final HttpConnection connection,
354                             final int streamId,
355                             final List<? extends Header> headers) {
356                         if (config.getVerbosity() >= 3) {
357                             for (final Header header : headers) {
358                                 System.out.println("<< " + header);
359                             }
360                             System.out.println();
361                         }
362                     }
363 
364                     @Override
365                     public void onHeaderOutput(
366                             final HttpConnection connection,
367                             final int streamId,
368                             final List<? extends Header> headers) {
369                         if (config.getVerbosity() >= 3) {
370                             for (final Header header : headers) {
371                                 System.out.println(">> " + header);
372                             }
373                             System.out.println();
374                         }
375                     }
376 
377                     @Override
378                     public void onFrameInput(
379                             final HttpConnection connection,
380                             final int streamId,
381                             final RawFrame frame) {
382                         if (config.getVerbosity() >= 4) {
383                             System.out.print("<< ");
384                             try {
385                                 framePrinter.printFrameInfo(frame, System.out);
386                                 System.out.println();
387                                 if (!frame.isType(FrameType.DATA)) {
388                                     framePrinter.printPayload(frame, System.out);
389                                     System.out.println();
390                                 }
391                             } catch (final IOException ignore) {
392                             }
393                         }
394                     }
395 
396                     @Override
397                     public void onFrameOutput(
398                             final HttpConnection connection,
399                             final int streamId,
400                             final RawFrame frame) {
401                         if (config.getVerbosity() >= 4) {
402                             System.out.print(">> ");
403                             try {
404                                 framePrinter.printFrameInfo(frame, System.out);
405                                 System.out.println();
406                                 if (!frame.isType(FrameType.DATA)) {
407                                     framePrinter.printPayload(frame, System.out);
408                                     System.out.println();
409                                 }
410                             } catch (final IOException ignore) {
411                             }
412                         }
413                     }
414 
415                     @Override
416                     public void onInputFlowControl(
417                             final HttpConnection connection,
418                             final int streamId,
419                             final int delta,
420                             final int actualSize) {
421                         if (config.getVerbosity() >= 5) {
422                             System.out.println("<< stream " + streamId + ": " + actualSize + " " + delta);
423                         }
424                     }
425 
426                     @Override
427                     public void onOutputFlowControl(
428                             final HttpConnection connection,
429                             final int streamId,
430                             final int delta,
431                             final int actualSize) {
432                         if (config.getVerbosity() >= 5) {
433                             System.out.println(">> stream " + streamId + ": " + actualSize + " " + delta);
434                         }
435                     }
436 
437                 })
438                 .setIOReactorConfig(IOReactorConfig.custom()
439                     .setSoTimeout(config.getSocketTimeout())
440                     .build())
441                 .create()) {
442             requester.setDefaultMaxPerRoute(config.getConcurrencyLevel());
443             requester.setMaxTotal(config.getConcurrencyLevel() * 2);
444             requester.start();
445             return doExecute(requester, stats);
446         }
447     }
448 
449     private Results doExecute(final HttpAsyncRequester requester, final Stats stats) throws Exception {
450 
451         final URI requestUri = config.getUri();
452         final HttpHost host = new HttpHost(requestUri.getScheme(), requestUri.getHost(), requestUri.getPort());
453 
454         final AtomicLong requestCount = new AtomicLong(config.getRequests());
455 
456         final HttpVersion version = HttpVersion.HTTP_1_1;
457 
458         final CountDownLatch completionLatch = new CountDownLatch(config.getConcurrencyLevel());
459         final BenchmarkWorker[] workers = new BenchmarkWorker[config.getConcurrencyLevel()];
460         for (int i = 0; i < workers.length; i++) {
461             final HttpCoreContext context = HttpCoreContext.create();
462             context.setProtocolVersion(version);
463             final BenchmarkWorker worker = new BenchmarkWorker(
464                     requester,
465                     host,
466                     context,
467                     requestCount,
468                     completionLatch,
469                     stats,
470                     config);
471             workers[i] = worker;
472         }
473 
474         final long deadline = config.getTimeLimit() != null ? config.getTimeLimit().toMilliseconds() : Long.MAX_VALUE;
475 
476         final long startTime = System.currentTimeMillis();
477 
478         for (int i = 0; i < workers.length; i++) {
479             workers[i].execute();
480         }
481 
482         completionLatch.await(deadline, TimeUnit.MILLISECONDS);
483 
484         if (config.getVerbosity() >= 3) {
485             System.out.println("...done");
486         }
487 
488         final long endTime = System.currentTimeMillis();
489 
490         for (int i = 0; i < workers.length; i++) {
491             workers[i].releaseResources();
492         }
493 
494         return new Results(
495                 stats.getServerName(),
496                 stats.getVersion(),
497                 host.getHostName(),
498                 host.getPort() > 0 ? host.getPort() : host.getSchemeName().equalsIgnoreCase("https") ? 443 : 80,
499                 requestUri.toASCIIString(),
500                 stats.getContentLength(),
501                 config.getConcurrencyLevel(),
502                 endTime - startTime,
503                 stats.getSuccessCount(),
504                 stats.getFailureCount(),
505                 stats.getKeepAliveCount(),
506                 stats.getTotalBytesRecv(),
507                 stats.getTotalBytesSent(),
508                 stats.getTotalContentLength());
509     }
510 
511 }