feat(stdiscosrv): make compression optional (and faster)

This commit is contained in:
Jakob Borg 2024-09-08 08:58:06 +02:00
parent ac8b3342ac
commit 68a1fd010f
No known key found for this signature in database
3 changed files with 103 additions and 4 deletions

View File

@ -45,7 +45,9 @@ type apiSrv struct {
listener net.Listener listener net.Listener
repl replicator // optional repl replicator // optional
useHTTP bool useHTTP bool
compression bool
missesIncrease int missesIncrease int
gzipWriters sync.Pool
mapsMut sync.Mutex mapsMut sync.Mutex
misses map[string]int32 misses map[string]int32
@ -61,13 +63,14 @@ type contextKey int
const idKey contextKey = iota const idKey contextKey = iota
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP bool, missesIncrease int) *apiSrv { func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, missesIncrease int) *apiSrv {
return &apiSrv{ return &apiSrv{
addr: addr, addr: addr,
cert: cert, cert: cert,
db: db, db: db,
repl: repl, repl: repl,
useHTTP: useHTTP, useHTTP: useHTTP,
compression: compression,
misses: make(map[string]int32), misses: make(map[string]int32),
missesIncrease: missesIncrease, missesIncrease: missesIncrease,
} }
@ -226,10 +229,16 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
var bw io.Writer = w var bw io.Writer = w
// Use compression if the client asks for it // Use compression if the client asks for it
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { if s.compression && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
gw, ok := s.gzipWriters.Get().(*gzip.Writer)
if ok {
gw.Reset(w)
} else {
gw = gzip.NewWriter(w)
}
w.Header().Set("Content-Encoding", "gzip") w.Header().Set("Content-Encoding", "gzip")
gw := gzip.NewWriter(bw)
defer gw.Close() defer gw.Close()
defer s.gzipWriters.Put(gw)
bw = gw bw = gw
} }

View File

@ -7,9 +7,19 @@
package main package main
import ( import (
"context"
"crypto/tls"
"fmt" "fmt"
"io"
"net" "net"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing" "testing"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/tlsutil"
) )
func TestFixupAddresses(t *testing.T) { func TestFixupAddresses(t *testing.T) {
@ -94,3 +104,81 @@ func addr(host string, port int) *net.TCPAddr {
Port: port, Port: port,
} }
} }
func BenchmarkAPIRequests(b *testing.B) {
db, err := newLevelDBStore(b.TempDir())
if err != nil {
b.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go db.Serve(ctx)
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1)
srv := httptest.NewServer(http.HandlerFunc(api.handler))
kf := b.TempDir() + "/cert"
crt, err := tlsutil.NewCertificate(kf+".crt", kf+".key", "localhost", 7)
if err != nil {
b.Fatal(err)
}
certBs, err := os.ReadFile(kf + ".crt")
if err != nil {
b.Fatal(err)
}
certString := string(strings.ReplaceAll(string(certBs), "\n", " "))
devID := protocol.NewDeviceID(crt.Certificate[0])
devIDString := devID.String()
b.Run("Announce", func(b *testing.B) {
b.ReportAllocs()
url := srv.URL + "/v2/?device=" + devIDString
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(`{"addresses":["tcp://10.10.10.10:42000"]}`))
req.Header.Set("X-Ssl-Cert", certString)
resp, err := http.DefaultClient.Do(req)
if err != nil {
b.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
b.Fatalf("unexpected status %s", resp.Status)
}
}
})
b.Run("Lookup", func(b *testing.B) {
b.ReportAllocs()
url := srv.URL + "/v2/?device=" + devIDString
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
b.Fatal(err)
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status %s", resp.Status)
}
}
})
b.Run("LookupNoCompression", func(b *testing.B) {
b.ReportAllocs()
url := srv.URL + "/v2/?device=" + devIDString
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.Header.Set("Accept-Encoding", "identity") // disable compression
resp, err := http.DefaultClient.Do(req)
if err != nil {
b.Fatal(err)
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status %s", resp.Status)
}
}
})
}

View File

@ -80,6 +80,7 @@ func main() {
var replCertFile string var replCertFile string
var replKeyFile string var replKeyFile string
var useHTTP bool var useHTTP bool
var compression bool
var largeDB bool var largeDB bool
var amqpAddress string var amqpAddress string
missesIncrease := 1 missesIncrease := 1
@ -92,6 +93,7 @@ func main() {
flag.StringVar(&dir, "db-dir", "./discovery.db", "Database directory") flag.StringVar(&dir, "db-dir", "./discovery.db", "Database directory")
flag.BoolVar(&debug, "debug", false, "Print debug output") flag.BoolVar(&debug, "debug", false, "Print debug output")
flag.BoolVar(&useHTTP, "http", false, "Listen on HTTP (behind an HTTPS proxy)") flag.BoolVar(&useHTTP, "http", false, "Listen on HTTP (behind an HTTPS proxy)")
flag.BoolVar(&compression, "compression", true, "Enable GZIP compression of responses")
flag.StringVar(&listen, "listen", ":8443", "Listen address") flag.StringVar(&listen, "listen", ":8443", "Listen address")
flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address") flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address")
flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated") flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated")
@ -225,7 +227,7 @@ func main() {
}() }()
// Start the main API server. // Start the main API server.
qs := newAPISrv(listen, cert, db, repl, useHTTP, missesIncrease) qs := newAPISrv(listen, cert, db, repl, useHTTP, compression, missesIncrease)
main.Add(qs) main.Add(qs)
// If we have a metrics port configured, start a metrics handler. // If we have a metrics port configured, start a metrics handler.