112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
|
package channel_test
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/creachadair/jrpc2/channel"
|
||
|
)
|
||
|
|
||
|
var benchMessage = map[string]interface{}{
|
||
|
"jsonrpc": "2.0",
|
||
|
"id": "<XXX>",
|
||
|
"method": "echo",
|
||
|
"params": map[string]interface{}{
|
||
|
"title": "A Cask of Amontillado",
|
||
|
"author": "Edgar Allan Poe",
|
||
|
"published": 1847,
|
||
|
"text": strings.Fields(`
|
||
|
|
||
|
The thousand injuries of Fortunato I had borne as I best could but when
|
||
|
he ventured upon insult I vowed revenge you who so well know the nature
|
||
|
of my soul will not suppose however that gave utterance to a threat at
|
||
|
length I would be avenged this was a point definitely settled but the
|
||
|
very definitiveness with which it was resolved precluded the idea of risk
|
||
|
I must not only punish but punish with impunity a wrong is unredressed
|
||
|
when retribution overtakes its redresser it is equally unredressed when
|
||
|
the avenger fails to make himself felt as such to him who has done the
|
||
|
wrong`),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
func BenchmarkFramingCost(b *testing.B) {
|
||
|
framings := []struct {
|
||
|
name string
|
||
|
framing channel.Framing
|
||
|
}{
|
||
|
{"Line", channel.Line},
|
||
|
{"LSP", channel.LSP},
|
||
|
{"NUL", channel.Split('\x00')},
|
||
|
{"RawJSON", channel.RawJSON},
|
||
|
}
|
||
|
|
||
|
msg, err := json.Marshal(benchMessage)
|
||
|
if err != nil {
|
||
|
b.Fatalf("Marshaling benchmark message: %v", err)
|
||
|
}
|
||
|
b.Logf("Benchmark baseline message is %d byte", len(msg))
|
||
|
|
||
|
// Benchmark the round-trip call cycle for a method that does no useful
|
||
|
// work, as a proxy for overhead for client and server maintenance, and the
|
||
|
// cost of encoding and decoding request messages.
|
||
|
for _, bench := range framings {
|
||
|
b.Run(bench.name, func(b *testing.B) {
|
||
|
b.ReportAllocs()
|
||
|
|
||
|
// Simulate a bidirectional channel with unbuffered in-memory pipes.
|
||
|
cr, sw := io.Pipe()
|
||
|
sr, cw := io.Pipe()
|
||
|
cli := bench.framing(cr, cw)
|
||
|
srv := bench.framing(sr, sw)
|
||
|
pkt := bytes.Replace(msg, []byte("<XXX>"), []byte(strconv.Itoa(b.N)), 1)
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
wg.Add(2)
|
||
|
b.ResetTimer()
|
||
|
|
||
|
// The "client" sends a message to the server and waits for it to be
|
||
|
// returned, then checks that the result matches.
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
if err := cli.Send(pkt); err != nil {
|
||
|
b.Errorf("cli.Send(%d bytes) failed: %v", len(pkt), err)
|
||
|
continue
|
||
|
}
|
||
|
cmp, err := cli.Recv()
|
||
|
if err != nil {
|
||
|
b.Errorf("cli.Recv() failed: %v", err)
|
||
|
continue
|
||
|
}
|
||
|
if !bytes.Equal(cmp, pkt) {
|
||
|
b.Errorf("Recv does not match Send\nsend: %s\nrecv: %s", string(pkt), string(cmp))
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// The "server" receives a messaage from the client and sends it back.
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
pkt, err := srv.Recv()
|
||
|
if err != nil {
|
||
|
b.Errorf("srv.Recv() failed: %v", err)
|
||
|
continue
|
||
|
}
|
||
|
if err := srv.Send(pkt); err != nil {
|
||
|
b.Errorf("srv.Send(%d bytes) failed: %v", len(pkt), err)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
wg.Wait()
|
||
|
})
|
||
|
}
|
||
|
}
|