mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-07-14 12:50:14 +00:00
test(bench): add allocation benchmarks for client/transport/dispatch hot paths
Hermetic benchmarks (no network) covering Call encode+decode, webhook ServeHTTP body parse, and Router dispatch (command/regex/filter). Use Go 1.24+ b.Loop() idiom. .benchstats/baseline.txt pins the pre-optimisation numbers for benchstat comparisons.
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
pkg: github.com/lukaszraczylo/go-telegram/client
|
||||||
|
cpu: Apple M4 Max
|
||||||
|
BenchmarkCall_BoolResponse-16 1875306 633.9 ns/op 1957 B/op 18 allocs/op
|
||||||
|
BenchmarkCall_StructResponse-16 1805024 665.2 ns/op 2005 B/op 18 allocs/op
|
||||||
|
BenchmarkEncodeJSONBody-16 23345811 51.55 ns/op 96 B/op 2 allocs/op
|
||||||
|
BenchmarkDecodeResult_Bool-16 23832240 50.37 ns/op 80 B/op 2 allocs/op
|
||||||
|
BenchmarkDecodeResult_Struct-16 13511192 92.64 ns/op 144 B/op 2 allocs/op
|
||||||
|
|
||||||
|
pkg: github.com/lukaszraczylo/go-telegram/transport
|
||||||
|
BenchmarkWebhook_ServeHTTP-16 465798 2564 ns/op 12707 B/op 24 allocs/op
|
||||||
|
|
||||||
|
pkg: github.com/lukaszraczylo/go-telegram/dispatch
|
||||||
|
BenchmarkRouter_DispatchCommand-16 7303522 152.7 ns/op 416 B/op 5 allocs/op
|
||||||
|
BenchmarkRouter_DispatchTextRegex-16 6740305 180.5 ns/op 428 B/op 5 allocs/op
|
||||||
|
BenchmarkRouter_DispatchFilter-16 39479149 32.18 ns/op 96 B/op 2 allocs/op
|
||||||
|
BenchmarkRouter_NewContext-16 208260764 5.790 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkExtractCommand-16 12988816 92.69 ns/op 0 B/op 0 allocs/op
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stubDoer returns the same canned response body for every request. It
|
||||||
|
// is intentionally minimal — testify mock has its own overhead that
|
||||||
|
// would dominate the per-call cost we want to measure.
|
||||||
|
type stubDoer struct{ body []byte }
|
||||||
|
|
||||||
|
func (s *stubDoer) Do(_ *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewReader(s.body)),
|
||||||
|
Header: http.Header{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type benchSendReq struct {
|
||||||
|
ChatID int64 `json:"chat_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type benchMsgResp struct {
|
||||||
|
MessageID int64 `json:"message_id"`
|
||||||
|
Date int64 `json:"date"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCall_BoolResponse(b *testing.B) {
|
||||||
|
d := &stubDoer{body: []byte(`{"ok":true,"result":true}`)}
|
||||||
|
bot := New("123:abc", WithHTTPClient(d))
|
||||||
|
ctx := context.Background()
|
||||||
|
req := &benchSendReq{ChatID: 42, Text: "hi"}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
if _, err := Call[*benchSendReq, bool](ctx, bot, "setMyCommands", req); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCall_StructResponse(b *testing.B) {
|
||||||
|
d := &stubDoer{body: []byte(`{"ok":true,"result":{"message_id":1,"date":0,"text":"ok"}}`)}
|
||||||
|
bot := New("123:abc", WithHTTPClient(d))
|
||||||
|
ctx := context.Background()
|
||||||
|
req := &benchSendReq{ChatID: 42, Text: "hi"}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
if _, err := Call[*benchSendReq, benchMsgResp](ctx, bot, "sendMessage", req); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeJSONBody(b *testing.B) {
|
||||||
|
codec := DefaultCodec{}
|
||||||
|
req := &benchSendReq{ChatID: 42, Text: "hello, world"}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
r, err := encodeJSONBody(codec, req)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeResult_Bool(b *testing.B) {
|
||||||
|
codec := DefaultCodec{}
|
||||||
|
raw := []byte(`{"ok":true,"result":true}`)
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
if _, err := decodeResult[bool](codec, raw); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeResult_Struct(b *testing.B) {
|
||||||
|
codec := DefaultCodec{}
|
||||||
|
raw := []byte(`{"ok":true,"result":{"message_id":1,"date":0,"text":"ok"}}`)
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
if _, err := decodeResult[benchMsgResp](codec, raw); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package dispatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lukaszraczylo/go-telegram/api"
|
||||||
|
"github.com/lukaszraczylo/go-telegram/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkRouter_DispatchCommand(b *testing.B) {
|
||||||
|
r := New(client.New("t"))
|
||||||
|
r.OnCommand("/start", func(c *Context, m *api.Message) error { return nil })
|
||||||
|
u := cmdMessage("/start hello")
|
||||||
|
ctx := context.Background()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
c := NewContext(ctx, r.bot, &u)
|
||||||
|
_ = r.dispatch(c, &u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRouter_DispatchTextRegex(b *testing.B) {
|
||||||
|
r := New(client.New("t"))
|
||||||
|
r.OnText("^hello.*", func(c *Context, m *api.Message) error { return nil })
|
||||||
|
u := api.Update{
|
||||||
|
UpdateID: 1,
|
||||||
|
Message: &api.Message{
|
||||||
|
MessageID: 1, Date: 0,
|
||||||
|
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||||
|
Text: "hello world",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
c := NewContext(ctx, r.bot, &u)
|
||||||
|
_ = r.dispatch(c, &u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRouter_DispatchFilter(b *testing.B) {
|
||||||
|
r := New(client.New("t"))
|
||||||
|
r.OnMessageFilter(
|
||||||
|
func(m *api.Message) bool { return m != nil && m.Text == "ping" },
|
||||||
|
func(c *Context, m *api.Message) error { return nil },
|
||||||
|
)
|
||||||
|
u := api.Update{
|
||||||
|
UpdateID: 1,
|
||||||
|
Message: &api.Message{
|
||||||
|
MessageID: 1, Date: 0,
|
||||||
|
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||||
|
Text: "ping",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
c := NewContext(ctx, r.bot, &u)
|
||||||
|
_ = r.dispatch(c, &u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRouter_NewContext(b *testing.B) {
|
||||||
|
bot := client.New("t")
|
||||||
|
u := &api.Update{UpdateID: 1}
|
||||||
|
ctx := context.Background()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
_ = NewContext(ctx, bot, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExtractCommand(b *testing.B) {
|
||||||
|
text := "/start@BotName hello world"
|
||||||
|
cmdLen := len("/start@BotName")
|
||||||
|
m := &api.Message{
|
||||||
|
MessageID: 1, Date: 0,
|
||||||
|
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||||
|
Text: text,
|
||||||
|
Entities: []api.MessageEntity{
|
||||||
|
{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: int64(cmdLen)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
_, _, _ = extractCommand(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lukaszraczylo/go-telegram/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
const benchUpdateBody = `{"update_id":12345,"message":{"message_id":1,"date":1700000000,"chat":{"id":42,"type":"private"},"from":{"id":42,"is_bot":false,"first_name":"User"},"text":"hello world"}}`
|
||||||
|
|
||||||
|
func BenchmarkWebhook_ServeHTTP(b *testing.B) {
|
||||||
|
w := NewWebhookServer(client.New("t"), WithBufferSize(1024))
|
||||||
|
body := []byte(benchUpdateBody)
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-w.Updates():
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
w.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
b.Fatalf("status %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user