From 819eaca09e4680c9fc5c26c5c5f82d3715430e6c Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Tue, 17 May 2016 18:05:00 -0700 Subject: [PATCH] Use codegen for json marshalling: 20% faster, 12% less bytes allocated, 85% less allocations --- command/agent/agent_test.go | 4 +-- command/agent/http.go | 21 +++++++++++---- command/agent/http_test.go | 54 ++++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index ae470bf62b44..a01b493918d2 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -18,7 +18,7 @@ func getPort() int { return int(atomic.AddUint32(&nextPort, 1)) } -func tmpDir(t *testing.T) string { +func tmpDir(t testing.TB) string { dir, err := ioutil.TempDir("", "nomad") if err != nil { t.Fatalf("err: %v", err) @@ -26,7 +26,7 @@ func tmpDir(t *testing.T) string { return dir } -func makeAgent(t *testing.T, cb func(*Config)) (string, *Agent) { +func makeAgent(t testing.TB, cb func(*Config)) (string, *Agent) { dir := tmpDir(t) conf := DevConfig() diff --git a/command/agent/http.go b/command/agent/http.go index bf2f95b78c5e..413f611dd3a1 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -1,6 +1,7 @@ package agent import ( + "bytes" "encoding/json" "fmt" "io" @@ -12,6 +13,7 @@ import ( "time" "github.com/hashicorp/nomad/nomad/structs" + "github.com/ugorji/go/codec" ) const ( @@ -25,6 +27,13 @@ const ( scadaHTTPAddr = "SCADA" ) +var ( + // jsonHandle and jsonHandlePretty are the codec handles to JSON encode + // structs. The pretty handle will add indents for easier human consumption. + jsonHandle = &codec.JsonHandle{} + jsonHandlePretty = &codec.JsonHandle{Indent: 4} +) + // HTTPServer is used to wrap an Agent and expose it over an HTTP interface type HTTPServer struct { agent *Agent @@ -183,20 +192,22 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque // Write out the JSON object if obj != nil { - var buf []byte + var buf bytes.Buffer if prettyPrint { - buf, err = json.MarshalIndent(obj, "", " ") + enc := codec.NewEncoder(&buf, jsonHandlePretty) + err = enc.Encode(obj) if err == nil { - buf = append(buf, "\n"...) + buf.Write([]byte("\n")) } } else { - buf, err = json.Marshal(obj) + enc := codec.NewEncoder(&buf, jsonHandle) + err = enc.Encode(obj) } if err != nil { goto HAS_ERR } resp.Header().Set("Content-Type", "application/json") - resp.Write(buf) + resp.Write(buf.Bytes()) } } return f diff --git a/command/agent/http_test.go b/command/agent/http_test.go index 634e7f68923d..aa71add72085 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -13,12 +13,13 @@ import ( "testing" "time" + "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" ) type TestServer struct { - T *testing.T + T testing.TB Dir string Agent *Agent Server *HTTPServer @@ -30,9 +31,25 @@ func (s *TestServer) Cleanup() { os.RemoveAll(s.Dir) } -func makeHTTPServer(t *testing.T, cb func(c *Config)) *TestServer { +// makeHTTPServerNoLogs returns a test server with full logging. +func makeHTTPServer(t testing.TB, cb func(c *Config)) *TestServer { + return makeHTTPServerWithWriter(t, nil, cb) +} + +// makeHTTPServerNoLogs returns a test server which only prints agent logs and +// no http server logs +func makeHTTPServerNoLogs(t testing.TB, cb func(c *Config)) *TestServer { + return makeHTTPServerWithWriter(t, ioutil.Discard, cb) +} + +// makeHTTPServerWithWriter returns a test server whose logs will be written to +// the passed writer. If the writer is nil, the logs are written to stderr. +func makeHTTPServerWithWriter(t testing.TB, w io.Writer, cb func(c *Config)) *TestServer { dir, agent := makeAgent(t, cb) - srv, err := NewHTTPServer(agent, agent.config, agent.logOutput) + if w == nil { + w = agent.logOutput + } + srv, err := NewHTTPServer(agent, agent.config, w) if err != nil { t.Fatalf("err: %v", err) } @@ -45,6 +62,37 @@ func makeHTTPServer(t *testing.T, cb func(c *Config)) *TestServer { return s } +func BenchmarkHTTPRequests(b *testing.B) { + s := makeHTTPServerNoLogs(b, func(c *Config) { + c.Client.Enabled = false + }) + defer s.Cleanup() + + job := mock.Job() + var allocs []*structs.Allocation + count := 1000 + for i := 0; i < count; i++ { + alloc := mock.Alloc() + alloc.Job = job + alloc.JobID = job.ID + alloc.Name = fmt.Sprintf("my-job.web[%d]", i) + allocs = append(allocs, alloc) + } + + handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + return allocs[:count], nil + } + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + resp := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/v1/kv/key", nil) + s.Server.wrap(handler)(resp, req) + } + }) +} + func TestSetIndex(t *testing.T) { resp := httptest.NewRecorder() setIndex(resp, 1000)