mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
75c7ce3119
Two changes that together cut allocs/call from 15 to 13 (client-internal bench) and per-call CPU from 600ns to 455ns (-24%) on the no-HTTP path: 1. Codec gets an optional BodyEncoder extension (MarshalTo io.Writer). When present, encodeJSONBody stream-encodes the request directly into a pooled *bytes.Buffer instead of allocating a [2-step] Marshal+Reader pair. DefaultCodec implements it via goccy/go-json.NewEncoder. 2. *Bot caches the parsed base URL on construction. buildRequest skips net/http.NewRequestWithContext for the common case and constructs *http.Request manually — clones the URL by value, sets the method path, and populates ContentLength + GetBody from the body's concrete type so RetryDoer's body-replay across attempts still works. Cross-library bench (sendMessage round-trip vs httptest.Server): -2 allocs/call (104 -> 102), bytes -1.2%, time within noise (real HTTP plumbing dominates). The CPU win is visible on synthetic stub-doer benches and translates to lower GC pressure on sustained-throughput workloads. Slow-path fallback preserved for codecs that don't implement BodyEncoder and for *Bot instances where url.Parse on the configured base failed — they take the original NewRequestWithContext path.
98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
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, pooled, err := encodeJSONBody(codec, req)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
_ = r
|
|
if pooled != nil {
|
|
putReqBuf(pooled)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|