You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2014/05/01 13:43:45 UTC

svn commit: r1591622 [31/33] - in /httpd/mod_spdy/trunk: ./ base/ base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/ build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/ build/linux/ build/mac/ build/util/ build/win/ install...

Added: httpd/mod_spdy/trunk/scripts/loadtest.py
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/scripts/loadtest.py?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/scripts/loadtest.py (added)
+++ httpd/mod_spdy/trunk/scripts/loadtest.py Thu May  1 11:43:36 2014
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+
+# Copyright 2012 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A simple script for load-testing mod_spdy (or any other SPDY server).  For
+# example, to hit the server with 150 simultaneous SPDY clients, each fetching
+# the URLs https://example.com/ and https://example.com/image.jpg, you would
+# run:
+#
+#  $ ./loadtest.py spdy 150 https://example.com/ https://example.com/image.jpg
+#
+# To run the same test with plain HTTPS clients instead of SPDY clients (for
+# comparison), you would run:
+#
+#  $ ./loadtest.py https 150 https://example.com/ https://example.com/image.jpg
+#
+# Press Ctrl-C to stop the test.
+#
+# You must have spdycat (https://github.com/tatsuhiro-t/spdylay) installed and
+# on your $PATH in order to run SPDY tests, and you must have curl installed in
+# order to run HTTPS or HTTP tests.
+
+from __future__ import division  # Always convert ints to floats for / operator
+from __future__ import print_function  # Treat print as function, not keyword
+
+import re
+import subprocess
+import sys
+import time
+
+#=============================================================================#
+
+def print_usage_and_quit():
+  sys.stderr.write('Usage: {0} TYPE MAX_CLIENTS URL...\n'.format(sys.argv[0]))
+  sys.stderr.write('TYPE must be one of "spdy", "https", or "http"\n')
+  sys.stderr.write('MAX_CLIENTS must be a positive integer\n')
+  sys.exit(1)
+
+def with_scheme(url, scheme):
+  """Given a URL string, return a new URL string with the given scheme."""
+  if re.match(r'^[a-zA-Z0-9]+:', url):
+    return re.sub(r'^[a-zA-Z0-9]+:', scheme + ':', url)
+  elif url.startswith('//'):
+    return scheme + ':' + url
+  else:
+    return scheme + '://' + url
+
+
+class ClientProcess (object):
+  """A client subprocess that will try to load the URLs from the server."""
+
+  def __init__(self, key, command, factory):
+    self.__key = key
+    self.__child = subprocess.Popen(command, stdout=open('/dev/null', 'wb'))
+    self.__start_time = time.time()
+    self.__factory = factory
+
+  def get_key(self):
+    return self.__key
+
+  def get_start_time(self):
+    return self.__start_time
+
+  def check_done(self):
+    """If the client is done, print time and return True, else return False."""
+    code = self.__child.poll()
+    if code is None:
+      return False
+    else:
+      duration = time.time() - self.__start_time
+      self.__factory._client_finished(self.__key, code, duration)
+      return True
+
+  def kill(self):
+    """Shut down this client."""
+    self.__child.kill()
+
+
+class ClientFactory (object):
+  """A factory for ClientProcess objects, that also tracks stats."""
+
+  def __init__(self, command):
+    """Create a factory that will use the given command for subprocesses."""
+    self.__command = command
+    self.num_started = 0
+    self.num_finished = 0
+    self.max_duration = 0.0
+    self.total_duration = 0.0
+
+  def new_client(self):
+    """Create and return a new ClientProcess."""
+    self.num_started += 1
+    return ClientProcess(key=self.num_started, command=self.__command,
+                         factory=self)
+
+  def _client_finished(self, key, code, duration):
+    """Called by each ClientProcess when it finishes."""
+    self.num_finished += 1
+    self.max_duration = max(self.max_duration, duration)
+    self.total_duration += duration
+    print('Client {0} exit {1} after {2:.3f}s'.format(key, code, duration))
+
+#=============================================================================#
+
+if len(sys.argv) < 4:
+  print_usage_and_quit()
+
+# Determine what type of test we're doing and what URL scheme to use.
+TYPE = sys.argv[1].lower()
+if TYPE not in ['spdy', 'https', 'http']:
+  print_usage_and_quit()
+SCHEME = 'https' if TYPE == 'spdy' else TYPE
+
+# Determine how many clients to have at once.
+try:
+  MAX_CLIENTS = int(sys.argv[2])
+except ValueError:
+  print_usage_and_quit()
+if MAX_CLIENTS < 1:
+  print_usage_and_quit()
+
+# Collect the URLs to fetch from.
+URLS = []
+for url in sys.argv[3:]:
+  URLS.append(with_scheme(url, SCHEME))
+
+# Put together the subprocess command to issue for each client.
+if TYPE == 'spdy':
+  # The -n flag tells spdycat throw away the downloaded data without saving it.
+  COMMAND = ['spdycat', '-n'] + URLS
+else:
+  # The -s flag tells curl to be silent (don't display progress meter); the -k
+  # flag tells curl to ignore certificate errors (e.g. self-signed certs).
+  COMMAND = ['curl', '-sk'] + URLS
+
+# Print out a summary of the test we'll be doing before we start.
+print('TYPE={0}'.format(TYPE))
+print('URLS ({0}):'.format(len(URLS)))
+for url in URLS:
+    print('  ' + url)
+print('MAX_CLIENTS={0}'.format(MAX_CLIENTS))
+
+# Run the test.
+factory = ClientFactory(COMMAND)
+clients = []
+try:
+  # Start us off with an initial batch of clients.
+  for index in xrange(MAX_CLIENTS):
+    clients.append(factory.new_client())
+  # Each time a client finishes, replace it with a new client.
+  # TODO(mdsteele): This is a busy loop, which isn't great.  What we want is to
+  #   sleep until one or more children are done.  Maybe we could do something
+  #   clever that would allow us to do a select() call here or something.
+  while True:
+    for index in xrange(MAX_CLIENTS):
+      if clients[index].check_done():
+        clients[index] = factory.new_client()
+# Stop when the user hits Ctrl-C, and print a summary of the results.
+except KeyboardInterrupt:
+  print()
+  if clients:
+    slowpoke = min(clients, key=(lambda c: c.get_key()))
+    print('Earliest unfinished client, {0}, not done after {1:.3f}s'.format(
+        slowpoke.get_key(), time.time() - slowpoke.get_start_time()))
+  if factory.num_finished > 0:
+    print('Avg time per client: {0:.3f}s ({1} started, {2} completed)'.format(
+        factory.total_duration / factory.num_finished,
+        factory.num_started, factory.num_finished))
+    print('Max time per client: {0:.3f}s'.format(factory.max_duration))
+    print("URLs served per second: {0:.3f}".format(
+        factory.num_finished * len(URLS) / factory.total_duration))
+for client in clients:
+  client.kill()
+
+#=============================================================================#

