Skip to content

Commit

Permalink
targets: Use github.com/mailru/easyjson
Browse files Browse the repository at this point in the history
```
benchmark                                old ns/op     new ns/op     delta
BenchmarkJSONTargetEncoding/encode-8     787           781           -0.76%
BenchmarkJSONTargetEncoding/decode-8     5812          1510          -74.02%

benchmark                                old allocs     new allocs     delta
BenchmarkJSONTargetEncoding/encode-8     1              1              +0.00%
BenchmarkJSONTargetEncoding/decode-8     18             9              -50.00%

benchmark                                old bytes     new bytes     delta
BenchmarkJSONTargetEncoding/encode-8     470           470           +0.00%
BenchmarkJSONTargetEncoding/decode-8     1230          703           -42.85%
```
  • Loading branch information
tsenart committed Aug 25, 2018
1 parent 2823e3e commit 8a05c77
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 13 deletions.
2 changes: 1 addition & 1 deletion lib/results_easyjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (out *jsonResult) decode(in *jlexer.Lexer) {
}
}

func (in *jsonResult) encode(out *jwriter.Writer) {
func (in jsonResult) encode(out *jwriter.Writer) {
out.RawByte('{')
first := true
_ = first
Expand Down
6 changes: 3 additions & 3 deletions lib/results_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"time"
)

func TestDecoding(t *testing.T) {
func TestResultDecoding(t *testing.T) {
t.Parallel()

var b1, b2 bytes.Buffer
Expand Down Expand Up @@ -48,7 +48,7 @@ func TestDecoding(t *testing.T) {
}
}

func TestEncoding(t *testing.T) {
func TestResultEncoding(t *testing.T) {
for _, tc := range []struct {
encoding string
enc func(io.Writer) Encoder
Expand Down Expand Up @@ -109,7 +109,7 @@ func TestEncoding(t *testing.T) {
}
}

func BenchmarkEncodings(b *testing.B) {
func BenchmarkResultEncodings(b *testing.B) {
b.StopTimer()
b.ResetTimer()

Expand Down
49 changes: 40 additions & 9 deletions lib/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package vegeta
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -14,6 +13,9 @@ import (
"strings"
"sync"
"sync/atomic"

jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)

// Target is an HTTP request blueprint.
Expand Down Expand Up @@ -103,6 +105,11 @@ const (
// Implementations must be safe for concurrent use.
type Targeter func(*Target) error

// Decode is a convenience method that calls the underlying Targeter function.
func (tr Targeter) Decode(t *Target) error {
return tr(t)
}

// NewJSONTargeter returns a new targeter that decodes one Target from the
// given io.Reader on every invocation. Each target is one JSON object in its own line.
//
Expand All @@ -127,16 +134,16 @@ func NewJSONTargeter(src io.Reader, body []byte, header http.Header) Targeter {
return ErrNilTarget
}

rd.Lock()
defer rd.Unlock()
var jl jlexer.Lexer

var line []byte
for len(line) == 0 {
if line, err = rd.ReadBytes('\n'); err != nil {
rd.Lock()
for len(jl.Data) == 0 {
if jl.Data, err = rd.ReadBytes('\n'); err != nil {
break
}
line = bytes.TrimSpace(line) // Skip empty lines
jl.Data = bytes.TrimSpace(jl.Data) // Skip empty lines
}
rd.Unlock()

if err != nil {
if err == io.EOF {
Expand All @@ -145,8 +152,10 @@ func NewJSONTargeter(src io.Reader, body []byte, header http.Header) Targeter {
return err
}

var t Target
if err = json.Unmarshal(line, &t); err != nil {
var t jsonTarget
t.decode(&jl)

if err = jl.Error(); err != nil {
return err
} else if t.Method == "" {
return ErrNoMethod
Expand Down Expand Up @@ -176,6 +185,28 @@ func NewJSONTargeter(src io.Reader, body []byte, header http.Header) Targeter {
}
}

// A TargetEncoder encodes a Target in a format that can be read by a Targeter.
type TargetEncoder func(*Target) error

// Encode is a convenience method that calls the underlying TargetEncoder function.
func (enc TargetEncoder) Encode(t *Target) error {
return enc(t)
}

// NewJSONTargetEncoder returns a TargetEncoder that encods Targets in the JSON format.
func NewJSONTargetEncoder(w io.Writer) TargetEncoder {
var jw jwriter.Writer
return func(t *Target) error {
(*jsonTarget)(t).encode(&jw)
if jw.Error != nil {
return jw.Error
}
jw.RawByte('\n')
_, err := jw.DumpTo(w)
return err
}
}

// NewStaticTargeter returns a Targeter which round-robins over the passed
// Targets.
func NewStaticTargeter(tgts ...Target) Targeter {
Expand Down
167 changes: 167 additions & 0 deletions lib/targets_easyjson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// This file has been modified from the original generated code to make it work with
// type alias jsonTarget so that the methods aren't exposed in Target.

package vegeta

import (
http "net/http"

jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)

type jsonTarget Target

func (out *jsonTarget) decode(in *jlexer.Lexer) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "method":
out.Method = string(in.String())
case "url":
out.URL = string(in.String())
case "body":
if in.IsNull() {
in.Skip()
out.Body = nil
} else {
out.Body = in.Bytes()
}
case "header":
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
if !in.IsDelim('}') {
out.Header = make(http.Header)
} else {
out.Header = nil
}
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v2 []string
if in.IsNull() {
in.Skip()
v2 = nil
} else {
in.Delim('[')
if v2 == nil {
if !in.IsDelim(']') {
v2 = make([]string, 0, 4)
} else {
v2 = []string{}
}
} else {
v2 = (v2)[:0]
}
for !in.IsDelim(']') {
var v3 string
v3 = string(in.String())
v2 = append(v2, v3)
in.WantComma()
}
in.Delim(']')
}
(out.Header)[key] = v2
in.WantComma()
}
in.Delim('}')
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}

func (in jsonTarget) encode(out *jwriter.Writer) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"method\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Method))
}
{
const prefix string = ",\"url\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.URL))
}
if len(in.Body) != 0 {
const prefix string = ",\"body\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Base64Bytes(in.Body)
}
if len(in.Header) != 0 {
const prefix string = ",\"header\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('{')
v6First := true
for v6Name, v6Value := range in.Header {
if v6First {
v6First = false
} else {
out.RawByte(',')
}
out.String(string(v6Name))
out.RawByte(':')
if v6Value == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v7, v8 := range v6Value {
if v7 > 0 {
out.RawByte(',')
}
out.String(string(v8))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
}
out.RawByte('}')
}
31 changes: 31 additions & 0 deletions lib/targets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,34 @@ func TestErrNilTarget(t *testing.T) {
}
}
}

func BenchmarkJSONTargetEncoding(b *testing.B) {
b.StopTimer()
b.ResetTimer()

targets := make([]Target, 1e5)
for i := 0; i < cap(targets); i++ {
targets[i] = Target{
Method: "POST",
URL: "https://goku/12345",
Body: []byte("BIG BANG!"),
Header: http.Header{"Content-Type": []string{"high/energy"}},
}
}

var buf bytes.Buffer
enc := NewJSONTargetEncoder(&buf)

b.Run("encode", func(b *testing.B) {
for i := 0; i < b.N; i++ {
enc.Encode(&targets[i%len(targets)])
}
})

dec := NewJSONTargeter(&buf, nil, nil)
b.Run("decode", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dec.Decode(&targets[i%len(targets)])
}
})
}

0 comments on commit 8a05c77

Please sign in to comment.