Skip to content

Instantly share code, notes, and snippets.

@rcrowley
Created February 28, 2014 02:01
Show Gist options
  • Save rcrowley/9263771 to your computer and use it in GitHub Desktop.
Save rcrowley/9263771 to your computer and use it in GitHub Desktop.

Benchmark of two graceful stop implementations

The server for each of these benchmarks is based on https://github.com/rcrowley/go-tigertonic/tree/master/example. It was run as ./example >/dev/null 2>/dev/null.

The client for each of these benchmarks is ab -H"Host: example.com" -c"100" -n"1000000" "http://127.0.0.1:8000/stuff/ID".

Baseline

As a baseline, I removed the chan struct{} and sync.WaitGroup from server.go and built the binary against Go tip.

Concurrency Level:      100
Time taken for tests:   235.452 seconds
Complete requests:      1000000
Failed requests:        0
Write errors:           0
Total transferred:      136000000 bytes
HTML transferred:       28000000 bytes
Requests per second:    4247.15 [#/sec] (mean)
Time per request:       23.545 [ms] (mean)
Time per request:       0.235 [ms] (mean, across all concurrent requests)
Transfer rate:          564.07 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.7      0      18
Processing:     0   23   8.5     21     252
Waiting:        0   23   8.5     21     252
Total:          0   24   8.5     22     252

Percentage of the requests served within a certain time (ms)
  50%     22
  66%     23
  75%     25
  80%     26
  90%     30
  95%     37
  98%     47
  99%     62
 100%    252 (longest request)

CL 67730046

This benchmark is of CL 67730046 that adds an int32 (accessed via sync/atomic) and a sync.WaitGroup to net/http and uses bufio.Buffer.Peek to preempt keepalive connections.

Concurrency Level:      100
Time taken for tests:   228.575 seconds
Complete requests:      1000000
Failed requests:        0
Write errors:           0
Total transferred:      136000000 bytes
HTML transferred:       28000000 bytes
Requests per second:    4374.94 [#/sec] (mean)
Time per request:       22.857 [ms] (mean)
Time per request:       0.229 [ms] (mean, across all concurrent requests)
Transfer rate:          581.05 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.6      0      17
Processing:     1   23   7.9     21     209
Waiting:        1   22   7.8     21     209
Total:          1   23   7.9     21     209

Percentage of the requests served within a certain time (ms)
  50%     21
  66%     23
  75%     24
  80%     25
  90%     28
  95%     34
  98%     45
  99%     57
 100%    209 (longest request)

CL 69260044

This benchmark is of CL 69260044 and uses http.Server.ConnState to manage a chan struct{}, sync.WaitGroup, and a map[string]net.Conn to preempt keepalive connections.

Concurrency Level:      100
Time taken for tests:   249.333 seconds
Complete requests:      1000000
Failed requests:        0
Write errors:           0
Total transferred:      136000000 bytes
HTML transferred:       28000000 bytes
Requests per second:    4010.70 [#/sec] (mean)
Time per request:       24.933 [ms] (mean)
Time per request:       0.249 [ms] (mean, across all concurrent requests)
Transfer rate:          532.67 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.8      0      70
Processing:     0   25   9.8     23     384
Waiting:        0   24   9.8     22     384
Total:          0   25   9.9     23     384

Percentage of the requests served within a certain time (ms)
  50%     23
  66%     25
  75%     26
  80%     27
  90%     31
  95%     40
  98%     51
  99%     65
 100%    384 (longest request)
package tigertonic
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"
"net/http"
)
// Server is an http.Server with better defaults.
type Server struct {
http.Server
listener net.Listener
}
// NewServer returns an http.Server with better defaults.
func NewServer(addr string, handler http.Handler) *Server {
s := &Server{
Server: http.Server{
Addr: addr,
Handler: &serverHandler{
Handler: handler,
//ch: ch,
},
MaxHeaderBytes: 4096,
ReadTimeout: 60e9, // These are absolute times which must be
WriteTimeout: 60e9, // longer than the longest {up,down}load.
},
}
return s
}
// NewTLSServer returns an http.Server with better defaults configured to use
// the certificate and private key files.
func NewTLSServer(
addr, cert, key string,
handler http.Handler,
) (*Server, error) {
s := NewServer(addr, handler)
return s, s.TLS(cert, key)
}
// CA overrides the certificate authority on the server's TLSConfig field.
func (s *Server) CA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.RootCAs = certPool
return nil
}
// ClientCA configures the CA pool for verifying client side certificates.
func (s *Server) ClientCA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
s.TLSConfig.ClientCAs = certPool
return nil
}
// Close closes the listener the server is using and signals open connections
// to close at their earliest convenience. Then it waits for all open
// connections to become closed.
func (s *Server) Close() error {
return s.Server.Close()
}
// ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if "" == addr {
if nil == s.TLSConfig {
addr = ":http"
} else {
addr = ":https"
}
}
l, err := net.Listen("tcp", addr)
if nil != err {
return err
}
if nil != s.TLSConfig {
l = tls.NewListener(l, s.TLSConfig)
}
return s.Serve(l)
}
// ListenAndServeTLS calls s.TLS with the given certificate and private key
// files and then calls s.ListenAndServe.
func (s *Server) ListenAndServeTLS(cert, key string) error {
s.TLS(cert, key)
return s.ListenAndServe()
}
// Serve behaves like http.Server.Serve with the added option to stop the
// server gracefully with the s.Close method.
func (s *Server) Serve(l net.Listener) error {
s.listener = l
return s.Server.Serve(s.listener)
}
// TLS configures this server to be a TLS server using the given certificate
// and private key files.
func (s *Server) TLS(cert, key string) error {
c, err := tls.LoadX509KeyPair(cert, key)
if nil != err {
return err
}
s.tlsConfig()
s.TLSConfig.Certificates = []tls.Certificate{c}
return nil
}
func (s *Server) tlsConfig() {
if nil == s.TLSConfig {
s.TLSConfig = &tls.Config{
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_RC4_128_SHA,
},
NextProtos: []string{"http/1.1"},
}
}
}
type serverHandler struct {
http.Handler
}
func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// r.Header.Set("Host", r.Host) // Should I?
r.URL.Host = r.Host
if nil != r.TLS {
r.URL.Scheme = "https"
} else {
r.URL.Scheme = "http"
}
h.Handler.ServeHTTP(w, r)
}
package tigertonic
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"
"net/http"
"sync"
)
// Server is an http.Server with better defaults.
type Server struct {
http.Server
ch chan<- struct{}
conns map[string]net.Conn
listener net.Listener
mu sync.Mutex // guards conns
waitGroup sync.WaitGroup
}
// NewServer returns an http.Server with better defaults.
func NewServer(addr string, handler http.Handler) *Server {
ch := make(chan struct{})
s := &Server{
Server: http.Server{
Addr: addr,
Handler: &serverHandler{
Handler: handler,
},
MaxHeaderBytes: 4096,
ReadTimeout: 60e9, // These are absolute times which must be
WriteTimeout: 60e9, // longer than the longest {up,down}load.
},
ch: ch,
conns: make(map[string]net.Conn),
}
s.ConnState = func(conn net.Conn, state http.ConnState) {
switch state {
case http.StateNew:
s.waitGroup.Add(1)
case http.StateActive:
s.mu.Lock()
delete(s.conns, conn.LocalAddr().String())
s.mu.Unlock()
case http.StateIdle:
select {
case <-ch:
conn.Close()
default:
s.mu.Lock()
s.conns[conn.LocalAddr().String()] = conn
s.mu.Unlock()
}
case http.StateHijacked, http.StateClosed:
s.waitGroup.Done()
}
}
return s
}
// NewTLSServer returns an http.Server with better defaults configured to use
// the certificate and private key files.
func NewTLSServer(
addr, cert, key string,
handler http.Handler,
) (*Server, error) {
s := NewServer(addr, handler)
return s, s.TLS(cert, key)
}
// CA overrides the certificate authority on the server's TLSConfig field.
func (s *Server) CA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.RootCAs = certPool
return nil
}
// ClientCA configures the CA pool for verifying client side certificates.
func (s *Server) ClientCA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
s.TLSConfig.ClientCAs = certPool
return nil
}
// Close closes the listener the server is using and signals open connections
// to close at their earliest convenience. Then it waits for all open
// connections to become closed.
func (s *Server) Close() error {
close(s.ch)
if err := s.listener.Close(); nil != err {
return err
}
s.mu.Lock()
for _, conn := range s.conns {
conn.Close()
}
s.mu.Unlock()
s.waitGroup.Wait()
return nil
}
// ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if "" == addr {
if nil == s.TLSConfig {
addr = ":http"
} else {
addr = ":https"
}
}
l, err := net.Listen("tcp", addr)
if nil != err {
return err
}
if nil != s.TLSConfig {
l = tls.NewListener(l, s.TLSConfig)
}
return s.Serve(l)
}
// ListenAndServeTLS calls s.TLS with the given certificate and private key
// files and then calls s.ListenAndServe.
func (s *Server) ListenAndServeTLS(cert, key string) error {
s.TLS(cert, key)
return s.ListenAndServe()
}
// Serve behaves like http.Server.Serve with the added option to stop the
// server gracefully with the s.Close method.
func (s *Server) Serve(l net.Listener) error {
s.listener = l
return s.Server.Serve(s.listener)
}
// TLS configures this server to be a TLS server using the given certificate
// and private key files.
func (s *Server) TLS(cert, key string) error {
c, err := tls.LoadX509KeyPair(cert, key)
if nil != err {
return err
}
s.tlsConfig()
s.TLSConfig.Certificates = []tls.Certificate{c}
return nil
}
func (s *Server) tlsConfig() {
if nil == s.TLSConfig {
s.TLSConfig = &tls.Config{
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_RC4_128_SHA,
},
NextProtos: []string{"http/1.1"},
}
}
}
type serverHandler struct {
http.Handler
}
func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// r.Header.Set("Host", r.Host) // Should I?
r.URL.Host = r.Host
if nil != r.TLS {
r.URL.Scheme = "https"
} else {
r.URL.Scheme = "http"
}
h.Handler.ServeHTTP(w, r)
}
package tigertonic
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"
"net/http"
)
// Server is an http.Server with better defaults.
type Server struct {
http.Server
listener net.Listener
}
// NewServer returns an http.Server with better defaults.
func NewServer(addr string, handler http.Handler) *Server {
s := &Server{
Server: http.Server{
Addr: addr,
Handler: &serverHandler{
Handler: handler,
},
MaxHeaderBytes: 4096,
ReadTimeout: 60e9, // These are absolute times which must be
WriteTimeout: 60e9, // longer than the longest {up,down}load.
},
}
return s
}
// NewTLSServer returns an http.Server with better defaults configured to use
// the certificate and private key files.
func NewTLSServer(
addr, cert, key string,
handler http.Handler,
) (*Server, error) {
s := NewServer(addr, handler)
return s, s.TLS(cert, key)
}
// CA overrides the certificate authority on the server's TLSConfig field.
func (s *Server) CA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.RootCAs = certPool
return nil
}
// ClientCA configures the CA pool for verifying client side certificates.
func (s *Server) ClientCA(ca string) error {
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(ca)
if nil != err {
return err
}
certPool.AppendCertsFromPEM(buf)
s.tlsConfig()
s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
s.TLSConfig.ClientCAs = certPool
return nil
}
// Close closes the listener the server is using and signals open connections
// to close at their earliest convenience. Then it waits for all open
// connections to become closed.
func (s *Server) Close() error {
return nil
}
// ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if "" == addr {
if nil == s.TLSConfig {
addr = ":http"
} else {
addr = ":https"
}
}
l, err := net.Listen("tcp", addr)
if nil != err {
return err
}
if nil != s.TLSConfig {
l = tls.NewListener(l, s.TLSConfig)
}
return s.Serve(l)
}
// ListenAndServeTLS calls s.TLS with the given certificate and private key
// files and then calls s.ListenAndServe.
func (s *Server) ListenAndServeTLS(cert, key string) error {
s.TLS(cert, key)
return s.ListenAndServe()
}
// Serve behaves like http.Server.Serve with the added option to stop the
// server gracefully with the s.Close method.
func (s *Server) Serve(l net.Listener) error {
s.listener = l
return s.Server.Serve(s.listener)
}
// TLS configures this server to be a TLS server using the given certificate
// and private key files.
func (s *Server) TLS(cert, key string) error {
c, err := tls.LoadX509KeyPair(cert, key)
if nil != err {
return err
}
s.tlsConfig()
s.TLSConfig.Certificates = []tls.Certificate{c}
return nil
}
func (s *Server) tlsConfig() {
if nil == s.TLSConfig {
s.TLSConfig = &tls.Config{
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_RC4_128_SHA,
},
NextProtos: []string{"http/1.1"},
}
}
}
type serverHandler struct {
http.Handler
//ch <-chan struct{}
}
func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// r.Header.Set("Host", r.Host) // Should I?
r.URL.Host = r.Host
if nil != r.TLS {
r.URL.Scheme = "https"
} else {
r.URL.Scheme = "http"
}
h.Handler.ServeHTTP(w, r)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment