Skip to content

Commit

Permalink
Merge pull request #327 from tsenart/easyjson
Browse files Browse the repository at this point in the history
Use github.com/mailru/easyjson for JSON encoding and decoding of Targets and Results
  • Loading branch information
tsenart authored Aug 25, 2018
2 parents b58839f + 7729067 commit 64d5bd3
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 26 deletions.
16 changes: 16 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@
[[constraint]]
branch = "master"
name = "github.com/c2h5oh/datasize"

[[constraint]]
branch = "master"
name = "github.com/mailru/easyjson"
28 changes: 23 additions & 5 deletions lib/results.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package vegeta

import (
"bufio"
"bytes"
"encoding/base64"
"encoding/csv"
"encoding/gob"
"encoding/json"
"io"
"sort"
"strconv"
"time"

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

func init() {
Expand Down Expand Up @@ -212,12 +215,27 @@ func NewCSVDecoder(rd io.Reader) Decoder {
// NewJSONEncoder returns an Encoder that dumps the given *Results as a JSON
// object.
func NewJSONEncoder(w io.Writer) Encoder {
enc := json.NewEncoder(w)
return func(r *Result) error { return enc.Encode(r) }
var jw jwriter.Writer
return func(r *Result) error {
(*jsonResult)(r).encode(&jw)
if jw.Error != nil {
return jw.Error
}
jw.RawByte('\n')
_, err := jw.DumpTo(w)
return err
}
}

// NewJSONDecoder returns a Decoder that decodes JSON encoded Results.
func NewJSONDecoder(r io.Reader) Decoder {
dec := json.NewDecoder(r)
return func(r *Result) error { return dec.Decode(r) }
rd := bufio.NewReader(r)
return func(r *Result) (err error) {
var jl jlexer.Lexer
if jl.Data, err = rd.ReadSlice('\n'); err != nil {
return err
}
(*jsonResult)(r).decode(&jl)
return jl.Error()
}
}
164 changes: 164 additions & 0 deletions lib/results_easyjson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// This file has been modified from the original generated code to make it work with
// type alias jsonResult so that the methods aren't exposed in Result.
package vegeta

import (
"time"

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

type jsonResult Result

func (out *jsonResult) 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 "attack":
out.Attack = string(in.String())
case "seq":
out.Seq = uint64(in.Uint64())
case "code":
out.Code = uint16(in.Uint16())
case "timestamp":
if data := in.Raw(); in.Ok() {
in.AddError((out.Timestamp).UnmarshalJSON(data))
}
case "latency":
out.Latency = time.Duration(in.Int64())
case "bytes_out":
out.BytesOut = uint64(in.Uint64())
case "bytes_in":
out.BytesIn = uint64(in.Uint64())
case "error":
out.Error = string(in.String())
case "body":
if in.IsNull() {
in.Skip()
out.Body = nil
} else {
out.Body = in.Bytes()
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}

func (in jsonResult) encode(out *jwriter.Writer) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"attack\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Attack))
}
{
const prefix string = ",\"seq\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Uint64(uint64(in.Seq))
}
{
const prefix string = ",\"code\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Uint16(uint16(in.Code))
}
{
const prefix string = ",\"timestamp\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Raw((in.Timestamp).MarshalJSON())
}
{
const prefix string = ",\"latency\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(in.Latency))
}
{
const prefix string = ",\"bytes_out\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Uint64(uint64(in.BytesOut))
}
{
const prefix string = ",\"bytes_in\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Uint64(uint64(in.BytesIn))
}
{
const prefix string = ",\"error\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Error))
}
{
const prefix string = ",\"body\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Base64Bytes(in.Body)
}
out.RawByte('}')
}
72 changes: 61 additions & 11 deletions lib/results_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package vegeta
import (
"bytes"
"io"
"math/rand"
"reflect"
"testing"
"testing/quick"
"time"
)

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

var b1, b2 bytes.Buffer
Expand Down Expand Up @@ -47,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 @@ -79,19 +80,23 @@ func TestEncoding(t *testing.T) {

var buf bytes.Buffer
enc := tc.enc(&buf)
if err := enc(&want); err != nil {
t.Fatal(err)
for j := 0; j < 2; j++ {
if err := enc(&want); err != nil {
t.Fatal(err)
}
}

dec := tc.dec(&buf)
var got Result
if err := dec(&got); err != nil {
t.Fatalf("err: %q buffer: %s", err, buf.String())
}
for j := 0; j < 2; j++ {
var got Result
if err := dec(&got); err != nil {
t.Fatalf("err: %q buffer: %s", err, buf.String())
}

if !got.Equal(want) {
t.Logf("\ngot: %#v\nwant: %#v\n", got, want)
return false
if !got.Equal(want) {
t.Logf("\ngot: %#v\nwant: %#v\n", got, want)
return false
}
}

return true
Expand All @@ -103,3 +108,48 @@ func TestEncoding(t *testing.T) {
})
}
}

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

rng := rand.New(rand.NewSource(0))
zf := rand.NewZipf(rng, 3, 2, 1000)
began := time.Now()
results := make([]Result, 1e5)

for i := 0; i < cap(results); i++ {
results[i] = Result{
Attack: "Big Bang!",
Seq: uint64(i),
Timestamp: began.Add(time.Duration(i) * time.Millisecond),
Latency: time.Duration(zf.Uint64()) * time.Millisecond,
}
}

for _, tc := range []struct {
encoding string
enc func(io.Writer) Encoder
dec func(io.Reader) Decoder
}{
{"gob", NewEncoder, NewDecoder},
{"csv", NewCSVEncoder, NewCSVDecoder},
{"json", NewJSONEncoder, NewJSONDecoder},
} {
var buf bytes.Buffer
enc := tc.enc(&buf)

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

dec := tc.dec(&buf)
b.Run(tc.encoding+"-decode", func(b *testing.B) {
for i := 0; i < b.N; i++ {
dec.Decode(&results[i%len(results)])
}
})
}
}
Loading

0 comments on commit 64d5bd3

Please sign in to comment.