Propchange: httpd/mod_spdy/trunk/scripts/loadtest.py
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch (added)
+++ httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch Thu May  1 11:43:36 2014
@@ -0,0 +1,231 @@
+Index: modules/ssl/ssl_engine_init.c
+===================================================================
+--- modules/ssl/ssl_engine_init.c	(revision 1367982)
++++ modules/ssl/ssl_engine_init.c	(working copy)
+@@ -559,6 +559,11 @@
+     SSL_CTX_set_tmp_dh_callback(ctx,  ssl_callback_TmpDH);
+ 
+     SSL_CTX_set_info_callback(ctx, ssl_callback_Info);
++
++#ifdef HAVE_TLS_NPN
++    SSL_CTX_set_next_protos_advertised_cb(
++        ctx, ssl_callback_AdvertiseNextProtos, NULL);
++#endif
+ }
+ 
+ static void ssl_init_ctx_verify(server_rec *s,
+Index: modules/ssl/ssl_engine_io.c
+===================================================================
+--- modules/ssl/ssl_engine_io.c	(revision 1367982)
++++ modules/ssl/ssl_engine_io.c	(working copy)
+@@ -338,6 +338,7 @@
+     apr_pool_t *pool;
+     char buffer[AP_IOBUFSIZE];
+     ssl_filter_ctx_t *filter_ctx;
++    int npn_finished;  /* 1 if NPN has finished, 0 otherwise */
+ } bio_filter_in_ctx_t;
+ 
+ /*
+@@ -1409,6 +1410,27 @@
+         APR_BRIGADE_INSERT_TAIL(bb, bucket);
+     }
+ 
++#ifdef HAVE_TLS_NPN
++    /* By this point, Next Protocol Negotiation (NPN) should be completed (if
++     * our version of OpenSSL supports it).  If we haven't already, find out
++     * which protocol was decided upon and inform other modules by calling
++     * npn_proto_negotiated_hook. */
++    if (!inctx->npn_finished) {
++        const unsigned char *next_proto = NULL;
++        unsigned next_proto_len = 0;
++
++        SSL_get0_next_proto_negotiated(
++            inctx->ssl, &next_proto, &next_proto_len);
++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
++                      "SSL NPN negotiated protocol: '%s'",
++                      apr_pstrmemdup(f->c->pool, (const char*)next_proto,
++                                     next_proto_len));
++        modssl_run_npn_proto_negotiated_hook(
++            f->c, (const char*)next_proto, next_proto_len);
++        inctx->npn_finished = 1;
++    }
++#endif
++
+     return APR_SUCCESS;
+ }
+ 
+@@ -1753,6 +1775,7 @@
+     inctx->block = APR_BLOCK_READ;
+     inctx->pool = c->pool;
+     inctx->filter_ctx = filter_ctx;
++    inctx->npn_finished = 0;
+ }
+ 
+ void ssl_io_filter_init(conn_rec *c, SSL *ssl)
+Index: modules/ssl/ssl_engine_kernel.c
+===================================================================
+--- modules/ssl/ssl_engine_kernel.c	(revision 1367982)
++++ modules/ssl/ssl_engine_kernel.c	(working copy)
+@@ -2139,3 +2139,84 @@
+ }
+ 
+ #endif /* HAVE_SRP */
++
++#ifdef HAVE_TLS_NPN
++/*
++ * This callback function is executed when SSL needs to decide what protocols
++ * to advertise during Next Protocol Negotiation (NPN).  It must produce a
++ * string in wire format -- a sequence of length-prefixed strings -- indicating
++ * the advertised protocols.  Refer to SSL_CTX_set_next_protos_advertised_cb
++ * in OpenSSL for reference.
++ */
++int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data_out,
++                                     unsigned int *size_out, void *arg)
++{
++    conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
++    apr_array_header_t *protos;
++    int num_protos;
++    unsigned int size;
++    int i;
++    unsigned char *data;
++    unsigned char *start;
++
++    *data_out = NULL;
++    *size_out = 0;
++
++    /* If the connection object is not available, then there's nothing for us
++     * to do. */
++    if (c == NULL) {
++        return SSL_TLSEXT_ERR_OK;
++    }
++
++    /* Invoke our npn_advertise_protos hook, giving other modules a chance to
++     * add alternate protocol names to advertise. */
++    protos = apr_array_make(c->pool, 0, sizeof(char*));
++    modssl_run_npn_advertise_protos_hook(c, protos);
++    num_protos = protos->nelts;
++
++    /* We now have a list of null-terminated strings; we need to concatenate
++     * them together into a single string, where each protocol name is prefixed
++     * by its length.  First, calculate how long that string will be. */
++    size = 0;
++    for (i = 0; i < num_protos; ++i) {
++        const char *string = APR_ARRAY_IDX(protos, i, const char*);
++        unsigned int length = strlen(string);
++        /* If the protocol name is too long (the length must fit in one byte),
++         * then log an error and skip it. */
++        if (length > 255) {
++            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
++                          "SSL NPN protocol name too long (length=%u): %s",
++                          length, string);
++            continue;
++        }
++        /* Leave room for the length prefix (one byte) plus the protocol name
++         * itself. */
++        size += 1 + length;
++    }
++
++    /* If there is nothing to advertise (either because no modules added
++     * anything to the protos array, or because all strings added to the array
++     * were skipped), then we're done. */
++    if (size == 0) {
++        return SSL_TLSEXT_ERR_OK;
++    }
++
++    /* Now we can build the string.  Copy each protocol name string into the
++     * larger string, prefixed by its length. */
++    data = apr_palloc(c->pool, size * sizeof(unsigned char));
++    start = data;
++    for (i = 0; i < num_protos; ++i) {
++        const char *string = APR_ARRAY_IDX(protos, i, const char*);
++        apr_size_t length = strlen(string);
++        *start = (unsigned char)length;
++        ++start;
++        memcpy(start, string, length * sizeof(unsigned char));
++        start += length;
++    }
++
++    /* Success. */
++    *data_out = data;
++    *size_out = size;
++    return SSL_TLSEXT_ERR_OK;
++}
++#endif
+Index: modules/ssl/mod_ssl.c
+===================================================================
+--- modules/ssl/mod_ssl.c	(revision 1367982)
++++ modules/ssl/mod_ssl.c	(working copy)
+@@ -220,6 +220,18 @@
+     AP_END_CMD
+ };
+ 
++/* Implement 'modssl_run_npn_advertise_protos_hook'. */
++APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(
++    modssl, AP, int, npn_advertise_protos_hook,
++    (conn_rec *connection, apr_array_header_t *protos),
++    (connection, protos), OK, DECLINED);
++
++/* Implement 'modssl_run_npn_proto_negotiated_hook'. */
++APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(
++    modssl, AP, int, npn_proto_negotiated_hook,
++    (conn_rec *connection, const char *proto_name, apr_size_t proto_name_len),
++    (connection, proto_name, proto_name_len), OK, DECLINED);
++
+ /*
+  *  the various processing hooks
+  */
+Index: modules/ssl/mod_ssl.h
+===================================================================
+--- modules/ssl/mod_ssl.h	(revision 1367982)
++++ modules/ssl/mod_ssl.h	(working copy)
+@@ -60,5 +60,26 @@
+ 
+ APR_DECLARE_OPTIONAL_FN(apr_array_header_t *, ssl_extlist_by_oid, (request_rec *r, const char *oidstr));
+ 
++/** The npn_advertise_protos optional hook allows other modules to add entries
++ * to the list of protocol names advertised by the server during the Next
++ * Protocol Negotiation (NPN) portion of the SSL handshake.  The hook callee is
++ * given the connection and an APR array; it should push one or more char*'s
++ * pointing to null-terminated strings (such as "http/1.1" or "spdy/2") onto
++ * the array and return OK, or do nothing and return DECLINED. */
++APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_advertise_protos_hook,
++                          (conn_rec *connection, apr_array_header_t *protos));
++
++/** The npn_proto_negotiated optional hook allows other modules to discover the
++ * name of the protocol that was chosen during the Next Protocol Negotiation
++ * (NPN) portion of the SSL handshake.  Note that this may be the empty string
++ * (in which case modules should probably assume HTTP), or it may be a protocol
++ * that was never even advertised by the server.  The hook callee is given the
++ * connection, a non-null-terminated string containing the protocol name, and
++ * the length of the string; it should do something appropriate (i.e. insert or
++ * remove filters) and return OK, or do nothing and return DECLINED. */
++APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_proto_negotiated_hook,
++                          (conn_rec *connection, const char *proto_name,
++                           apr_size_t proto_name_len));
++
+ #endif /* __MOD_SSL_H__ */
+ /** @} */
+Index: modules/ssl/ssl_private.h
+===================================================================
+--- modules/ssl/ssl_private.h	(revision 1367982)
++++ modules/ssl/ssl_private.h	(working copy)
+@@ -146,6 +146,11 @@
+ #define HAVE_ECC
+ #endif
+ 
++#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_NEXTPROTONEG) \
++    && !defined(OPENSSL_NO_TLSEXT)
++#define HAVE_TLS_NPN
++#endif
++
+ /* OCSP stapling */
+ #if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTX_set_tlsext_status_cb)
+ #define HAVE_OCSP_STAPLING
+@@ -614,6 +619,7 @@
+     unsigned char aes_key[16];
+ } modssl_ticket_key_t;
+ #endif
++int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg);
+ 
+ typedef struct SSLSrvConfigRec SSLSrvConfigRec;
+

Added: httpd/mod_spdy/trunk/testing/OWNERS
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/testing/OWNERS?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/testing/OWNERS (added)
+++ httpd/mod_spdy/trunk/testing/OWNERS Thu May  1 11:43:36 2014
@@ -0,0 +1 @@
+*