git » gofer » commit 74da20b

proxy: Add benchmarks

author Alberto Bertogli
2017-08-26 01:03:02 UTC
committer Alberto Bertogli
2017-08-26 01:16:24 UTC
parent e08b0495890b4bd9057e78fb7ed9941b9952c88d

proxy: Add benchmarks

This patch adds two simple benchmark tests for the proxy, one for raw
mode and one for http mode.

proxy/proxy_test.go +102 -37

diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go
index a5eb438..74035ce 100644
--- a/proxy/proxy_test.go
+++ b/proxy/proxy_test.go
@@ -3,20 +3,60 @@ package proxy
 import (
 	"fmt"
 	"io/ioutil"
-	"log"
 	"net"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"strings"
 	"testing"
 	"time"
 
 	"blitiri.com.ar/go/gofer/config"
+	"blitiri.com.ar/go/log"
 )
 
+// WaitForHTTPServer waits 5 seconds for an HTTP server to start, and returns
+// an error if it fails to do so.
+// It does this by repeatedly querying the server until it either replies or
+// times out.
+func waitForHTTPServer(addr string) error {
+	c := http.Client{
+		Timeout: 100 * time.Millisecond,
+	}
+
+	deadline := time.Now().Add(5 * time.Second)
+	tick := time.Tick(100 * time.Millisecond)
+
+	for (<-tick).Before(deadline) {
+		_, err := c.Get("http://" + addr + "/testpoke")
+		if err == nil {
+			return nil
+		}
+	}
+
+	return fmt.Errorf("timed out")
+}
+
+// Get a free (TCP) port. This is hacky and not race-free, but it works well
+// enough for testing purposes.
+func getFreePort() string {
+	l, _ := net.Listen("tcp", "localhost:0")
+	defer l.Close()
+	return l.Addr().String()
+}
+
 const backendResponse = "backend response\n"
 
-func TestSimple(t *testing.T) {
+// Addresses of the proxy under test (created by TestMain).
+var (
+	httpAddr string
+	rawAddr  string
+)
+
+// startServer for testing. Returns raw addr, http addr, and the backend test
+// server (which should be closed afterwards).
+// Note it leaks goroutines, we're ok with this for testing.
+func TestMain(m *testing.M) {
 	backend := httptest.NewServer(
 		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			fmt.Fprint(w, backendResponse)
@@ -24,8 +64,10 @@ func TestSimple(t *testing.T) {
 	defer backend.Close()
 
 	// We have two frontends: one raw and one http.
-	rawAddr := getFreePort()
-	httpAddr := getFreePort()
+	rawAddr = getFreePort()
+	httpAddr = getFreePort()
+
+	log.Default.Level = log.Error
 
 	const configTemplate = `
 [[raw]]
@@ -48,10 +90,8 @@ addr = "$HTTP_ADDR"
 
 	conf, err := config.LoadString(configStr)
 	if err != nil {
-		log.Fatal(err)
+		log.Fatalf("error loading test config: %v", err)
 	}
-	t.Logf("conf.Raw[0]: %#v", conf.Raw[0])
-	t.Logf("conf.HTTP[0]: %#v", *conf.HTTP[0])
 
 	go Raw(conf.Raw[0])
 	go HTTP(*conf.HTTP[0])
@@ -59,6 +99,10 @@ addr = "$HTTP_ADDR"
 	waitForHTTPServer(httpAddr)
 	waitForHTTPServer(rawAddr)
 
+	os.Exit(m.Run())
+}
+
+func TestSimple(t *testing.T) {
 	// Test the raw proxy.
 	testGet(t, "http://"+rawAddr+"/be", 200)
 
@@ -78,6 +122,7 @@ addr = "$HTTP_ADDR"
 }
 
 func testGet(t *testing.T, url string, expectedStatus int) {
+	//t.Helper()  -- Uncomment once Go 1.9 is commonplace.
 	t.Logf("URL: %s", url)
 	resp, err := http.Get(url)
 	if err != nil {
@@ -106,36 +151,6 @@ func testGet(t *testing.T, url string, expectedStatus int) {
 	t.Logf("response body: %q", b)
 }
 
-// WaitForHTTPServer waits 5 seconds for an HTTP server to start, and returns
-// an error if it fails to do so.
-// It does this by repeatedly querying the server until it either replies or
-// times out.
-func waitForHTTPServer(addr string) error {
-	c := http.Client{
-		Timeout: 100 * time.Millisecond,
-	}
-
-	deadline := time.Now().Add(5 * time.Second)
-	tick := time.Tick(100 * time.Millisecond)
-
-	for (<-tick).Before(deadline) {
-		_, err := c.Get("http://" + addr + "/testpoke")
-		if err == nil {
-			return nil
-		}
-	}
-
-	return fmt.Errorf("timed out")
-}
-
-// Get a free (TCP) port. This is hacky and not race-free, but it works well
-// enough for testing purposes.
-func getFreePort() string {
-	l, _ := net.Listen("tcp", "localhost:0")
-	defer l.Close()
-	return l.Addr().String()
-}
-
 func TestJoinPath(t *testing.T) {
 	cases := []struct{ a, b, expected string }{
 		{"/a/", "", "/a/"},
@@ -155,3 +170,53 @@ func TestJoinPath(t *testing.T) {
 		}
 	}
 }
+
+func Benchmark(b *testing.B) {
+	makeBench := func(url string) func(b *testing.B) {
+		return func(b *testing.B) {
+			var resp *http.Response
+			var err error
+			for i := 0; i < b.N; i++ {
+				resp, err = http.Get(url)
+				if err != nil {
+					b.Fatal(err)
+				}
+				resp.Body.Close()
+				if resp.StatusCode != 200 {
+					b.Errorf("expected status 200, got %v", resp.Status)
+					b.Fatalf("response: %#v", resp)
+				}
+			}
+		}
+	}
+
+	b.Run("HTTP", makeBench("http://"+httpAddr+"/be"))
+	b.Run("Raw", makeBench("http://"+rawAddr+"/be"))
+}
+
+func BenchmarkParallel(b *testing.B) {
+	makeP := func(url string) func(pb *testing.PB) {
+		return func(pb *testing.PB) {
+			var resp *http.Response
+			var err error
+			for pb.Next() {
+				resp, err = http.Get(url)
+				if err != nil {
+					b.Fatal(err)
+				}
+				resp.Body.Close()
+				if resp.StatusCode != 200 {
+					b.Errorf("expected status 200, got %v", resp.Status)
+					b.Fatalf("response: %#v", resp)
+				}
+			}
+		}
+	}
+
+	b.Run("HTTP", func(b *testing.B) {
+		b.RunParallel(makeP("http://" + httpAddr + "/be"))
+	})
+	b.Run("Raw", func(b *testing.B) {
+		b.RunParallel(makeP("http://" + rawAddr + "/be"))
+	})
+}