Skip to content

Commit 819eaca

Browse files
committed
Use codegen for json marshalling: 20% faster, 12% less bytes allocated, 85% less allocations
1 parent 320a5cb commit 819eaca

File tree

3 files changed

+69
-10
lines changed

3 files changed

+69
-10
lines changed

command/agent/agent_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ func getPort() int {
1818
return int(atomic.AddUint32(&nextPort, 1))
1919
}
2020

21-
func tmpDir(t *testing.T) string {
21+
func tmpDir(t testing.TB) string {
2222
dir, err := ioutil.TempDir("", "nomad")
2323
if err != nil {
2424
t.Fatalf("err: %v", err)
2525
}
2626
return dir
2727
}
2828

29-
func makeAgent(t *testing.T, cb func(*Config)) (string, *Agent) {
29+
func makeAgent(t testing.TB, cb func(*Config)) (string, *Agent) {
3030
dir := tmpDir(t)
3131
conf := DevConfig()
3232

command/agent/http.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package agent
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"io"
@@ -12,6 +13,7 @@ import (
1213
"time"
1314

1415
"github.com/hashicorp/nomad/nomad/structs"
16+
"github.com/ugorji/go/codec"
1517
)
1618

1719
const (
@@ -25,6 +27,13 @@ const (
2527
scadaHTTPAddr = "SCADA"
2628
)
2729

30+
var (
31+
// jsonHandle and jsonHandlePretty are the codec handles to JSON encode
32+
// structs. The pretty handle will add indents for easier human consumption.
33+
jsonHandle = &codec.JsonHandle{}
34+
jsonHandlePretty = &codec.JsonHandle{Indent: 4}
35+
)
36+
2837
// HTTPServer is used to wrap an Agent and expose it over an HTTP interface
2938
type HTTPServer struct {
3039
agent *Agent
@@ -183,20 +192,22 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque
183192

184193
// Write out the JSON object
185194
if obj != nil {
186-
var buf []byte
195+
var buf bytes.Buffer
187196
if prettyPrint {
188-
buf, err = json.MarshalIndent(obj, "", " ")
197+
enc := codec.NewEncoder(&buf, jsonHandlePretty)
198+
err = enc.Encode(obj)
189199
if err == nil {
190-
buf = append(buf, "\n"...)
200+
buf.Write([]byte("\n"))
191201
}
192202
} else {
193-
buf, err = json.Marshal(obj)
203+
enc := codec.NewEncoder(&buf, jsonHandle)
204+
err = enc.Encode(obj)
194205
}
195206
if err != nil {
196207
goto HAS_ERR
197208
}
198209
resp.Header().Set("Content-Type", "application/json")
199-
resp.Write(buf)
210+
resp.Write(buf.Bytes())
200211
}
201212
}
202213
return f

command/agent/http_test.go

+51-3
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import (
1313
"testing"
1414
"time"
1515

16+
"github.com/hashicorp/nomad/nomad/mock"
1617
"github.com/hashicorp/nomad/nomad/structs"
1718
"github.com/hashicorp/nomad/testutil"
1819
)
1920

2021
type TestServer struct {
21-
T *testing.T
22+
T testing.TB
2223
Dir string
2324
Agent *Agent
2425
Server *HTTPServer
@@ -30,9 +31,25 @@ func (s *TestServer) Cleanup() {
3031
os.RemoveAll(s.Dir)
3132
}
3233

33-
func makeHTTPServer(t *testing.T, cb func(c *Config)) *TestServer {
34+
// makeHTTPServerNoLogs returns a test server with full logging.
35+
func makeHTTPServer(t testing.TB, cb func(c *Config)) *TestServer {
36+
return makeHTTPServerWithWriter(t, nil, cb)
37+
}
38+
39+
// makeHTTPServerNoLogs returns a test server which only prints agent logs and
40+
// no http server logs
41+
func makeHTTPServerNoLogs(t testing.TB, cb func(c *Config)) *TestServer {
42+
return makeHTTPServerWithWriter(t, ioutil.Discard, cb)
43+
}
44+
45+
// makeHTTPServerWithWriter returns a test server whose logs will be written to
46+
// the passed writer. If the writer is nil, the logs are written to stderr.
47+
func makeHTTPServerWithWriter(t testing.TB, w io.Writer, cb func(c *Config)) *TestServer {
3448
dir, agent := makeAgent(t, cb)
35-
srv, err := NewHTTPServer(agent, agent.config, agent.logOutput)
49+
if w == nil {
50+
w = agent.logOutput
51+
}
52+
srv, err := NewHTTPServer(agent, agent.config, w)
3653
if err != nil {
3754
t.Fatalf("err: %v", err)
3855
}
@@ -45,6 +62,37 @@ func makeHTTPServer(t *testing.T, cb func(c *Config)) *TestServer {
4562
return s
4663
}
4764

65+
func BenchmarkHTTPRequests(b *testing.B) {
66+
s := makeHTTPServerNoLogs(b, func(c *Config) {
67+
c.Client.Enabled = false
68+
})
69+
defer s.Cleanup()
70+
71+
job := mock.Job()
72+
var allocs []*structs.Allocation
73+
count := 1000
74+
for i := 0; i < count; i++ {
75+
alloc := mock.Alloc()
76+
alloc.Job = job
77+
alloc.JobID = job.ID
78+
alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
79+
allocs = append(allocs, alloc)
80+
}
81+
82+
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
83+
return allocs[:count], nil
84+
}
85+
b.ResetTimer()
86+
87+
b.RunParallel(func(pb *testing.PB) {
88+
for pb.Next() {
89+
resp := httptest.NewRecorder()
90+
req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
91+
s.Server.wrap(handler)(resp, req)
92+
}
93+
})
94+
}
95+
4896
func TestSetIndex(t *testing.T) {
4997
resp := httptest.NewRecorder()
5098
setIndex(resp, 1000)

0 commit comments

Comments
 (0)