Performance testing Load function

This commit is contained in:
Matthew Holt 2019-03-26 19:42:52 -06:00
parent 86e2d1b0a4
commit a8dc73b4d9
7 changed files with 132 additions and 12 deletions

View file

@ -1,6 +1,7 @@
package caddy2 package caddy2
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -8,8 +9,10 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"strings" "strings"
"sync" "sync"
"time"
) )
var ( var (
@ -30,6 +33,14 @@ func StartAdmin(addr string) error {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/load", handleLoadConfig) mux.HandleFunc("/load", handleLoadConfig)
///// BEGIN PPROF STUFF //////
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
///// END PPROF STUFF //////
for _, m := range GetModules("admin") { for _, m := range GetModules("admin") {
moduleValue, err := m.New() moduleValue, err := m.New()
if err != nil { if err != nil {
@ -40,7 +51,11 @@ func StartAdmin(addr string) error {
} }
cfgEndptSrv = &http.Server{ cfgEndptSrv = &http.Server{
Handler: mux, Handler: mux,
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
MaxHeaderBytes: 1024 * 256,
} }
go cfgEndptSrv.Serve(ln) go cfgEndptSrv.Serve(ln)
@ -74,6 +89,7 @@ type AdminRoute struct {
} }
func handleLoadConfig(w http.ResponseWriter, r *http.Request) { func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
r.Close = true
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return return
@ -94,14 +110,31 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
// Load loads and starts a configuration. // Load loads and starts a configuration.
func Load(r io.Reader) error { func Load(r io.Reader) error {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, io.LimitReader(r, 1024*1024))
if err != nil {
return err
}
var cfg Config var cfg Config
err := json.NewDecoder(r).Decode(&cfg) err = json.Unmarshal(buf.Bytes(), &cfg)
if err != nil { if err != nil {
return fmt.Errorf("decoding config: %v", err) return fmt.Errorf("decoding config: %v", err)
} }
err = Start(cfg) err = Start(cfg)
if err != nil { if err != nil {
return fmt.Errorf("starting: %v", err) return fmt.Errorf("starting: %v", err)
} }
return nil return nil
} }
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

30
admin_test.go Normal file
View file

@ -0,0 +1,30 @@
package caddy2
import (
"strings"
"testing"
)
func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
r := strings.NewReader(`{
"testval": "Yippee!",
"modules": {
"http": {
"servers": {
"myserver": {
"listen": ["tcp/localhost:8080-8084"],
"read_timeout": "30s"
},
"yourserver": {
"listen": ["127.0.0.1:5000"],
"read_header_timeout": "15s"
}
}
}
}
}
`)
Load(r)
}
}

View file

@ -3,10 +3,16 @@ package caddy2
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"runtime/debug"
"strings" "strings"
"sync"
"time" "time"
) )
var currentCfg *Config
var currentCfgMu sync.Mutex
// Start runs Caddy with the given config. // Start runs Caddy with the given config.
func Start(cfg Config) error { func Start(cfg Config) error {
cfg.runners = make(map[string]Runner) cfg.runners = make(map[string]Runner)
@ -26,16 +32,33 @@ func Start(cfg Config) error {
for name, r := range cfg.runners { for name, r := range cfg.runners {
err := r.Run() err := r.Run()
if err != nil { if err != nil {
// TODO: If any one has an error, stop the others
return fmt.Errorf("%s module: %v", name, err) return fmt.Errorf("%s module: %v", name, err)
} }
} }
currentCfgMu.Lock()
if currentCfg != nil {
for _, r := range cfg.runners {
err := r.Cancel()
if err != nil {
log.Println(err)
}
}
}
currentCfg = &cfg
currentCfgMu.Unlock()
// TODO: debugging memory leak...
debug.FreeOSMemory()
return nil return nil
} }
// Runner is a thing that Caddy runs. // Runner is a thing that Caddy runs.
type Runner interface { type Runner interface {
Run() error Run() error
Cancel() error
} }
// Config represents a Caddy configuration. // Config represents a Caddy configuration.

View file

@ -5,17 +5,19 @@ import (
"bitbucket.org/lightcodelabs/caddy2" "bitbucket.org/lightcodelabs/caddy2"
_ "net/http/pprof"
// this is where modules get plugged in // this is where modules get plugged in
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
_ "bitbucket.org/lightcodelabs/dynamicconfig" _ "bitbucket.org/lightcodelabs/dynamicconfig"
) )
func main() { func main() {
err := caddy2.Start("127.0.0.1:1234") err := caddy2.StartAdmin("127.0.0.1:1234")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer caddy2.Stop() defer caddy2.StopAdmin()
select {} select {}
} }

View file

@ -3,15 +3,29 @@ package caddy2
import ( import (
"fmt" "fmt"
"net" "net"
"sync"
"sync/atomic" "sync/atomic"
) )
// Listen returns a listener suitable for use in a Caddy module. // Listen returns a listener suitable for use in a Caddy module.
func Listen(proto, addr string) (net.Listener, error) { func Listen(proto, addr string) (net.Listener, error) {
lnKey := proto + "/" + addr
listenersMu.Lock()
defer listenersMu.Unlock()
// if listener already exists, return it
if ln, ok := listeners[lnKey]; ok {
return &fakeCloseListener{Listener: ln}, nil
}
// or, create new one and save it
ln, err := net.Listen(proto, addr) ln, err := net.Listen(proto, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
listeners[lnKey] = ln
return &fakeCloseListener{Listener: ln}, nil return &fakeCloseListener{Listener: ln}, nil
} }
@ -49,3 +63,8 @@ func (fcl *fakeCloseListener) CloseUnderlying() error {
// Close() is called, indicating that it is pretending to // Close() is called, indicating that it is pretending to
// be closed so that the server using it can terminate. // be closed so that the server using it can terminate.
var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉") var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉")
var (
listeners = make(map[string]net.Listener)
listenersMu sync.Mutex
)

View file

@ -1,6 +1,7 @@
package caddyhttp package caddyhttp
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"net" "net"
@ -24,13 +25,15 @@ func init() {
type httpModuleConfig struct { type httpModuleConfig struct {
Servers map[string]httpServerConfig `json:"servers"` Servers map[string]httpServerConfig `json:"servers"`
servers []*http.Server
} }
func (hc *httpModuleConfig) Run() error { func (hc *httpModuleConfig) Run() error {
fmt.Printf("RUNNING: %#v\n", hc) // fmt.Printf("RUNNING: %#v\n", hc)
for _, srv := range hc.Servers { for _, srv := range hc.Servers {
s := http.Server{ s := &http.Server{
ReadTimeout: time.Duration(srv.ReadTimeout), ReadTimeout: time.Duration(srv.ReadTimeout),
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout), ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
} }
@ -53,11 +56,21 @@ func (hc *httpModuleConfig) Run() error {
return nil return nil
} }
func (hc *httpModuleConfig) Cancel() error {
for _, s := range hc.servers {
err := s.Shutdown(context.Background()) // TODO
if err != nil {
return err
}
}
return nil
}
func parseListenAddr(a string) (proto string, addrs []string, err error) { func parseListenAddr(a string) (proto string, addrs []string, err error) {
proto = "tcp" proto = "tcp"
if idx := strings.Index(a, ":::"); idx >= 0 { if idx := strings.Index(a, "/"); idx >= 0 {
proto = strings.ToLower(strings.TrimSpace(a[:idx])) proto = strings.ToLower(strings.TrimSpace(a[:idx]))
a = a[idx+3:] a = a[idx+1:]
} }
var host, port string var host, port string
host, port, err = net.SplitHostPort(a) host, port, err = net.SplitHostPort(a)

View file

@ -28,22 +28,22 @@ func TestParseListenerAddr(t *testing.T) {
expectAddrs: []string{":1234"}, expectAddrs: []string{":1234"},
}, },
{ {
input: "tcp::::1234", input: "tcp/:1234",
expectProto: "tcp", expectProto: "tcp",
expectAddrs: []string{":1234"}, expectAddrs: []string{":1234"},
}, },
{ {
input: "tcp6::::1234", input: "tcp6/:1234",
expectProto: "tcp6", expectProto: "tcp6",
expectAddrs: []string{":1234"}, expectAddrs: []string{":1234"},
}, },
{ {
input: "tcp4:::localhost:1234", input: "tcp4/localhost:1234",
expectProto: "tcp4", expectProto: "tcp4",
expectAddrs: []string{"localhost:1234"}, expectAddrs: []string{"localhost:1234"},
}, },
{ {
input: "unix:::localhost:1234-1236", input: "unix/localhost:1234-1236",
expectProto: "unix", expectProto: "unix",
expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"}, expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"},
}, },