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