From 443cafab9091ae3d5da26150adf6e91818368ea5 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Tue, 12 Jan 2016 21:10:38 -0500 Subject: [PATCH] Add benchmarks for watch over websocket and http ... and a quick doc on how to run them ``` $ godep go test ./pkg/apiserver -benchmem -run=XXX -bench=BenchmarkWatch PASS BenchmarkWatchHTTP-8 20000 95669 ns/op 15053 B/op 196 allocs/op BenchmarkWatchWebsocket-8 10000 102871 ns/op 18430 B/op 204 allocs/op ``` --- docs/devel/development.md | 9 +++ pkg/apiserver/apiserver_test.go | 2 + pkg/apiserver/watch_test.go | 102 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/docs/devel/development.md b/docs/devel/development.md index 95dccaa930..06aa870a64 100644 --- a/docs/devel/development.md +++ b/docs/devel/development.md @@ -374,6 +374,15 @@ See [conformance-test.sh](http://releases.k8s.io/HEAD/hack/conformance-test.sh). [Instructions here](flaky-tests.md) +## Benchmarking + +To run benchmark tests, you'll typically use something like: + + $ godep go test ./pkg/apiserver -benchmem -run=XXX -bench=BenchmarkWatch + +The `-run=XXX` prevents normal unit tests for running, while `-bench` is a regexp for selecting which benchmarks to run. +See `go test -h` for more instructions on generating profiles from benchmarks. + ## Regenerating the CLI documentation ```sh diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 6141edd47e..c558ff5d7f 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -38,6 +38,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" @@ -158,6 +159,7 @@ func addNewTestTypes() { api.Scheme.AddKnownTypes(newGroupVersion, &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{}, &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) + api.Scheme.AddKnownTypes(newGroupVersion, &v1.Pod{}) } func init() { diff --git a/pkg/apiserver/watch_test.go b/pkg/apiserver/watch_test.go index 4ff824e000..7f5165afe8 100644 --- a/pkg/apiserver/watch_test.go +++ b/pkg/apiserver/watch_test.go @@ -19,16 +19,20 @@ package apiserver import ( "encoding/json" "io" + "io/ioutil" + "math/rand" "net/http" "net/http/httptest" "net/url" "reflect" + "sync" "testing" "time" "golang.org/x/net/websocket" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/rest" + apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing" "k8s.io/kubernetes/pkg/fields" @@ -424,3 +428,101 @@ func TestWatchHTTPTimeout(t *testing.T) { t.Errorf("Unexpected non-error") } } + +const benchmarkSeed = 100 + +func benchmarkItems() []api.Pod { + apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) + items := make([]api.Pod, 3) + for i := range items { + apiObjectFuzzer.Fuzz(&items[i]) + } + return items +} + +// BenchmarkWatchHTTP measures the cost of serving a watch. +func BenchmarkWatchHTTP(b *testing.B) { + items := benchmarkItems() + + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + b.Fatalf("Unexpected response %#v", response) + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer response.Body.Close() + if _, err := io.Copy(ioutil.Discard, response.Body); err != nil { + b.Fatal(err) + } + wg.Done() + }() + + actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)]) + } + simpleStorage.fakeWatch.Stop() + wg.Wait() + b.StopTimer() +} + +// BenchmarkWatchWebsocket measures the cost of serving a watch. +func BenchmarkWatchWebsocket(b *testing.B) { + items := benchmarkItems() + + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + + dest, _ := url.Parse(server.URL) + dest.Scheme = "ws" // Required by websocket, though the server never sees it. + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + ws, err := websocket.Dial(dest.String(), "", "http://localhost") + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer ws.Close() + if _, err := io.Copy(ioutil.Discard, ws); err != nil { + b.Fatal(err) + } + wg.Done() + }() + + actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)]) + } + simpleStorage.fakeWatch.Stop() + wg.Wait() + b.StopTimer() +}