blob: 2c7750cb0ac78d0ecb89cf70b16b14bed71a18ca [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package golang
import (
"bytes"
"fmt"
"strings"
"text/template"
gidlconfig "go.fuchsia.dev/fuchsia/tools/fidl/gidl/config"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
var benchmarkTmpl = template.Must(template.New("benchmarkTmpls").Parse(`
package benchmark_suite
import (
"context"
"sync"
"testing"
"fidl/benchmarkfidl"
"syscall/zx"
"syscall/zx/fidl"
"go.fuchsia.dev/fuchsia/src/lib/component"
)
type pools struct {
bytes sync.Pool
handleInfos sync.Pool
handleDispositions sync.Pool
}
func newPools() *pools {
return &pools{
bytes: sync.Pool{
New: func() interface{} {
return make([]byte, zx.ChannelMaxMessageBytes)
},
},
handleInfos: sync.Pool{
New: func() interface{} {
return make([]zx.HandleInfo, zx.ChannelMaxMessageHandles)
},
},
handleDispositions: sync.Pool{
New: func() interface{} {
return make([]zx.HandleDisposition, zx.ChannelMaxMessageHandles)
},
},
}
}
func (p *pools) useOnce() {
p.bytes.Put(p.bytes.Get().([]byte))
p.handleInfos.Put(p.handleInfos.Get().([]zx.HandleInfo))
p.handleDispositions.Put(p.handleDispositions.Get().([]zx.HandleDisposition))
}
{{ range .Benchmarks }}
func BenchmarkEncode{{ .Name }}(b *testing.B) {
pools := newPools()
pools.useOnce()
input := {{ .Value }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// This should be kept in sync with the buffer allocation strategy used in Go bindings.
respb := pools.bytes.Get().([]byte)
resphd := pools.handleDispositions.Get().([]zx.HandleDisposition)
_, _, err := fidl.Marshal(&input, respb, resphd)
if err != nil {
b.Fatal(err)
}
pools.bytes.Put(respb)
pools.handleDispositions.Put(resphd)
}
}
func BenchmarkDecode{{ .Name }}(b *testing.B) {
data := make([]byte, 65536)
input := {{ .Value }}
_, _, err := fidl.Marshal(&input, data, nil)
if err != nil {
b.Fatal(err)
}
var output {{ .ValueType }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := fidl.Unmarshal(data, nil, &output)
if err != nil {
b.Fatal(err)
}
}
}
{{ if .EnableSendEventBenchmark }}
func BenchmarkSendEvent{{ .Name }}(b *testing.B) {
sender_end, recipient_end, err := zx.NewChannel(0)
if err != nil {
b.Fatal(err)
}
defer sender_end.Close()
defer recipient_end.Close()
sender_proxy := {{ .ValueType }}EventProtocolEventProxy(fidl.ChannelProxy{Channel: sender_end})
recipient_proxy := {{ .ValueType }}EventProtocolWithCtxInterface(fidl.ChannelProxy{Channel: recipient_end})
input := {{ .Value }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := sender_proxy.Send(input); err != nil {
b.Fatal(err)
}
if _, err := recipient_proxy.ExpectSend(context.Background()); err != nil {
b.Fatal(err)
}
}
}
{{ end }}
{{ if .EnableEchoCallBenchmark }}
type {{ .Name }}EchoCallService struct {}
func (s *{{ .Name }}EchoCallService) Echo(ctx fidl.Context, val {{ .ValueType }}) ({{ .ValueType }}, error) {
return val, nil
}
func BenchmarkEchoCall{{ .Name }}(b *testing.B) {
client_end, server_end, err := zx.NewChannel(0)
if err != nil {
b.Fatal(err)
}
defer client_end.Close()
stub := &{{ .ValueType }}EchoCallWithCtxStub{
Impl: &{{ .Name }}EchoCallService{},
}
go component.ServeExclusive(context.Background(), stub, server_end, func (err error) {
b.Fatal(err)
})
proxy := {{ .ValueType }}EchoCallWithCtxInterface(fidl.ChannelProxy{Channel: client_end})
input := {{ .Value }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := proxy.Echo(context.Background(), input); err != nil {
b.Fatal(err)
}
}
}
{{- end -}}
{{ end }}
type Benchmark struct {
Label string
BenchFunc func(*testing.B)
}
// Benchmarks is read by go_fidl_benchmarks_lib.
var Benchmarks = []Benchmark{
{{ range .Benchmarks }}
{
Label: "Encode/{{ .ChromeperfPath }}",
BenchFunc: BenchmarkEncode{{ .Name }},
},
{
Label: "Decode/{{ .ChromeperfPath }}",
BenchFunc: BenchmarkDecode{{ .Name }},
},
{{ if .EnableSendEventBenchmark }}
{
Label: "SendEvent/{{ .ChromeperfPath }}",
BenchFunc: BenchmarkSendEvent{{ .Name }},
},
{{- end -}}
{{ if .EnableEchoCallBenchmark }}
{
Label: "EchoCall/{{ .ChromeperfPath }}",
BenchFunc: BenchmarkEchoCall{{ .Name }},
},
{{- end -}}
{{ end }}
}
`))
type benchmarkTmplInput struct {
Benchmarks []benchmark
}
type benchmark struct {
Name, ChromeperfPath, Value, ValueType string
EnableSendEventBenchmark, EnableEchoCallBenchmark bool
}
// GenerateBenchmarks generates Go benchmarks.
func GenerateBenchmarks(gidl gidlir.All, fidl fidlgen.Root, config gidlconfig.GeneratorConfig) ([]byte, error) {
schema := gidlmixer.BuildSchema(fidl)
var benchmarks []benchmark
for _, gidlBenchmark := range gidl.Benchmark {
decl, err := schema.ExtractDeclaration(gidlBenchmark.Value, gidlBenchmark.HandleDefs)
if err != nil {
return nil, fmt.Errorf("benchmark %s: %s", gidlBenchmark.Name, err)
}
value := visit(gidlBenchmark.Value, decl)
benchmarks = append(benchmarks, benchmark{
Name: goBenchmarkName(gidlBenchmark.Name),
ChromeperfPath: gidlBenchmark.Name,
Value: value,
ValueType: declName(decl),
EnableSendEventBenchmark: gidlBenchmark.EnableSendEventBenchmark,
EnableEchoCallBenchmark: gidlBenchmark.EnableEchoCallBenchmark,
})
}
input := benchmarkTmplInput{
Benchmarks: benchmarks,
}
var buf bytes.Buffer
err := withGoFmt{benchmarkTmpl}.Execute(&buf, input)
return buf.Bytes(), err
}
func goBenchmarkName(gidlName string) string {
return strings.ReplaceAll(gidlName, "/", "_")
}