diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go index 5f3e8c62f..0b2db0ebc 100644 --- a/pkg/app/client/client_test.go +++ b/pkg/app/client/client_test.go @@ -121,6 +121,49 @@ func TestCloseIdleConnections(t *testing.T) { } } +func BenchmarkCloseIdleConnections(b *testing.B) { + opt := config.NewOptions([]config.Option{}) + opt.Addr = "unix-test-10000" + opt.Network = "unix" + engine := route.NewEngine(opt) + + go engine.Run() + defer func() { + engine.Close() + }() + time.Sleep(time.Millisecond * 500) + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil))) + if _, _, err := c.Get(context.Background(), nil, "http://google.com"); err != nil { + b.Fatal(err) + } + connsLen := func() int { + c.mLock.Lock() + defer c.mLock.Unlock() + + if _, ok := c.m["google.com"]; !ok { + return 0 + } + return c.m["google.com"].ConnectionCount() + } + + if conns := connsLen(); conns > 1 { + b.Errorf("expected 1 conns got %d", conns) + } + + c.CloseIdleConnections() + + if conns := connsLen(); conns > 0 { + b.Errorf("expected 0 conns got %d", conns) + } + } + }) +} + func TestClientInvalidURI(t *testing.T) { t.Parallel() @@ -190,6 +233,45 @@ func TestClientGetWithBody(t *testing.T) { } } +func BenchmarkClientGetWithBody(b *testing.B) { + opt := config.NewOptions([]config.Option{}) + opt.Addr = "unix-test-10002" + opt.Network = "unix" + engine := route.NewEngine(opt) + engine.GET("/", func(c context.Context, ctx *app.RequestContext) { + body := ctx.Request.Body() + ctx.Write(body) //nolint:errcheck + }) + go engine.Run() + defer func() { + engine.Close() + }() + time.Sleep(time.Millisecond * 500) + + c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil))) + req, res := protocol.AcquireRequest(), protocol.AcquireResponse() + defer func() { + protocol.ReleaseRequest(req) + protocol.ReleaseResponse(res) + }() + req.Header.SetMethod(consts.MethodGet) + req.SetRequestURI("http://example.com") + req.SetBodyString("test") + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err := c.Do(context.Background(), req, res) + if err != nil { + b.Fatal(err) + } + if len(res.Body()) == 0 { + b.Fatal("missing request body") + } + res.Reset() + } +} + func TestClientPostBodyStream(t *testing.T) { t.Parallel() diff --git a/pkg/app/context_timing_test.go b/pkg/app/context_timing_test.go new file mode 100644 index 000000000..c95a948e1 --- /dev/null +++ b/pkg/app/context_timing_test.go @@ -0,0 +1,213 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "errors" + "io" + "testing" + + "github.com/cloudwego/hertz/pkg/common/test/assert" +) + +func BenchmarkNewContext(b *testing.B) { + for i := 0; i < b.N; i++ { + c := NewContext(0) + c.Reset() + } +} + +// go test -v -run=^$ -bench=BenchmarkCtxJSON -benchmem -count=4 +func BenchmarkCtxJSON(b *testing.B) { + ctx := NewContext(0) + defer ctx.Reset() + type SomeStruct struct { + Name string + Age uint8 + } + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + ctx.JSON(200, &data) + } +} + +func BenchmarkCtxString(b *testing.B) { + c := NewContext(0) + defer c.Reset() + s := "Hello, World!" + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.String(200, s) + } +} + +// go test -v -run=^$ -bench=BenchmarkCtxBody -benchmem -count=4 +func BenchmarkCtxBody(b *testing.B) { + ctx := NewContext(0) + defer ctx.Reset() + data := []byte("hello world") + ctx.Request.SetBodyRaw(data) + for n := 0; n < b.N; n++ { + _ = ctx.Request.Body() + } + assert.DeepEqual(b, data, ctx.Request.Body()) +} + +// go test -v -run=^$ -bench=BenchmarkCtxBodyWithCompression -benchmem -count=4 +func BenchmarkCtxBodyWithCompression(b *testing.B) { + encodingErr := errors.New("failed to encoding data") + var ( + compressGzip = func(data []byte) ([]byte, error) { + var buf bytes.Buffer + writer := gzip.NewWriter(&buf) + if _, err := writer.Write(data); err != nil { + return nil, encodingErr + } + if err := writer.Flush(); err != nil { + return nil, encodingErr + } + if err := writer.Close(); err != nil { + return nil, encodingErr + } + return buf.Bytes(), nil + } + compressDeflate = func(data []byte) ([]byte, error) { + var buf bytes.Buffer + writer := zlib.NewWriter(&buf) + if _, err := writer.Write(data); err != nil { + return nil, encodingErr + } + if err := writer.Flush(); err != nil { + return nil, encodingErr + } + if err := writer.Close(); err != nil { + return nil, encodingErr + } + return buf.Bytes(), nil + } + ) + compressionTests := []struct { + contentEncoding string + compressWriter func([]byte) ([]byte, error) + }{ + { + contentEncoding: "gzip", + compressWriter: compressGzip, + }, + { + contentEncoding: "gzip,invalid", + compressWriter: compressGzip, + }, + { + contentEncoding: "deflate", + compressWriter: compressDeflate, + }, + { + contentEncoding: "gzip,deflate", + compressWriter: func(data []byte) ([]byte, error) { + var ( + buf bytes.Buffer + writer interface { + io.WriteCloser + Flush() error + } + err error + ) + // deflate + { + writer = zlib.NewWriter(&buf) + if _, err = writer.Write(data); err != nil { + return nil, encodingErr + } + if err = writer.Flush(); err != nil { + return nil, encodingErr + } + if err = writer.Close(); err != nil { + return nil, encodingErr + } + } + + data = make([]byte, buf.Len()) + copy(data, buf.Bytes()) + buf.Reset() + + // gzip + { + writer = gzip.NewWriter(&buf) + if _, err = writer.Write(data); err != nil { + return nil, encodingErr + } + if err = writer.Flush(); err != nil { + return nil, encodingErr + } + if err = writer.Close(); err != nil { + return nil, encodingErr + } + } + + return buf.Bytes(), nil + }, + }, + } + + for _, ct := range compressionTests { + b.Run(ct.contentEncoding, func(b *testing.B) { + c := NewContext(0) + defer c.Reset() + const input = "john=doe" + + c.Request.Header.Set("Content-Encoding", ct.contentEncoding) + compressedBody, err := ct.compressWriter([]byte(input)) + assert.DeepEqual(b, nil, err) + + c.Request.SetBody(compressedBody) + for i := 0; i < b.N; i++ { + _ = c.Request.Body() + } + }) + } +} + +func BenchmarkCtxWrite(b *testing.B) { + c := NewContext(0) + byt := []byte("Hello, World!") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = c.Write(byt) + } +} + +func BenchmarkCtxWriteString(b *testing.B) { + c := NewContext(0) + defer c.Reset() + s := "Hello, World!" + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = c.WriteString(s) + } +} diff --git a/pkg/app/fs_test.go b/pkg/app/fs_test.go index c4ad0e426..c99ead772 100644 --- a/pkg/app/fs_test.go +++ b/pkg/app/fs_test.go @@ -86,6 +86,26 @@ func TestNewVHostPathRewriter(t *testing.T) { } } +func BenchmarkNewVHostPathRewriter(b *testing.B) { + var ctx RequestContext + var req protocol.Request + + b.ResetTimer() + for i := 0; i < b.N; i++ { + req.Header.SetHost("foobar.com") + req.SetRequestURI("https://aaa.bbb.cc/one/two/three/four?asdf=dsf") + req.CopyTo(&ctx.Request) + + f := NewVHostPathRewriter(2) + path := f(&ctx) + + expectedPath := "/aaa.bbb.cc/three/four" + assert.DeepEqual(b, expectedPath, string(path)) + ctx.Request.Reset() + req.Reset() + } +} + func TestNewVHostPathRewriterMaliciousHost(t *testing.T) { var ctx RequestContext var req protocol.Request @@ -295,6 +315,48 @@ func TestServeFileCompressed(t *testing.T) { } } +func BenchmarkServeFileHead(b *testing.B) { + var ctx RequestContext + var req protocol.Request + + req.Header.SetMethod(consts.MethodHead) + req.SetRequestURI("http://foobar.com/baz") + req.CopyTo(&ctx.Request) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ServeFile(&ctx, "fs.go") + + var r protocol.Response + r.SkipBody = true + s := resp.GetHTTP1Response(&ctx.Response).String() + zr := mock.NewZeroCopyReader(s) + if err := resp.Read(&r, zr); err != nil { + b.Fatalf("unexpected error: %s", err) + } + + ce := r.Header.ContentEncoding() + if len(ce) > 0 { + b.Fatalf("Unexpected 'Content-Encoding' %q", ce) + } + + body := r.Body() + if len(body) > 0 { + b.Fatalf("unexpected response body %q. Expecting empty body", body) + } + + expectedBody, err := getFileContents("/fs.go") + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + contentLength := r.Header.ContentLength() + if contentLength != len(expectedBody) { + b.Fatalf("unexpected Content-Length: %d. expecting %d", contentLength, len(expectedBody)) + } + } + b.ReportAllocs() +} + func TestServeFileUncompressed(t *testing.T) { t.Parallel() @@ -328,6 +390,42 @@ func TestServeFileUncompressed(t *testing.T) { } } +func BenchmarkServeFileUncompressed(b *testing.B) { + var ctx RequestContext + var req protocol.Request + + req.SetRequestURI("http://foobar.com/baz") + req.Header.Set(consts.HeaderAcceptEncoding, "gzip") + req.CopyTo(&ctx.Request) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ServeFileUncompressed(&ctx, "fs.go") + + var r protocol.Response + s := resp.GetHTTP1Response(&ctx.Response).String() + zr := mock.NewZeroCopyReader(s) + if err := resp.Read(&r, zr); err != nil { + b.Fatalf("unexpected error: %s", err) + } + + ce := r.Header.ContentEncoding() + if len(ce) > 0 { + b.Fatalf("Unexpected 'Content-Encoding' %q", ce) + } + + body := r.Body() + expectedBody, err := getFileContents("/fs.go") + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + if !bytes.Equal(body, expectedBody) { + b.Fatalf("unexpected body %q. expecting %q", body, expectedBody) + } + } +} + func TestFSByteRangeConcurrent(t *testing.T) { t.Parallel() @@ -462,6 +560,27 @@ func testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, } } +func BenchmarkParseByteRange(b *testing.B) { + f := func(b *testing.B, v string, contentLength, startPos, endPos int) { + startPos1, endPos1, err := ParseByteRange([]byte(v), contentLength) + if err != nil { + b.Fatalf("unexpected error: %s. v=%q, contentLength=%d", err, v, contentLength) + } + if startPos1 != startPos { + b.Fatalf("unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d", startPos1, startPos, v, contentLength) + } + if endPos1 != endPos { + b.Fatalf("unexpected endPos=%d. Expectind %d. v=%q, contentLength=%d", endPos1, endPos, v, contentLength) + } + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + f(b, "bytes=1234-6789", 6790, 1234, 6789) + } +} + func TestParseByteRangeError(t *testing.T) { t.Parallel() diff --git a/pkg/app/middlewares/client/sd/discovery_test.go b/pkg/app/middlewares/client/sd/discovery_test.go index a1c8c8d5f..86f96c830 100644 --- a/pkg/app/middlewares/client/sd/discovery_test.go +++ b/pkg/app/middlewares/client/sd/discovery_test.go @@ -55,3 +55,39 @@ func TestDiscovery(t *testing.T) { _ = mw(checkMdw)(context.Background(), req, resp) } } + +func BenchmarkDiscovery(b *testing.B) { + instances := []discovery.Instance{ + discovery.NewInstance("tcp", "127.0.0.1:8888", 10, nil), + discovery.NewInstance("tcp", "127.0.0.1:8889", 10, nil), + } + r := &discovery.SynthesizedResolver{ + TargetFunc: func(ctx context.Context, target *discovery.TargetInfo) string { + return target.Host + }, + ResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) { + return discovery.Result{CacheKey: "svc1", Instances: instances}, nil + }, + NameFunc: func() string { return b.Name() }, + } + + midware := Discovery(r) + checkMdw := func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) { + assert.Assert(b, string(req.Host()) == "127.0.0.1:8888" || string(req.Host()) == "127.0.0.1:8889") + return nil + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + req := protocol.AcquireRequest() + resp := protocol.AcquireResponse() + + req.Options().Apply([]config.RequestOption{config.WithSD(true)}) + req.SetRequestURI("http://service_name") + _ = midware(checkMdw)(context.Background(), req, resp) + + protocol.ReleaseRequest(req) + protocol.ReleaseResponse(resp) + } +} diff --git a/pkg/app/server/binding/reflect_internal_test.go b/pkg/app/server/binding/reflect_internal_test.go index 65dc68fc8..847cbe436 100644 --- a/pkg/app/server/binding/reflect_internal_test.go +++ b/pkg/app/server/binding/reflect_internal_test.go @@ -49,6 +49,17 @@ func Test_ReferenceValue(t *testing.T) { assert.DeepEqual(t, "f1", deFoo1PointerVal.Field(0).Interface().(string)) } +func BenchmarkReferenceValue(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + foo1 := foo2{F1: "f1"} + foo1Val := reflect.ValueOf(foo1) + decoder.ReferenceValue(foo1Val, 5) + } +} + func Test_GetNonNilReferenceValue(t *testing.T) { foo1 := (****foo)(nil) foo1Val := reflect.ValueOf(foo1) @@ -88,3 +99,19 @@ func Test_GetFieldValue(t *testing.T) { t.Errorf("expect can set value, but not") } } + +func BenchmarkGetFieldValue(b *testing.B) { + type bar struct { + B1 **fooq + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bar1 := (***bar)(nil) + parentIdx := []int{0} + + bar1Val := reflect.ValueOf(bar1) + decoder.GetFieldValue(bar1Val, parentIdx) + } +} diff --git a/pkg/app/server/render/html_test.go b/pkg/app/server/render/html_test.go index d474f1c63..e0a36978d 100644 --- a/pkg/app/server/render/html_test.go +++ b/pkg/app/server/render/html_test.go @@ -127,3 +127,25 @@ func TestRenderHTML(t *testing.T) { assert.DeepEqual(t, []byte("text/html; charset=utf-8"), respDebug.Header.Peek("Content-Type")) assert.DeepEqual(t, []byte("

Main website

"), respDebug.Body()) } + +func BenchmarkRenderHTML(b *testing.B) { + resp := &protocol.Response{} + + tmpl := template.Must(template.New(""). + Delims("{[{", "}]}"). + Funcs(template.FuncMap{}). + ParseFiles("../../../common/testdata/template/index.tmpl")) + + r := &HTMLProduction{Template: tmpl} + + html := r.Instance("index.tmpl", utils.H{ + "title": "Main website", + }) + html.WriteContentType(resp) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + html.Render(resp) + } +} diff --git a/pkg/app/server/render/json_test.go b/pkg/app/server/render/json_test.go index 2c2841b32..65cb6fe2a 100644 --- a/pkg/app/server/render/json_test.go +++ b/pkg/app/server/render/json_test.go @@ -74,3 +74,15 @@ func Test_DefaultJSONMarshal(t *testing.T) { t.Fatal("marshal struct is not equal to the string") } } + +func BenchmarkDefaultJSONMarshal(b *testing.B) { + table := map[string]string{ + "testA": "hello", + "B": "world", + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = jsonMarshalFunc(table) + } +} diff --git a/pkg/app/server/render/render_test.go b/pkg/app/server/render/render_test.go index 0669cb48c..25ddaf2be 100644 --- a/pkg/app/server/render/render_test.go +++ b/pkg/app/server/render/render_test.go @@ -93,6 +93,21 @@ func TestRenderJSON(t *testing.T) { assert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek("Content-Type")) } +func BenchmarkRenderJSON(b *testing.B) { + resp := &protocol.Response{} + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + err := (JSONRender{data}).Render(resp) + assert.Nil(b, err) + } +} + func TestRenderJSONError(t *testing.T) { resp := &protocol.Response{} data := make(chan int) @@ -120,6 +135,21 @@ func TestRenderPureJSON(t *testing.T) { assert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek("Content-Type")) } +func BenchmarkRenderPureJSON(b *testing.B) { + resp := &protocol.Response{} + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err := (PureJSON{data}).Render(resp) + assert.Nil(b, err) + } +} + func TestRenderPureJSONError(t *testing.T) { resp := &protocol.Response{} data := make(chan int) @@ -143,6 +173,18 @@ func TestRenderProtobuf(t *testing.T) { assert.DeepEqual(t, []byte("application/x-protobuf"), resp.Header.Peek("Content-Type")) } +func BenchmarkRenderProtobuf(b *testing.B) { + resp := &protocol.Response{} + data := proto.TestStruct{Body: []byte("Hello World")} + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err := (ProtoBuf{&data}).Render(resp) + assert.Nil(b, err) + } +} + func TestRenderProtobufError(t *testing.T) { resp := &protocol.Response{} data := proto.Test{} @@ -171,6 +213,17 @@ func TestRenderString(t *testing.T) { assert.DeepEqual(t, []byte(consts.MIMETextPlainUTF8), resp.Header.Peek("Content-Type")) } +func BenchmarkRenderString(b *testing.B) { + resp := &protocol.Response{} + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err := (String{Format: "hola %s %d", Data: []interface{}{"manu", 2}}).Render(resp) + assert.Nil(b, err) + } +} + func TestRenderStringLenZero(t *testing.T) { resp := &protocol.Response{} diff --git a/pkg/common/adaptor/request_test.go b/pkg/common/adaptor/request_test.go index ba3ec6ad3..cfa322866 100644 --- a/pkg/common/adaptor/request_test.go +++ b/pkg/common/adaptor/request_test.go @@ -68,6 +68,37 @@ func TestCompatResponse_WriteHeader(t *testing.T) { makeACall(t, http.MethodPost, testUrl2, testHeader, testBody, consts.StatusOK, []byte(testCookieValue)) } +func BenchmarkGetCompatRequest(b *testing.B) { + var req protocol.Request + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + req.SetMethod("GET") + req.SetRequestURI("127.0.0.1") + req.SetBody([]byte("foo.com")) + + GetCompatRequest(&req) + req.Reset() + } +} + +func BenchmarkGetCompatResponseWriter(b *testing.B) { + var resp protocol.Response + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + resp.SetBody([]byte("foo.com")) + resp.SetStatusCode(200) + resp.Header.Set("foo", "bzz") + GetCompatResponseWriter(&resp) + + resp.Reset() + } +} + func makeACall(t *testing.T, method, url string, header http.Header, body string, expectStatusCode int, expectCookieValue []byte) { client := http.Client{} req, _ := http.NewRequest(method, url, strings.NewReader(body)) diff --git a/pkg/common/compress/compress_test.go b/pkg/common/compress/compress_test.go index 301412441..5c814206a 100644 --- a/pkg/common/compress/compress_test.go +++ b/pkg/common/compress/compress_test.go @@ -102,6 +102,15 @@ func TestCompressAppendGzipBytesLevel(t *testing.T) { } } +func BenchmarkAppendGzipBytesLevel(b *testing.B) { + dst1 := []byte("") + src1 := []byte("hello") + b.ResetTimer() + for i := 0; i < b.N; i++ { + AppendGzipBytesLevel(dst1, src1, 5) + } +} + func TestCompressWriteGzipLevel(t *testing.T) { // test default case for WriteGzipLevel var w defaultByteWriter @@ -119,6 +128,16 @@ func TestCompressWriteGzipLevel(t *testing.T) { } } +func BenchmarkCompressWriteGzipLevel(b *testing.B) { + var w defaultByteWriter + p := []byte("hello") + b.ResetTimer() + for i := 0; i < b.N; i++ { + WriteGzipLevel(&w, p, 5) + w.Reset() + } +} + type defaultByteWriter struct { b []byte } @@ -127,3 +146,7 @@ func (w *defaultByteWriter) Write(p []byte) (int, error) { w.b = append(w.b, p...) return len(p), nil } + +func (w *defaultByteWriter) Reset() { + w.b = w.b[:0] +} diff --git a/pkg/common/timer/timer_test.go b/pkg/common/timer/timer_test.go index 21d77e648..d01dda423 100644 --- a/pkg/common/timer/timer_test.go +++ b/pkg/common/timer/timer_test.go @@ -101,3 +101,28 @@ func TestTimerReleaseTimer(t *testing.T) { t.Fatalf("Expecting the timer is released.") } } + +func BenchmarkAcquireTimer(b *testing.B) { + // run the AcquireTimer function b.N times + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + t := AcquireTimer(time.Second) + ReleaseTimer(t) // release the timer after acquiring it + } +} + +func BenchmarkReleaseTimer(b *testing.B) { + // create a slice of timers to be released + timers := make([]*time.Timer, b.N) + for i := 0; i < b.N; i++ { + timers[i] = AcquireTimer(time.Second) + } + + // run the ReleaseTimer function b.N times + b.ResetTimer() // reset the timer to exclude the time spent on acquiring timers + b.ReportAllocs() + for n := 0; n < b.N; n++ { + ReleaseTimer(timers[n]) + } +} diff --git a/pkg/common/utils/chunk_test.go b/pkg/common/utils/chunk_test.go index d9c8570b0..128a9a0e5 100644 --- a/pkg/common/utils/chunk_test.go +++ b/pkg/common/utils/chunk_test.go @@ -17,6 +17,7 @@ package utils import ( + "io" "testing" "github.com/cloudwego/hertz/pkg/common/test/assert" @@ -97,3 +98,105 @@ func TestChunkReadFalseCRLF(t *testing.T) { err := SkipCRLF(zr) assert.DeepEqual(t, errBrokenChunk, err) } + +// mockReader is a mock implementation of network.Reader interface +type mockReader struct { + data []byte + pos int +} + +func (r *mockReader) Release() error { + r.pos = 0 + r.data = r.data[:0] + return nil +} + +func (r *mockReader) Len() int { + return len(r.data) +} + +func (r *mockReader) ReadBinary(n int) (p []byte, err error) { + return +} + +func (r *mockReader) Read(p []byte) (int, error) { + if r.pos >= len(r.data) { + return 0, io.EOF + } + n := copy(p, r.data[r.pos:]) + r.pos += n + return n, nil +} + +func (r *mockReader) ReadByte() (byte, error) { + if r.pos >= len(r.data) { + return 0, io.EOF + } + b := r.data[r.pos] + r.pos++ + return b, nil +} + +func (r *mockReader) Peek(n int) ([]byte, error) { + if r.pos+n > len(r.data) { + return nil, io.EOF + } + return r.data[r.pos : r.pos+n], nil +} + +func (r *mockReader) Skip(n int) error { + if r.pos+n > len(r.data) { + return io.EOF + } + r.pos += n + return nil +} + +// BenchmarkParseChunkSize benchmarks the ParseChunkSize function with different inputs +func BenchmarkParseChunkSize(b *testing.B) { + // create a slice of mock readers with different chunk sizes + readers := []*mockReader{ + {data: []byte("1\r\n")}, + {data: []byte("10\r\n")}, + {data: []byte("100\r\n")}, + {data: []byte("1000\r\n")}, + {data: []byte("10000\r\n")}, + {data: []byte("100000\r\n")}, + {data: []byte("1000000\r\n")}, + } + + // run the ParseChunkSize function b.N times for each reader + for _, r := range readers { + b.Run(string(r.data), func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + ParseChunkSize(r) + r.pos = 0 // reset the reader position + } + }) + } +} + +// BenchmarkSkipCRLF benchmarks the SkipCRLF function with different inputs +func BenchmarkSkipCRLF(b *testing.B) { + // create a slice of mock readers with different data + readers := []*mockReader{ + {data: []byte("\r\n")}, + {data: []byte("foo\r\n")}, + {data: []byte("bar\r\n")}, + {data: []byte("baz\r\n")}, + } + + // run the SkipCRLF function b.N times for each reader + for _, r := range readers { + b.Run(string(r.data), func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + SkipCRLF(r) + r.pos = 0 // reset the reader position + } + }) + } +} diff --git a/pkg/common/utils/ioutil_test.go b/pkg/common/utils/ioutil_test.go index e9a573b47..86ced3843 100644 --- a/pkg/common/utils/ioutil_test.go +++ b/pkg/common/utils/ioutil_test.go @@ -95,7 +95,7 @@ func newTestReaderForm(r io.ReaderFrom) readerTest { func TestIoutilCopyBuffer(t *testing.T) { var writeBuffer bytes.Buffer - str := string("hertz is very good!!!") + str := "hertz is very good!!!" src := bytes.NewBufferString(str) dst := network.NewWriter(&writeBuffer) var buf []byte @@ -125,6 +125,22 @@ func TestIoutilCopyBuffer(t *testing.T) { assert.DeepEqual(t, []byte(str[:limit]), writeBuffer.Bytes()) } +func BenchmarkCopyBuffer(b *testing.B) { + var buf []byte + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + var writeBuffer bytes.Buffer + str := "hertz is very good!!!" + src := bytes.NewBufferString(str) + dst := network.NewWriter(&writeBuffer) + + CopyBuffer(dst, src, buf) + buf = buf[:0] + } +} + func TestIoutilCopyBufferWithIoWriter(t *testing.T) { var writeBuffer bytes.Buffer str := "hertz is very good!!!" @@ -234,6 +250,27 @@ func TestIoutilCopyZeroAlloc(t *testing.T) { assert.DeepEqual(t, []byte(""), writeBuffer.Bytes()) } +func BenchmarkCopyZeroAlloc(b *testing.B) { + var writeBuffer bytes.Buffer + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + str := "hertz is very good!!!" + src := bytes.NewBufferString(str) + dst := network.NewWriter(&writeBuffer) + srcLen := int64(src.Len()) + + written, err := CopyZeroAlloc(dst, src) + assert.DeepEqual(b, written, srcLen) + assert.DeepEqual(b, err, nil) + assert.DeepEqual(b, []byte(str), writeBuffer.Bytes()) + + writeBuffer.Reset() + dst.Flush() + } +} + func TestIoutilCopyBufferWithEmptyBuffer(t *testing.T) { var writeBuffer bytes.Buffer str := "hertz is very good!!!" diff --git a/pkg/common/utils/path_test.go b/pkg/common/utils/path_test.go index b98a8c747..bf03ed701 100644 --- a/pkg/common/utils/path_test.go +++ b/pkg/common/utils/path_test.go @@ -89,6 +89,28 @@ func TestPathCleanPath(t *testing.T) { assert.DeepEqual(t, expectedPath, cleanedPath) } +func BenchmarkCleanPath(b *testing.B) { + inputs := []string{ + "/path/to/some/directory", + "/path/../to/../some/directory", + "/a/b/c/../../d", + "/../a/b/c", + "/a/b/c/", + "", + } + + for _, input := range inputs { + b.Run(input, func(b *testing.B) { + // Run the CleanPath function b.N times + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = CleanPath(input) + } + }) + } +} + // The Function AddMissingPort can only add the missed port, don't consider the other error case. func TestPathAddMissingPort(t *testing.T) { ipList := []string{"127.0.0.1", "111.111.1.1", "[0:0:0:0:0:ffff:192.1.56.10]", "[0:0:0:0:0:ffff:c0a8:101]", "www.foobar.com"} diff --git a/pkg/common/utils/utils_test.go b/pkg/common/utils/utils_test.go index 92873b51d..2d6c6b036 100644 --- a/pkg/common/utils/utils_test.go +++ b/pkg/common/utils/utils_test.go @@ -129,7 +129,7 @@ func TestUtilsNextLine(t *testing.T) { singleHeaderStrWithFirstNewLine := []byte("\nContent-Type: application/x-www-form-urlencoded") firstStr, secondStr, sErr := NextLine(singleHeaderStrWithFirstNewLine) assert.DeepEqual(t, nil, sErr) - assert.DeepEqual(t, string(""), string(firstStr)) + assert.DeepEqual(t, "", string(firstStr)) assert.DeepEqual(t, "Content-Type: application/x-www-form-urlencoded", string(secondStr)) singleHeaderStr := []byte("Content-Type: application/x-www-form-urlencoded") diff --git a/pkg/network/writer_test.go b/pkg/network/writer_test.go index 4cf76831a..a68b13edf 100644 --- a/pkg/network/writer_test.go +++ b/pkg/network/writer_test.go @@ -86,6 +86,35 @@ func TestConvertNetworkWriter(t *testing.T) { assert.DeepEqual(t, size1K*14+1, iw.WriteNum) } +func BenchmarkMalloc1K(b *testing.B) { + iw := &mockIOWriter{} + w := NewWriter(iw) + nw, _ := w.(*networkWriter) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + buf, _ := nw.Malloc(size1K) + assert.DeepEqual(b, len(buf), size1K) + } +} + +func BenchmarkWriteBinary1K(b *testing.B) { + iw := &mockIOWriter{} + w := NewWriter(iw) + nw, _ := w.(*networkWriter) + + nw.Malloc(size1K * 6) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b := make([]byte, size1K) + nw.WriteBinary(b) + nw.Flush() + } +} + type mockIOWriter struct { WriteNum int } diff --git a/pkg/protocol/args_test.go b/pkg/protocol/args_test.go index 0e89f30cc..a8c6e54b4 100644 --- a/pkg/protocol/args_test.go +++ b/pkg/protocol/args_test.go @@ -61,6 +61,16 @@ func TestArgsDeleteAll(t *testing.T) { } } +func BenchmarkArgs_Add(b *testing.B) { + var a Args + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + a.Add("q1", "foo") + } +} + func TestArgsBytesOperation(t *testing.T) { var a Args a.Add("q1", "foo") @@ -71,6 +81,19 @@ func TestArgsBytesOperation(t *testing.T) { assert.DeepEqual(t, []byte(""), peekArgBytes(a.args, []byte("q2"))) } +func BenchmarkArgs_setArgBytes(b *testing.B) { + var a Args + a.Add("q1", "foo") + a.Add("q2", "bar") + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + setArgBytes(a.args, a.args[0].key, a.args[0].value, false) + setArgBytes(a.args, a.args[1].key, a.args[1].value, true) + } +} + func TestArgsPeekExists(t *testing.T) { var a Args a.Add("q1", "foo") @@ -90,6 +113,20 @@ func TestArgsPeekExists(t *testing.T) { assert.True(t, b4) } +func BenchmarkArgs_PeekExists(b *testing.B) { + var a Args + a.Add("q1", "foo") + a.Add("", "") + a.Add("?", "=") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.PeekExists("q1") + a.PeekExists("") + a.PeekExists("?") + } +} + func TestSetArg(t *testing.T) { a := Args{args: setArg(nil, "q1", "foo", true)} a.Add("", "") @@ -119,6 +156,21 @@ func TestArgsParseBytes(t *testing.T) { assert.DeepEqual(t, &ta2, &a2) } +func BenchmarkArgs_ParseBytes(b *testing.B) { + var ta1 Args + ta1.Add("q1", "foo") + ta1.Add("q1", "bar") + ta1.Add("q2", "123") + ta1.Add("q3", "") + var a1 Args + + b.ResetTimer() + for i := 0; i < b.N; i++ { + a1.ParseBytes([]byte("q1=foo&q1=bar&q2=123&q3=")) + a1.Reset() + } +} + func TestArgsVisitAll(t *testing.T) { var a Args var s []string @@ -130,6 +182,17 @@ func TestArgsVisitAll(t *testing.T) { assert.DeepEqual(t, []string{"cloudwego", "hertz", "hello", "world"}, s) } +func BenchmarkArgs_VisitAll(b *testing.B) { + var a Args + a.Add("cloudwego", "hertz") + a.Add("hello", "world") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.VisitAll(func(key, value []byte) {}) + } +} + func TestArgsPeekMulti(t *testing.T) { var a Args a.Add("cloudwego", "hertz") @@ -152,3 +215,16 @@ func TestArgsPeekMulti(t *testing.T) { expectedVV = [][]byte{[]byte("world")} assert.DeepEqual(t, expectedVV, vv) } + +func BenchmarkArgs_PeekAll(b *testing.B) { + var a Args + a.Add("cloudwego", "hertz") + a.Add("cloudwego", "kitex") + a.Add("cloudwego", "") + a.Add("hello", "world") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.PeekAll("cloudwego") + } +} diff --git a/pkg/protocol/cookie_test.go b/pkg/protocol/cookie_test.go index f7637e540..0d4c6bae8 100644 --- a/pkg/protocol/cookie_test.go +++ b/pkg/protocol/cookie_test.go @@ -78,6 +78,18 @@ func testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string } } +func BenchmarkCookieAppendBytes(b *testing.B) { + c := &Cookie{} + c.SetKey("xxx") + c.SetValue("yyy") + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + c.AppendBytes(nil) + } +} + func TestParseRequestCookies(t *testing.T) { t.Parallel() @@ -99,6 +111,16 @@ func testParseRequestCookies(t *testing.T, s, expectedS string) { } } +func BenchmarkParseRequestCookies(b *testing.B) { + s := "xxx=aa; bb=c; d; e=g" + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + parseRequestCookies(nil, []byte(s)) + } +} + func TestAppendRequestCookieBytes(t *testing.T) { t.Parallel() @@ -133,6 +155,22 @@ func testAppendRequestCookieBytes(t *testing.T, s, expectedS string) { } } +func BenchmarkAppendRequestCookieBytes(b *testing.B) { + cookies := make([]argsKV, 0) + c := argsKV{ + key: []byte("fff"), + value: []byte("yyy"), + } + cookies = append(cookies, c) + prefix := []byte("foobar") + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + appendRequestCookieBytes(prefix, cookies) + } +} + func TestCookieSecureHTTPOnly(t *testing.T) { t.Parallel() @@ -341,6 +379,16 @@ func TestCookieParse(t *testing.T) { "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b") } +func BenchmarkCookie_Parse(b *testing.B) { + var c Cookie + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = c.Parse(" xxx = yyy ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b") + } +} + func Test_decodeCookieArg(t *testing.T) { src := []byte(" \"aaaaabbbbb\" ") dst := make([]byte, 0) diff --git a/pkg/protocol/header_timing_test.go b/pkg/protocol/header_timing_test.go index 1f4897a1a..a740f0269 100644 --- a/pkg/protocol/header_timing_test.go +++ b/pkg/protocol/header_timing_test.go @@ -44,7 +44,11 @@ package protocol import ( "net/http" "strconv" + "strings" "testing" + + "github.com/cloudwego/hertz/pkg/common/test/assert" + "github.com/cloudwego/hertz/pkg/protocol/consts" ) func BenchmarkHTTPHeaderGet(b *testing.B) { @@ -104,3 +108,80 @@ func BenchmarkRefreshServerDate(b *testing.B) { refreshServerDate() } } + +func BenchmarkRequestHeaderCopyTo(b *testing.B) { + h := new(RequestHeader) + h.Add(consts.HeaderContentType, "aaa/bbb") + h.Add(consts.HeaderContentEncoding, "gzip") + h.Add(consts.HeaderConnection, "close") + h.Add(consts.HeaderContentLength, "1234") + h.Add(consts.HeaderServer, "aaaa") + h.Add(consts.HeaderSetCookie, "cccc") + reqHeader := new(RequestHeader) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + h.CopyTo(reqHeader) + } +} + +func BenchmarkResponseHeaderCopyTo(b *testing.B) { + h := new(ResponseHeader) + h.Add(consts.HeaderContentType, "aaa/bbb") + h.Add(consts.HeaderContentEncoding, "gzip") + h.Add(consts.HeaderConnection, "close") + h.Add(consts.HeaderContentLength, "1234") + h.Add(consts.HeaderServer, "aaaa") + h.Add(consts.HeaderSetCookie, "cccc") + respHeader := new(ResponseHeader) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + h.CopyTo(respHeader) + } +} + +func Benchmark_peekRawHeader(b *testing.B) { + s := "Expect: 100-continue\r\nUser-Agent: foo\r\nHost: 127.0.0.1\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" + b.ResetTimer() + for i := 0; i < b.N; i++ { + peekRawHeader([]byte(s), []byte("Host")) + } +} + +func BenchmarkResponseHeader_SetContentLength(b *testing.B) { + rh := new(ResponseHeader) + b.ResetTimer() + for i := 0; i < b.N; i++ { + rh.SetContentLength(-1) + assert.True(b, strings.Contains(string(rh.Header()), "Transfer-Encoding: chunked")) + rh.SetContentLength(-2) + assert.True(b, strings.Contains(string(rh.Header()), "Transfer-Encoding: identity")) + rh.Reset() + } +} + +func BenchmarkRequestHeaderVisitAll(b *testing.B) { + h := RequestHeader{} + h.Set("xxx", "yyy") + h.Set("xxx2", "yyy2") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + h.VisitAll(func(k, v []byte) { + key := string(k) + value := string(v) + if key != "Xxx" && key != "Xxx2" { + b.Fatalf("Unexpected %v. Expected %v", key, "xxx or yyy") + } + if key == "Xxx" && value != "yyy" { + b.Fatalf("Unexpected %v. Expected %v", value, "yyy") + } + if key == "Xxx2" && value != "yyy2" { + b.Fatalf("Unexpected %v. Expected %v", value, "yyy2") + } + }) + } +} diff --git a/pkg/protocol/http1/ext/common_test.go b/pkg/protocol/http1/ext/common_test.go index 9bc1936e3..eec41d127 100644 --- a/pkg/protocol/http1/ext/common_test.go +++ b/pkg/protocol/http1/ext/common_test.go @@ -123,6 +123,23 @@ func TestReadRawHeaders(t *testing.T) { assert.DeepEqual(t, s[:index], string(rawHeaders)) } +func BenchmarkReadRawHeaders(b *testing.B) { + s := "HTTP/1.1 200 OK\r\n" + + "EmptyValue1:\r\n" + + "Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" + + "Foo: Bar\r\n" + + "Multi-Line: one;\r\n two\r\n" + + "Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" + + "Content-Length: 5\r\n\r\n" + + "HELLOaaa" + + var dst []byte + for i := 0; i < b.N; i++ { + ReadRawHeaders(dst, []byte(s)) + dst = dst[:0] + } +} + func TestBodyChunked(t *testing.T) { var log bytes.Buffer hlog.SetOutput(&log) @@ -145,6 +162,21 @@ func TestBodyChunked(t *testing.T) { assert.DeepEqual(t, 0, log.Len()) } +func BenchmarkWriteBodyChunked(b *testing.B) { + var log bytes.Buffer + hlog.SetOutput(&log) + + body := "foobar baz aaa bbb ccc" + by := bytes.NewBufferString(body) + + var w bytes.Buffer + for i := 0; i < b.N; i++ { + zw := netpoll.NewWriter(&w) + WriteBodyChunked(zw, by) + w.Reset() + } +} + func TestBrokenBodyChunked(t *testing.T) { brokenReader := mock.NewBrokenConn("") var log bytes.Buffer @@ -175,6 +207,20 @@ func TestBodyFixedSize(t *testing.T) { assert.DeepEqual(t, body, rb) } +func BenchmarkWriteBodyFixedSize(b *testing.B) { + body := mock.CreateFixedBody(10) + by := bytes.NewBuffer(body) + + var w bytes.Buffer + zw := netpoll.NewWriter(&w) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + WriteBodyFixedSize(zw, by, int64(len(body))) + by.Reset() + } +} + func TestBodyFixedSizeQuickPath(t *testing.T) { conn := mock.NewBrokenConn("") err := WriteBodyFixedSize(conn.Writer(), conn, 0) diff --git a/pkg/protocol/http1/ext/headerscanner_test.go b/pkg/protocol/http1/ext/headerscanner_test.go index 0f8874d81..87bd0d8b0 100644 --- a/pkg/protocol/http1/ext/headerscanner_test.go +++ b/pkg/protocol/http1/ext/headerscanner_test.go @@ -112,3 +112,27 @@ func testTestHeaderScannerError(t *testing.T, rawHeaders string, expectError err assert.NotNil(t, hs.Err) assert.True(t, errors.Is(hs.Err, expectError)) } + +func BenchmarkHeaderScanner_Next(b *testing.B) { + firstLine := "HTTP/1.1 200 OK\r\n" + rawHeaders := "EmptyValue1:\r\n" + + "Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" + + "Foo: Bar\r\n" + + "Multi-Line: one;\r\n two\r\n" + + "Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" + + "\r\n" + + // compared with http response + response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstLine+rawHeaders)), nil) + assert.Nil(b, err) + defer func() { response.Body.Close() }() + + hs := &HeaderScanner{} + hs.B = []byte(rawHeaders) + hs.DisableNormalizing = false + b.ResetTimer() + for i := 0; i < b.N; i++ { + for hs.Next() { + } + } +} diff --git a/pkg/protocol/http1/req/header_test.go b/pkg/protocol/http1/req/header_test.go index d0978b3c4..1073ba0fa 100644 --- a/pkg/protocol/http1/req/header_test.go +++ b/pkg/protocol/http1/req/header_test.go @@ -82,6 +82,16 @@ func TestRequestHeader_Read(t *testing.T) { assert.DeepEqual(t, []byte("100-continue"), rh.Peek("Expect")) } +func BenchmarkRequestHeaderRead(b *testing.B) { + s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nUser-Agent: foo\r\nHost: 127.0.0.1\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" + zr := mock.NewZeroCopyReader(s) + rh := protocol.RequestHeader{} + for i := 0; i < b.N; i++ { + ReadHeader(&rh, zr) + rh.Reset() + } +} + func TestRequestHeaderMultiLineValue(t *testing.T) { s := "HTTP/1.1 200 OK\r\n" + "EmptyValue1:\r\n" + diff --git a/pkg/protocol/http1/req/request_test.go b/pkg/protocol/http1/req/request_test.go index 0411187a5..c130aae46 100644 --- a/pkg/protocol/http1/req/request_test.go +++ b/pkg/protocol/http1/req/request_test.go @@ -96,6 +96,21 @@ func TestRequestContinueReadBody(t *testing.T) { } } +func BenchmarkRequest_ContinueReadBody(b *testing.B) { + s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" + zr := mock.NewZeroCopyReader(s) + + var r protocol.Request + err := Read(&r, zr) + assert.Nil(b, err) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ContinueReadBody(&r, zr, 0, true) + } +} + func TestRequestReadNoBody(t *testing.T) { t.Parallel() @@ -136,6 +151,19 @@ func TestRequestRead(t *testing.T) { } } +func BenchmarkRequest_Read(b *testing.B) { + var r protocol.Request + + s := "POST / HTTP/1.1\r\n\r\n" + zr := mock.NewZeroCopyReader(s) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = Read(&r, zr) + } +} + func TestRequestReadNoBodyStreaming(t *testing.T) { t.Parallel() @@ -156,6 +184,21 @@ func TestRequestReadNoBodyStreaming(t *testing.T) { } } +func BenchmarkRequest_ContinueReadBodyStream(b *testing.B) { + var r protocol.Request + r.Header.SetContentLength(-2) + r.Header.SetMethod("GET") + s := "" + zr := mock.NewZeroCopyReader(s) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _ = ContinueReadBodyStream(&r, zr, 2048, true) + } +} + func TestRequestReadStreaming(t *testing.T) { t.Parallel() @@ -411,6 +454,20 @@ func TestChunkedUnexpectedEOF(t *testing.T) { } } +func BenchmarkExtReadBody(b *testing.B) { + body := mock.CreateFixedBody(3 * 1024 * 1024) + expectedTrailer := map[string]string{"Foo": "chunked shit"} + chunkedBody := mock.CreateChunkedBody(body, expectedTrailer, true) + + zr := mock.NewZeroCopyReader(string(chunkedBody)) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = ext.ReadBody(zr, -1, 0, nil) + } +} + func TestReadBodyChunked(t *testing.T) { t.Parallel() diff --git a/pkg/protocol/http1/resp/header_test.go b/pkg/protocol/http1/resp/header_test.go index 0fad02394..d70882b8e 100644 --- a/pkg/protocol/http1/resp/header_test.go +++ b/pkg/protocol/http1/resp/header_test.go @@ -43,6 +43,7 @@ package resp import ( "bytes" + "fmt" "testing" "github.com/cloudwego/hertz/pkg/protocol" @@ -179,3 +180,51 @@ func equalCookie(c1, c2 *protocol.Cookie) bool { } return true } + +func BenchmarkSetCookie(b *testing.B) { + var h protocol.ResponseHeader + var c protocol.Cookie + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.SetKey("foobar") + c.SetValue("aaa") + c.SetDomain("foobar.com") + + h.SetCookie(&c) + c.Reset() + } +} + +func BenchmarkDelCookie(b *testing.B) { + var h protocol.ResponseHeader + var c protocol.Cookie + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + c.SetKey("foobar") + c.SetValue("aaa") + h.SetCookie(&c) + + h.DelCookie("foobar") + } +} + +func BenchmarkVisitAllCookie(b *testing.B) { + var h protocol.ResponseHeader + var c protocol.Cookie + + for i := 0; i < b.N; i++ { + c.SetKey(fmt.Sprintf("foobar%v", i)) + c.SetValue("aaa") + c.SetDomain("foobar.com") + h.SetCookie(&c) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + h.VisitAllCookie(func(k, v []byte) {}) + } +} diff --git a/pkg/protocol/http1/resp/response_test.go b/pkg/protocol/http1/resp/response_test.go index 0ff010fd7..aa0edbf3a 100644 --- a/pkg/protocol/http1/resp/response_test.go +++ b/pkg/protocol/http1/resp/response_test.go @@ -687,6 +687,27 @@ func testSetResponseBodyStream(t *testing.T, body string) { } } +func BenchmarkResponseWrite(b *testing.B) { + body := string(mock.CreateFixedBody(100500)) + var resp protocol.Response + bodySize := len(body) + resp.SetBodyStream(bytes.NewBufferString(body), bodySize) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + var w bytes.Buffer + zw := netpoll.NewWriter(&w) + if err := Write(&resp, zw); err != nil { + b.Fatalf("unexpected error when writing response: %s. body=%q", err, body) + } + if err := zw.Flush(); err != nil { + b.Fatalf("unexpected error when flushing response: %s. body=%q", err, body) + } + resp.Reset() + } +} + func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) { var resp protocol.Response if resp.IsBodyStream() { @@ -821,8 +842,29 @@ func TestResponseReadBodyStreamBadTrailer(t *testing.T) { testResponseReadBodyStreamBadTrailer(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nproxy-connection: bar2\r\n\r\n") } +func BenchmarkReadBodyStream(b *testing.B) { + resp := &protocol.Response{} + zr := mock.NewZeroCopyReader("HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\ncontent-type: bar\r\n\r\n") + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ReadBodyStream(resp, zr, 0, nil) + } +} + func TestResponseString(t *testing.T) { resp := protocol.Response{} resp.Header.Set("Location", "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n") assert.True(t, strings.Contains(GetHTTP1Response(&resp).String(), "Location: foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n")) } + +func BenchmarkGetHTTP1Response_String(b *testing.B) { + resp := protocol.Response{} + resp.Header.Set("Location", "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n") + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetHTTP1Response(&resp).String() + } +} diff --git a/pkg/protocol/http1/resp/writer_test.go b/pkg/protocol/http1/resp/writer_test.go index b57281ae3..eca003939 100644 --- a/pkg/protocol/http1/resp/writer_test.go +++ b/pkg/protocol/http1/resp/writer_test.go @@ -64,3 +64,17 @@ func TestNewChunkedBodyWriterNoData(t *testing.T) { assert.True(t, strings.Contains(string(out), "Foo: Bar")) assert.True(t, strings.Contains(string(out), "0"+string(bytestr.StrCRLF)+string(bytestr.StrCRLF))) } + +func BenchmarkNewChunkedBodyWriter(b *testing.B) { + response := protocol.AcquireResponse() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + mockConn := mock.NewConn("") + w := NewChunkedBodyWriter(response, mockConn) + w.Write([]byte("hello")) + w.Finalize() + w.Flush() + mockConn.WriterRecorder().ReadBinary(mockConn.WriterRecorder().WroteLen()) + } +} diff --git a/pkg/protocol/multipart_test.go b/pkg/protocol/multipart_test.go index 6b96ae86f..c66055ef8 100644 --- a/pkg/protocol/multipart_test.go +++ b/pkg/protocol/multipart_test.go @@ -101,6 +101,32 @@ Content-Type: application/json } } +func BenchmarkWriteMultipartForm(b *testing.B) { + var w bytes.Buffer + s := strings.Replace(`--foo +Content-Disposition: form-data; name="key" + +value +--foo +Content-Disposition: form-data; name="file"; filename="test.json" +Content-Type: application/json + +{"foo": "bar"} +--foo-- +`, "\n", "\r\n", -1) + mr := multipart.NewReader(strings.NewReader(s), "foo") + form, err := mr.ReadForm(1024) + assert.Nil(b, err) + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + WriteMultipartForm(&w, form, s) + } + }) +} + func TestParseMultipartForm(t *testing.T) { t.Parallel() s := strings.Replace(`--foo @@ -138,6 +164,25 @@ value assert.NotNil(t, err) } +func BenchmarkParseMultipartForm(b *testing.B) { + s := strings.Replace(`--foo +Content-Disposition: form-data; name="key" + +value +--foo-- +`, "\n", "\r\n", -1) + req1 := Request{} + req1.SetMultipartFormBoundary("foo") + + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + ParseMultipartForm(strings.NewReader(s), &req1, 1024, 1024) + } + }) +} + func TestWriteMultipartFormFile(t *testing.T) { t.Parallel() bodyBuffer := &bytes.Buffer{} @@ -223,6 +268,30 @@ func TestWriteMultipartFormFile(t *testing.T) { assert.Nil(t, WriteMultipartFormFile(w, "empty_test", "test.data", bytes.NewBuffer(nil))) } +func BenchmarkWriteMultipartFormFile(b *testing.B) { + bodyBuffer := &bytes.Buffer{} + w := multipart.NewWriter(bodyBuffer) + + // read multipart.go to buf1 + f1, err := os.Open("./multipart.go") + if err != nil { + b.Fatalf("open file %s error: %s", f1.Name(), err) + } + defer f1.Close() + + multipartFile := File{ + Name: f1.Name(), + ParamName: "multipartCode", + Reader: f1, + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + WriteMultipartFormFile(w, multipartFile.ParamName, f1.Name(), multipartFile.Reader) + } +} + func TestMarshalMultipartForm(t *testing.T) { s := strings.Replace(`--foo Content-Disposition: form-data; name="key" @@ -249,6 +318,31 @@ Content-Type: application/json assert.NotNil(t, err) } +func BenchmarkMarshalMultipartForm(b *testing.B) { + s := strings.Replace(`--foo +Content-Disposition: form-data; name="key" + +value +--foo +Content-Disposition: form-data; name="file"; filename="test.json" +Content-Type: application/json + +{"foo": "bar"} +--foo-- +`, "\n", "\r\n", -1) + mr := multipart.NewReader(strings.NewReader(s), "foo") + form, err := mr.ReadForm(1024) + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + + b.ResetTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MarshalMultipartForm(form, "foo") + } +} + func TestAddFile(t *testing.T) { t.Parallel() bodyBuffer := &bytes.Buffer{} diff --git a/pkg/protocol/request_test.go b/pkg/protocol/request_test.go index fda96cd47..3a285aa4d 100644 --- a/pkg/protocol/request_test.go +++ b/pkg/protocol/request_test.go @@ -67,9 +67,18 @@ func (er errorReader) Read(p []byte) (int, error) { func TestMultiForm(t *testing.T) { var r Request - // r.Header.Set() _, err := r.MultipartForm() - fmt.Println(err) + assert.NotNil(t, err) +} + +func BenchmarkMultipartForm(b *testing.B) { + var r Request + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + r.MultipartForm() + } } func TestRequestBodyWriterWrite(t *testing.T) { @@ -78,6 +87,16 @@ func TestRequestBodyWriterWrite(t *testing.T) { assert.DeepEqual(t, "test", string(w.r.body.B)) } +func Benchmark_RequestBodyWriterWrite(b *testing.B) { + w := requestBodyWriter{&Request{}} + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + w.Write([]byte("test")) + } +} + func TestRequestScheme(t *testing.T) { req := NewRequest("", "ptth://127.0.0.1:8080", nil) assert.DeepEqual(t, "ptth", string(req.Scheme())) @@ -117,6 +136,19 @@ func TestRequestSwapBody(t *testing.T) { assert.DeepEqual(t, "testB", string(body)) } +func BenchmarkSwapRequestBody(b *testing.B) { + reqA := &Request{} + reqB := &Request{} + reqB.SetBodyRaw([]byte("testB")) + reqA.SetBodyRaw([]byte("testA")) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + SwapRequestBody(reqA, reqB) + } +} + func TestRequestKnownSizeStreamMultipartFormWithFile(t *testing.T) { t.Parallel() diff --git a/pkg/protocol/response_test.go b/pkg/protocol/response_test.go index 20a18ffce..d20609e80 100644 --- a/pkg/protocol/response_test.go +++ b/pkg/protocol/response_test.go @@ -92,6 +92,22 @@ func TestResponseBodyStreamMultipleBodyCalls(t *testing.T) { } } +func BenchmarkResponseBodyStreamMultipleBodyCalls(b *testing.B) { + var r Response + s := "foobar baz abc" + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + r.SetBodyStream(bytes.NewBufferString(s), len(s)) + body := r.Body() + if string(body) != s { + b.Fatalf("unexpected body %q. Expecting %q. iteration %d", body, s, i) + } + r.Reset() + } +} + func TestResponseBodyWriteToPlain(t *testing.T) { t.Parallel() @@ -202,6 +218,20 @@ func TestResponseBodyGunzip(t *testing.T) { assert.DeepEqual(t, zipData, src1) } +func BenchmarkResponse_BodyGunzip(b *testing.B) { + dst1 := []byte("") + src1 := []byte("hello") + res1 := compress.AppendGzipBytes(dst1, src1) + resp := Response{} + resp.SetBody(res1) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + resp.BodyGunzip() + } +} + func TestResponseSwapResponseBody(t *testing.T) { t.Parallel() resp1 := Response{} @@ -222,6 +252,28 @@ func TestResponseSwapResponseBody(t *testing.T) { assert.DeepEqual(t, resp2.BodyStream(), bytes.NewBufferString(str1)) } +func BenchmarkSwapResponseBody(b *testing.B) { + str1 := "resp1" + str2 := "resp2" + + byteBuffer1 := &bytebufferpool.ByteBuffer{} + byteBuffer2 := &bytebufferpool.ByteBuffer{} + resp1 := Response{} + resp2 := Response{} + + byteBuffer1.Set([]byte(str1)) + resp1.ConstructBodyStream(byteBuffer1, bytes.NewBufferString(str1)) + + byteBuffer2.Set([]byte(str2)) + resp2.ConstructBodyStream(byteBuffer2, bytes.NewBufferString(str2)) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + SwapResponseBody(&resp1, &resp2) + } +} + func TestResponseAcquireResponse(t *testing.T) { t.Parallel() resp1 := AcquireResponse() @@ -294,3 +346,20 @@ func TestResponse_HijackWriter(t *testing.T) { resp.GetHijackWriter().Finalize() assert.True(t, isFinal) } + +func BenchmarkResponse_HijackWriter(b *testing.B) { + buf := new(bytes.Buffer) + isFinal := false + for i := 0; i < b.N; i++ { + resp := AcquireResponse() + resp.HijackWriter(&mock.ExtWriter{Buf: buf, IsFinal: &isFinal}) + resp.AppendBody([]byte("hello")) + resp.GetHijackWriter().Flush() + resp.AppendBodyString(", world") + resp.GetHijackWriter().Flush() + resp.GetHijackWriter().Flush() + resp.GetHijackWriter().Finalize() + resp.Reset() + buf.Reset() + } +} diff --git a/pkg/protocol/trailer_test.go b/pkg/protocol/trailer_test.go index ced1cc26f..0d5cd27db 100644 --- a/pkg/protocol/trailer_test.go +++ b/pkg/protocol/trailer_test.go @@ -17,6 +17,7 @@ package protocol import ( + "fmt" "strings" "testing" @@ -36,6 +37,16 @@ func TestTrailerAdd(t *testing.T) { assert.True(t, strings.Contains(string(tr.Header()), "Bar: value3")) } +func BenchmarkTrailer_Add(b *testing.B) { + var tr Trailer + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tr.Add("bar", "value3") + } +} + func TestHeaderTrailerSet(t *testing.T) { h := &RequestHeader{} @@ -89,6 +100,18 @@ func TestTrailerDel(t *testing.T) { assert.True(t, strings.Contains(string(tr.Header()), "Bar: value3")) } +func BenchmarkTrailer_Del(b *testing.B) { + var tr Trailer + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tr.Add("foo", "value3") + tr.Del("foo") + tr.Reset() + } +} + func TestTrailerSet(t *testing.T) { var tr Trailer assert.Nil(t, tr.Set("foo", "value1")) @@ -99,6 +122,16 @@ func TestTrailerSet(t *testing.T) { assert.True(t, strings.Contains(string(tr.Header()), "Bar: value3")) } +func BenchmarkTrailer_Set(b *testing.B) { + var tr Trailer + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tr.Set(fmt.Sprintf("foo%v", i), "value1") + } +} + func TestTrailerGet(t *testing.T) { var tr Trailer assert.Nil(t, tr.Add("foo", "value1")) @@ -118,6 +151,17 @@ func TestTrailerUpdateArgBytes(t *testing.T) { assert.False(t, strings.Contains(string(tr.Header()), "Bar: value3")) } +func BenchmarkUpdateArgBytes(b *testing.B) { + var tr Trailer + tr.addArgBytes([]byte("Foo"), []byte("value0"), argsNoValue) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + tr.UpdateArgBytes([]byte("Foo"), []byte("value1")) + } +} + func TestTrailerEmpty(t *testing.T) { var tr Trailer assert.DeepEqual(t, tr.Empty(), true) diff --git a/pkg/protocol/uri_timing_test.go b/pkg/protocol/uri_timing_test.go index 950117a2d..5abab2290 100644 --- a/pkg/protocol/uri_timing_test.go +++ b/pkg/protocol/uri_timing_test.go @@ -81,6 +81,8 @@ func BenchmarkURIFullURI(b *testing.B) { func benchmarkURIParse(b *testing.B, host, uri string) { strHost, strURI := []byte(host), []byte(uri) + b.ResetTimer() + b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { var u URI for pb.Next() { diff --git a/pkg/route/routes_timing_test.go b/pkg/route/routes_timing_test.go index 4beb71d65..baabbd633 100644 --- a/pkg/route/routes_timing_test.go +++ b/pkg/route/routes_timing_test.go @@ -236,13 +236,8 @@ func BenchmarkTree_FindGithub(b *testing.B) { // OAuth Authorizations {"/authorizations"}, {"/authorizations/:id"}, - //{"/authorizations"}, - //{"/authorizations/clients/:client_id"}, - //{"/authorizations/:id"}, - //{"/authorizations/:id"}, {"/applications/:client_id/tokens/:access_token"}, {"/applications/:client_id/tokens"}, - //{"/applications/:client_id/tokens/:access_token"}, // Activity {"/events"}, @@ -255,55 +250,33 @@ func BenchmarkTree_FindGithub(b *testing.B) { {"/users/:user/events/public"}, {"/users/:user/events/orgs/:org"}, {"/feeds"}, - //{"/notifications"}, {"/repos/:owner/:repo/notifications"}, {"/notifications"}, - //{"/repos/:owner/:repo/notifications"}, {"/notifications/threads/:id"}, - //{"/notifications/threads/:id"}, {"/notifications/threads/:id/subscription"}, - //{"/notifications/threads/:id/subscription"}, - //{"/notifications/threads/:id/subscription"}, {"/repos/:owner/:repo/stargazers"}, {"/users/:user/starred"}, {"/user/starred"}, {"/user/starred/:owner/:repo"}, - //{"/user/starred/:owner/:repo"}, - //{"/user/starred/:owner/:repo"}, {"/repos/:owner/:repo/subscribers"}, {"/users/:user/subscriptions"}, {"/user/subscriptions"}, {"/repos/:owner/:repo/subscription"}, - //{"/repos/:owner/:repo/subscription"}, - //{"/repos/:owner/:repo/subscription"}, {"/user/subscriptions/:owner/:repo"}, - //{"PUT", "/user/subscriptions/:owner/:repo"}, - //{"DELETE", "/user/subscriptions/:owner/:repo"}, // Gists {"/users/:user/gists"}, {"/gists"}, - //{"GET", "/gists/public"}, - //{"GET", "/gists/starred"}, {"/gists/:id"}, - //{"POST", "/gists"}, - //{"PATCH", "/gists/:id"}, {"/gists/:id/star"}, - //{"DELETE", "/gists/:id/star"}, - //{"GET", "/gists/:id/star"}, {"/gists/:id/forks"}, - //{"DELETE", "/gists/:id"}, // Git Data {"/repos/:owner/:repo/git/blobs/:sha"}, {"/repos/:owner/:repo/git/blobs"}, {"/repos/:owner/:repo/git/commits/:sha"}, {"/repos/:owner/:repo/git/commits"}, - //{"GET", "/repos/:owner/:repo/git/refs/*ref"}, {"/repos/:owner/:repo/git/refs"}, - //{"POST", "/repos/:owner/:repo/git/refs"}, - //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"}, - //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"}, {"/repos/:owner/:repo/git/tags/:sha"}, {"/repos/:owner/:repo/git/tags"}, {"/repos/:owner/:repo/git/trees/:sha"}, @@ -314,35 +287,16 @@ func BenchmarkTree_FindGithub(b *testing.B) { {"/orgs/:org/issues"}, {"/repos/:owner/:repo/issues"}, {"/repos/:owner/:repo/issues/:number"}, - //{"POST", "/repos/:owner/:repo/issues"}, - //{"PATCH", "/repos/:owner/:repo/issues/:number"}, {"/repos/:owner/:repo/assignees"}, {"/repos/:owner/:repo/assignees/:assignee"}, {"/repos/:owner/:repo/issues/:number/comments"}, - //{"GET", "/repos/:owner/:repo/issues/comments"}, - //{"GET", "/repos/:owner/:repo/issues/comments/:id"}, - //{"POST", "/repos/:owner/:repo/issues/:number/comments"}, - //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"}, - //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"}, {"/repos/:owner/:repo/issues/:number/events"}, - //{"GET", "/repos/:owner/:repo/issues/events"}, - //{"GET", "/repos/:owner/:repo/issues/events/:id"}, {"/repos/:owner/:repo/labels"}, {"/repos/:owner/:repo/labels/:name"}, - //{"POST", "/repos/:owner/:repo/labels"}, - //{"PATCH", "/repos/:owner/:repo/labels/:name"}, - //{"DELETE", "/repos/:owner/:repo/labels/:name"}, {"/repos/:owner/:repo/issues/:number/labels"}, - //{"POST", "/repos/:owner/:repo/issues/:number/labels"}, - //{"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"}, - //{"PUT", "/repos/:owner/:repo/issues/:number/labels"}, - //{"DELETE", "/repos/:owner/:repo/issues/:number/labels"}, {"/repos/:owner/:repo/milestones/:number/labels"}, {"/repos/:owner/:repo/milestones"}, {"/repos/:owner/:repo/milestones/:number"}, - //{"POST", "/repos/:owner/:repo/milestones"}, - //{"PATCH", "/repos/:owner/:repo/milestones/:number"}, - //{"DELETE", "/repos/:owner/:repo/milestones/:number"}, // Miscellaneous {"/emojis"}, @@ -357,100 +311,55 @@ func BenchmarkTree_FindGithub(b *testing.B) { {"/users/:user/orgs"}, {"/user/orgs"}, {"/orgs/:org"}, - //{"PATCH", "/orgs/:org"}, {"/orgs/:org/members"}, {"/orgs/:org/members/:user"}, - //{"DELETE", "/orgs/:org/members/:user"}, {"/orgs/:org/public_members"}, {"/orgs/:org/public_members/:user"}, - //{"PUT", "/orgs/:org/public_members/:user"}, - //{"DELETE", "/orgs/:org/public_members/:user"}, {"/orgs/:org/teams"}, {"/teams/:id"}, - //{"POST", "/orgs/:org/teams"}, - //{"PATCH", "/teams/:id"}, - //{"DELETE", "/teams/:id"}, {"/teams/:id/members"}, {"/teams/:id/members/:user"}, - //{"PUT", "/teams/:id/members/:user"}, - //{"DELETE", "/teams/:id/members/:user"}, {"/teams/:id/repos"}, {"/teams/:id/repos/:owner/:repo"}, - //{"PUT", "/teams/:id/repos/:owner/:repo"}, - //{"DELETE", "/teams/:id/repos/:owner/:repo"}, {"/user/teams"}, // Pull Requests {"/repos/:owner/:repo/pulls"}, {"/repos/:owner/:repo/pulls/:number"}, - //{"POST", "/repos/:owner/:repo/pulls"}, - //{"PATCH", "/repos/:owner/:repo/pulls/:number"}, {"/repos/:owner/:repo/pulls/:number/commits"}, {"/repos/:owner/:repo/pulls/:number/files"}, {"/repos/:owner/:repo/pulls/:number/merge"}, - //{"PUT", "/repos/:owner/:repo/pulls/:number/merge"}, {"/repos/:owner/:repo/pulls/:number/comments"}, - //{"GET", "/repos/:owner/:repo/pulls/comments"}, - //{"GET", "/repos/:owner/:repo/pulls/comments/:number"}, - //{"PUT", "/repos/:owner/:repo/pulls/:number/comments"}, - //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"}, - //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"}, // Repositories {"/user/repos"}, {"/users/:user/repos"}, {"/orgs/:org/repos"}, {"/repositories"}, - //{"POST", "/user/repos"}, - //{"POST", "/orgs/:org/repos"}, {"/repos/:owner/:repo"}, - //{"PATCH", "/repos/:owner/:repo"}, {"/repos/:owner/:repo/contributors"}, {"/repos/:owner/:repo/languages"}, {"/repos/:owner/:repo/teams"}, {"/repos/:owner/:repo/tags"}, {"/repos/:owner/:repo/branches"}, {"/repos/:owner/:repo/branches/:branch"}, - //{"DELETE", "/repos/:owner/:repo"}, {"/repos/:owner/:repo/collaborators"}, {"/repos/:owner/:repo/collaborators/:user"}, - //{"PUT", "/repos/:owner/:repo/collaborators/:user"}, - //{"DELETE", "/repos/:owner/:repo/collaborators/:user"}, {"/repos/:owner/:repo/comments"}, {"/repos/:owner/:repo/commits/:sha/comments"}, - //{"POST", "/repos/:owner/:repo/commits/:sha/comments"}, {"/repos/:owner/:repo/comments/:id"}, - //{"PATCH", "/repos/:owner/:repo/comments/:id"}, - //{"DELETE", "/repos/:owner/:repo/comments/:id"}, {"/repos/:owner/:repo/commits"}, {"/repos/:owner/:repo/commits/:sha"}, {"/repos/:owner/:repo/readme"}, - //{"GET", "/repos/:owner/:repo/contents/*path"}, - //{"PUT", "/repos/:owner/:repo/contents/*path"}, - //{"DELETE", "/repos/:owner/:repo/contents/*path"}, - //{"GET", "/repos/:owner/:repo/:archive_format/:ref"}, {"/repos/:owner/:repo/keys"}, {"/repos/:owner/:repo/keys/:id"}, - //{"POST", "/repos/:owner/:repo/keys"}, - //{"PATCH", "/repos/:owner/:repo/keys/:id"}, - //{"DELETE", "/repos/:owner/:repo/keys/:id"}, {"/repos/:owner/:repo/downloads"}, {"/repos/:owner/:repo/downloads/:id"}, - //{"DELETE", "/repos/:owner/:repo/downloads/:id"}, {"/repos/:owner/:repo/forks"}, - //{"POST", "/repos/:owner/:repo/forks"}, {"/repos/:owner/:repo/hooks"}, {"/repos/:owner/:repo/hooks/:id"}, - //{"POST", "/repos/:owner/:repo/hooks"}, - //{"PATCH", "/repos/:owner/:repo/hooks/:id"}, - //{"POST", "/repos/:owner/:repo/hooks/:id/tests"}, - //{"DELETE", "/repos/:owner/:repo/hooks/:id"}, - //{"POST", "/repos/:owner/:repo/merges"}, {"/repos/:owner/:repo/releases"}, {"/repos/:owner/:repo/releases/:id"}, - //{"POST", "/repos/:owner/:repo/releases"}, - //{"PATCH", "/repos/:owner/:repo/releases/:id"}, - //{"DELETE", "/repos/:owner/:repo/releases/:id"}, {"/repos/:owner/:repo/releases/:id/assets"}, {"/repos/:owner/:repo/stats/contributors"}, {"/repos/:owner/:repo/stats/commit_activity"}, @@ -458,7 +367,6 @@ func BenchmarkTree_FindGithub(b *testing.B) { {"/repos/:owner/:repo/stats/participation"}, {"/repos/:owner/:repo/stats/punch_card"}, {"/repos/:owner/:repo/statuses/:ref"}, - //{"POST", "/repos/:owner/:repo/statuses/:ref"}, // Search {"/search/repositories"}, @@ -473,25 +381,17 @@ func BenchmarkTree_FindGithub(b *testing.B) { // Users {"/users/:user"}, {"/user"}, - //{"PATCH", "/user"}, {"/users"}, {"/user/emails"}, - //{"POST", "/user/emails"}, - //{"DELETE", "/user/emails"}, {"/users/:user/followers"}, {"/user/followers"}, {"/users/:user/following"}, {"/user/following"}, {"/user/following/:user"}, {"/users/:user/following/:target_user"}, - //{"PUT", "/user/following/:user"}, - //{"DELETE", "/user/following/:user"}, {"/users/:user/keys"}, {"/user/keys"}, {"/user/keys/:id"}, - //{"POST", "/user/keys"}, - //{"PATCH", "/user/keys/:id"}, - //{"DELETE", "/user/keys/:id"}, } for _, route := range static { @@ -619,40 +519,62 @@ func BenchmarkTree_FindAnyFallback(b *testing.B) { } func BenchmarkRouteStatic(b *testing.B) { - r := NewEngine(config.NewOptions(nil)) + cfg := []config.Option{ + { + F: func(o *config.Options) { + o.DisablePrintRoute = true + }, + }, + } + r := NewEngine(config.NewOptions(cfg)) r.GET("/hi/foo", func(c context.Context, ctx *app.RequestContext) {}) ctx := r.NewContext() req := protocol.NewRequest("GET", "/hi/foo", nil) - req.CopyTo(&ctx.Request) b.ResetTimer() for i := 0; i < b.N; i++ { + req.CopyTo(&ctx.Request) r.ServeHTTP(context.Background(), ctx) - // ctx.index = -1 + ctx.Reset() } } func BenchmarkRouteParam(b *testing.B) { - r := NewEngine(config.NewOptions(nil)) + cfg := []config.Option{ + { + F: func(o *config.Options) { + o.DisablePrintRoute = true + }, + }, + } + r := NewEngine(config.NewOptions(cfg)) r.GET("/hi/:user", func(c context.Context, ctx *app.RequestContext) {}) ctx := r.NewContext() req := protocol.NewRequest("GET", "/hi/foo", nil) - req.CopyTo(&ctx.Request) b.ResetTimer() for i := 0; i < b.N; i++ { + req.CopyTo(&ctx.Request) r.ServeHTTP(context.Background(), ctx) - // ctx.index = -1 + ctx.Reset() } } func BenchmarkRouteAny(b *testing.B) { - r := NewEngine(config.NewOptions(nil)) + cfg := []config.Option{ + { + F: func(o *config.Options) { + o.DisablePrintRoute = true + }, + }, + } + r := NewEngine(config.NewOptions(cfg)) r.GET("/hi/*user", func(c context.Context, ctx *app.RequestContext) {}) ctx := r.NewContext() req := protocol.NewRequest("GET", "/hi/foo/dy", nil) req.CopyTo(&ctx.Request) b.ResetTimer() for i := 0; i < b.N; i++ { + req.CopyTo(&ctx.Request) r.ServeHTTP(context.Background(), ctx) - // ctx.index = -1 + ctx.Reset() } }