Skip to content

Commit

Permalink
Use codegen for json marshalling: 20% faster, 12% less bytes allocate…
Browse files Browse the repository at this point in the history
…d, 85% less allocations
  • Loading branch information
dadgar committed May 18, 2016
1 parent 320a5cb commit 819eaca
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 10 deletions.
4 changes: 2 additions & 2 deletions command/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ 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)
}
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()

Expand Down
21 changes: 16 additions & 5 deletions command/agent/http.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package agent

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand All @@ -12,6 +13,7 @@ import (
"time"

"github.com/hashicorp/nomad/nomad/structs"
"github.com/ugorji/go/codec"
)

const (
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
54 changes: 51 additions & 3 deletions command/agent/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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)
Expand Down

0 comments on commit 819eaca

Please sign in to comment.