Skip to content

Commit 4dd3cbb

Browse files
authoredJan 25, 2022
API client: make http reads more efficient (#976)
Replace `io.ReadAll` with `bytes.Buffer.ReadFrom`. Both need to resize a buffer until they have finished reading; the former increases by 1.25x each time while the latter uses 2x. Also added a benchmark to demonstrate the benefit: name old time/op new time/op delta Client/4KB-8 35.9µs ± 4% 35.3µs ± 3% ~ (p=0.310 n=5+5) Client/50KB-8 83.1µs ± 8% 69.5µs ± 1% -16.37% (p=0.008 n=5+5) Client/1000KB-8 891µs ± 6% 750µs ± 0% -15.83% (p=0.016 n=5+4) Client/2000KB-8 1.74ms ± 2% 1.35ms ± 1% -22.72% (p=0.008 n=5+5) name old alloc/op new alloc/op delta Client/4KB-8 20.2kB ± 0% 20.4kB ± 0% +1.26% (p=0.008 n=5+5) Client/50KB-8 218kB ± 0% 136kB ± 0% -37.65% (p=0.008 n=5+5) Client/1000KB-8 5.88MB ± 0% 2.11MB ± 0% -64.10% (p=0.008 n=5+5) Client/2000KB-8 11.7MB ± 0% 4.2MB ± 0% -63.93% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Client/4KB-8 75.0 ± 0% 72.0 ± 0% -4.00% (p=0.008 n=5+5) Client/50KB-8 109 ± 0% 98 ± 0% -10.09% (p=0.079 n=4+5) Client/1000KB-8 617 ± 0% 593 ± 0% -3.89% (p=0.008 n=5+5) Client/2000KB-8 1.13k ± 0% 1.09k ± 0% -3.27% (p=0.008 n=5+5) Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
1 parent 8520671 commit 4dd3cbb

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed
 

‎api/client.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
package api
1616

1717
import (
18+
"bytes"
1819
"context"
19-
"io/ioutil"
2020
"net"
2121
"net/http"
2222
"net/url"
@@ -111,7 +111,9 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
111111
var body []byte
112112
done := make(chan struct{})
113113
go func() {
114-
body, err = ioutil.ReadAll(resp.Body)
114+
var buf bytes.Buffer
115+
_, err = buf.ReadFrom(resp.Body)
116+
body = buf.Bytes()
115117
close(done)
116118
}()
117119

‎api/client_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
package api
1515

1616
import (
17+
"bytes"
18+
"context"
19+
"fmt"
1720
"net/http"
21+
"net/http/httptest"
1822
"net/url"
1923
"testing"
2024
)
@@ -111,3 +115,50 @@ func TestClientURL(t *testing.T) {
111115
}
112116
}
113117
}
118+
119+
// Serve any http request with a response of N KB of spaces.
120+
type serveSpaces struct {
121+
sizeKB int
122+
}
123+
124+
func (t serveSpaces) ServeHTTP(w http.ResponseWriter, req *http.Request) {
125+
kb := bytes.Repeat([]byte{' '}, 1024)
126+
for i := 0; i < t.sizeKB; i++ {
127+
w.Write(kb)
128+
}
129+
}
130+
131+
func BenchmarkClient(b *testing.B) {
132+
b.ReportAllocs()
133+
ctx := context.Background()
134+
135+
for _, sizeKB := range []int{4, 50, 1000, 2000} {
136+
b.Run(fmt.Sprintf("%dKB", sizeKB), func(b *testing.B) {
137+
138+
testServer := httptest.NewServer(serveSpaces{sizeKB})
139+
defer testServer.Close()
140+
141+
client, err := NewClient(Config{
142+
Address: testServer.URL,
143+
})
144+
if err != nil {
145+
b.Fatalf("Failed to initialize client: %v", err)
146+
}
147+
url, err := url.Parse(testServer.URL + "/prometheus/api/v1/query?query=up")
148+
if err != nil {
149+
b.Fatalf("Failed to parse url: %v", err)
150+
}
151+
req := &http.Request{
152+
URL: url,
153+
}
154+
b.ResetTimer()
155+
for i := 0; i < b.N; i++ {
156+
_, _, err := client.Do(ctx, req)
157+
if err != nil {
158+
b.Fatalf("Query failed: %v", err)
159+
}
160+
}
161+
b.StopTimer()
162+
})
163+
}
164+
}

0 commit comments

Comments
 (0)