From 5351d8db7c7c8662bf20fe0b4374cbe9de33aee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Th=C3=B6mmes?= Date: Thu, 17 Dec 2020 10:56:20 +0100 Subject: [PATCH] Fix module usage of vegeta --- go.mod | 2 +- go.sum | 4 +- .../infra/sender/http_load_generator.go | 2 +- .../infra/sender/http_load_generator_test.go | 2 +- .../tsenart/vegeta/{ => v12}/lib/LICENSE | 2 +- vendor/github.com/mailru/easyjson/.gitignore | 5 + vendor/github.com/mailru/easyjson/.travis.yml | 12 + vendor/github.com/mailru/easyjson/Makefile | 54 +++ vendor/github.com/mailru/easyjson/README.md | 336 ++++++++++++++++++ vendor/github.com/mailru/easyjson/go.mod | 3 + vendor/github.com/mailru/easyjson/helpers.go | 78 ++++ vendor/github.com/mailru/easyjson/raw.go | 45 +++ .../tsenart/vegeta/lib/results_easyjson.go | 165 --------- .../tsenart/vegeta/{ => v12}/LICENSE | 2 +- .../tsenart/vegeta/{ => v12}/lib/attack.go | 46 +++ .../tsenart/vegeta/v12/lib/attack_fuzz.go | 154 ++++++++ .../tsenart/vegeta/{ => v12}/lib/histogram.go | 6 +- .../tsenart/vegeta/{ => v12}/lib/metrics.go | 13 + .../tsenart/vegeta/{ => v12}/lib/pacer.go | 46 ++- .../tsenart/vegeta/{ => v12}/lib/reporters.go | 33 +- .../tsenart/vegeta/{ => v12}/lib/results.go | 81 ++++- .../vegeta/v12/lib/results_easyjson.go | 223 ++++++++++++ .../tsenart/vegeta/v12/lib/results_fuzz.go | 63 ++++ .../vegeta/{ => v12}/lib/target.schema.json | 0 .../tsenart/vegeta/{ => v12}/lib/targets.go | 6 +- .../vegeta/{ => v12}/lib/targets_easyjson.go | 0 .../tsenart/vegeta/v12/lib/targets_fuzz.go | 68 ++++ .../tsenart/vegeta/v12/lib/util_fuzz.go | 120 +++++++ vendor/modules.txt | 5 +- 29 files changed, 1372 insertions(+), 204 deletions(-) rename third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/{ => v12}/lib/LICENSE (96%) create mode 100644 vendor/github.com/mailru/easyjson/.gitignore create mode 100644 vendor/github.com/mailru/easyjson/.travis.yml create mode 100644 vendor/github.com/mailru/easyjson/Makefile create mode 100644 vendor/github.com/mailru/easyjson/README.md create mode 100644 vendor/github.com/mailru/easyjson/go.mod create mode 100644 vendor/github.com/mailru/easyjson/helpers.go create mode 100644 vendor/github.com/mailru/easyjson/raw.go delete mode 100644 vendor/github.com/tsenart/vegeta/lib/results_easyjson.go rename vendor/github.com/tsenart/vegeta/{ => v12}/LICENSE (96%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/attack.go (88%) create mode 100644 vendor/github.com/tsenart/vegeta/v12/lib/attack_fuzz.go rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/histogram.go (91%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/metrics.go (94%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/pacer.go (88%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/reporters.go (88%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/results.go (78%) create mode 100644 vendor/github.com/tsenart/vegeta/v12/lib/results_easyjson.go create mode 100644 vendor/github.com/tsenart/vegeta/v12/lib/results_fuzz.go rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/target.schema.json (100%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/targets.go (98%) rename vendor/github.com/tsenart/vegeta/{ => v12}/lib/targets_easyjson.go (100%) create mode 100644 vendor/github.com/tsenart/vegeta/v12/lib/targets_fuzz.go create mode 100644 vendor/github.com/tsenart/vegeta/v12/lib/util_fuzz.go diff --git a/go.mod b/go.mod index d686d1a6b45..31515aad072 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/rogpeppe/fastuuid v1.2.0 github.com/stretchr/testify v1.5.1 - github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible + github.com/tsenart/vegeta/v12 v12.8.4 github.com/wavesoftware/go-ensure v1.0.0 go.opencensus.io v0.22.5 go.opentelemetry.io/otel v0.2.3 diff --git a/go.sum b/go.sum index 2b5c616176c..80b05e06a62 100644 --- a/go.sum +++ b/go.sum @@ -634,8 +634,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= -github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible h1:ErZrHhRveAoznVW80gbrxz+qxJNydpA2fcQxTPHkZbU= -github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM= +github.com/tsenart/vegeta/v12 v12.8.4 h1:UQ7tG7WkDorKj0wjx78Z4/vsMBP8RJQMGJqRVrkvngg= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1154,6 +1153,7 @@ knative.dev/pkg v0.0.0-20201215202458-ef8048c0ba77 h1:N9DkFX8F4h86MGXgbvr5Sz9ojj knative.dev/pkg v0.0.0-20201215202458-ef8048c0ba77/go.mod h1:VjrwVhfEZUnn6FruncHcBm854FldnRpekpyBbYtBvZM= knative.dev/reconciler-test v0.0.0-20201124190335-83a44efcdfef h1:8PttDFSsac32mq6Va+uPOyOR5CfX8JQT9g+MnHNyJ94= knative.dev/reconciler-test v0.0.0-20201124190335-83a44efcdfef/go.mod h1:YSs1y1rgnjs8w39/drLIOQbWvZUQwqApvd+EizO8UsA= +pgregory.net/rapid v0.3.3 h1:jCjBsY4ln4Atz78QoBWxUEvAHaFyNDQg9+WU62aCn1U= pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/test/performance/infra/sender/http_load_generator.go b/test/performance/infra/sender/http_load_generator.go index 7b68ed0ccc0..3ac5756e57d 100644 --- a/test/performance/infra/sender/http_load_generator.go +++ b/test/performance/infra/sender/http_load_generator.go @@ -28,7 +28,7 @@ import ( "github.com/golang/protobuf/ptypes" "github.com/google/uuid" "github.com/rogpeppe/fastuuid" - vegeta "github.com/tsenart/vegeta/lib" + vegeta "github.com/tsenart/vegeta/v12/lib" "knative.dev/eventing/test/performance/infra/common" ) diff --git a/test/performance/infra/sender/http_load_generator_test.go b/test/performance/infra/sender/http_load_generator_test.go index c10169860a0..748efaa2757 100644 --- a/test/performance/infra/sender/http_load_generator_test.go +++ b/test/performance/infra/sender/http_load_generator_test.go @@ -16,7 +16,7 @@ import ( "bytes" "testing" - vegeta "github.com/tsenart/vegeta/lib" + vegeta "github.com/tsenart/vegeta/v12/lib" ) func TestGenerateRandStringPayload(t *testing.T) { diff --git a/third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/lib/LICENSE b/third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/v12/lib/LICENSE similarity index 96% rename from third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/lib/LICENSE rename to third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/v12/lib/LICENSE index 441df0ab407..03ec4f5a824 100644 --- a/third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/lib/LICENSE +++ b/third_party/VENDOR-LICENSE/github.com/tsenart/vegeta/v12/lib/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2016 Tomás Senart +Copyright (c) 2013-2020 Tomás Senart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/vendor/github.com/mailru/easyjson/.gitignore b/vendor/github.com/mailru/easyjson/.gitignore new file mode 100644 index 00000000000..26156fb4b4f --- /dev/null +++ b/vendor/github.com/mailru/easyjson/.gitignore @@ -0,0 +1,5 @@ +.root +*_easyjson.go +*.iml +.idea +*.swp diff --git a/vendor/github.com/mailru/easyjson/.travis.yml b/vendor/github.com/mailru/easyjson/.travis.yml new file mode 100644 index 00000000000..0ececa0718f --- /dev/null +++ b/vendor/github.com/mailru/easyjson/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - tip + - stable + +matrix: + allow_failures: + - go: tip + +install: + - go get golang.org/x/lint/golint diff --git a/vendor/github.com/mailru/easyjson/Makefile b/vendor/github.com/mailru/easyjson/Makefile new file mode 100644 index 00000000000..66cc402431a --- /dev/null +++ b/vendor/github.com/mailru/easyjson/Makefile @@ -0,0 +1,54 @@ +all: test + +clean: + rm -rf bin + rm -rf tests/*_easyjson.go + rm -rf benchmark/*_easyjson.go + +build: + go build -i -o ./bin/easyjson ./easyjson + +generate: build + bin/easyjson -stubs \ + ./tests/snake.go \ + ./tests/data.go \ + ./tests/omitempty.go \ + ./tests/nothing.go \ + ./tests/named_type.go \ + ./tests/custom_map_key_type.go \ + ./tests/embedded_type.go \ + ./tests/reference_to_pointer.go \ + ./tests/html.go \ + + bin/easyjson -all ./tests/data.go + bin/easyjson -all ./tests/nothing.go + bin/easyjson -all ./tests/errors.go + bin/easyjson -all ./tests/html.go + bin/easyjson -snake_case ./tests/snake.go + bin/easyjson -omit_empty ./tests/omitempty.go + bin/easyjson -build_tags=use_easyjson ./benchmark/data.go + bin/easyjson ./tests/nested_easy.go + bin/easyjson ./tests/named_type.go + bin/easyjson ./tests/custom_map_key_type.go + bin/easyjson ./tests/embedded_type.go + bin/easyjson ./tests/reference_to_pointer.go + bin/easyjson ./tests/key_marshaler_map.go + bin/easyjson -disallow_unknown_fields ./tests/disallow_unknown.go + +test: generate + go test \ + ./tests \ + ./jlexer \ + ./gen \ + ./buffer + cd benchmark && go test -benchmem -tags use_easyjson -bench . + golint -set_exit_status ./tests/*_easyjson.go + +bench-other: generate + cd benchmark && make + +bench-python: + benchmark/ujson.sh + + +.PHONY: clean generate test build diff --git a/vendor/github.com/mailru/easyjson/README.md b/vendor/github.com/mailru/easyjson/README.md new file mode 100644 index 00000000000..3bdcf2d06c2 --- /dev/null +++ b/vendor/github.com/mailru/easyjson/README.md @@ -0,0 +1,336 @@ +# easyjson [![Build Status](https://travis-ci.org/mailru/easyjson.svg?branch=master)](https://travis-ci.org/mailru/easyjson) [![Go Report Card](https://goreportcard.com/badge/github.com/mailru/easyjson)](https://goreportcard.com/report/github.com/mailru/easyjson) + +Package easyjson provides a fast and easy way to marshal/unmarshal Go structs +to/from JSON without the use of reflection. In performance tests, easyjson +outperforms the standard `encoding/json` package by a factor of 4-5x, and other +JSON encoding packages by a factor of 2-3x. + +easyjson aims to keep generated Go code simple enough so that it can be easily +optimized or fixed. Another goal is to provide users with the ability to +customize the generated code by providing options not available with the +standard `encoding/json` package, such as generating "snake_case" names or +enabling `omitempty` behavior by default. + +## Usage +```sh +# install +go get -u github.com/mailru/easyjson/... + +# run +easyjson -all .go +``` + +The above will generate `_easyjson.go` containing the appropriate marshaler and +unmarshaler funcs for all structs contained in `.go`. + +Please note that easyjson requires a full Go build environment and the `GOPATH` +environment variable to be set. This is because easyjson code generation +invokes `go run` on a temporary file (an approach to code generation borrowed +from [ffjson](https://github.com/pquerna/ffjson)). + +## Options +```txt +Usage of easyjson: + -all + generate marshaler/unmarshalers for all structs in a file + -build_tags string + build tags to add to generated file + -leave_temps + do not delete temporary files + -no_std_marshalers + don't generate MarshalJSON/UnmarshalJSON funcs + -noformat + do not run 'gofmt -w' on output file + -omit_empty + omit empty fields by default + -output_filename string + specify the filename of the output + -pkg + process the whole package instead of just the given file + -snake_case + use snake_case names instead of CamelCase by default + -lower_camel_case + use lowerCamelCase instead of CamelCase by default + -stubs + only generate stubs for marshaler/unmarshaler funcs + -disallow_unknown_fields + return error if some unknown field in json appeared +``` + +Using `-all` will generate marshalers/unmarshalers for all Go structs in the +file. If `-all` is not provided, then only those structs whose preceding +comment starts with `easyjson:json` will have marshalers/unmarshalers +generated. For example: + +```go +//easyjson:json +type A struct {} +``` + +Additional option notes: + +* `-snake_case` tells easyjson to generate snake\_case field names by default + (unless overridden by a field tag). The CamelCase to snake\_case conversion + algorithm should work in most cases (ie, HTTPVersion will be converted to + "http_version"). + +* `-build_tags` will add the specified build tags to generated Go sources. + +## Generated Marshaler/Unmarshaler Funcs + +For Go struct types, easyjson generates the funcs `MarshalEasyJSON` / +`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisify +the `easyjson.Marshaler` and `easyjson.Unmarshaler` interfaces and when used in +conjunction with `easyjson.Marshal` / `easyjson.Unmarshal` avoid unnecessary +reflection / type assertions during marshaling/unmarshaling to/from JSON for Go +structs. + +easyjson also generates `MarshalJSON` and `UnmarshalJSON` funcs for Go struct +types compatible with the standard `json.Marshaler` and `json.Unmarshaler` +interfaces. Please be aware that using the standard `json.Marshal` / +`json.Unmarshal` for marshaling/unmarshaling will incur a significant +performance penalty when compared to using `easyjson.Marshal` / +`easyjson.Unmarshal`. + +Additionally, easyjson exposes utility funcs that use the `MarshalEasyJSON` and +`UnmarshalEasyJSON` for marshaling/unmarshaling to and from standard readers +and writers. For example, easyjson provides `easyjson.MarshalToHTTPResponseWriter` +which marshals to the standard `http.ResponseWriter`. Please see the [GoDoc +listing](https://godoc.org/github.com/mailru/easyjson) for the full listing of +utility funcs that are available. + +## Controlling easyjson Marshaling and Unmarshaling Behavior + +Go types can provide their own `MarshalEasyJSON` and `UnmarshalEasyJSON` funcs +that satisify the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces. +These will be used by `easyjson.Marshal` and `easyjson.Unmarshal` when defined +for a Go type. + +Go types can also satisify the `easyjson.Optional` interface, which allows the +type to define its own `omitempty` logic. + +## Type Wrappers + +easyjson provides additional type wrappers defined in the `easyjson/opt` +package. These wrap the standard Go primitives and in turn satisify the +easyjson interfaces. + +The `easyjson/opt` type wrappers are useful when needing to distinguish between +a missing value and/or when needing to specifying a default value. Type +wrappers allow easyjson to avoid additional pointers and heap allocations and +can significantly increase performance when used properly. + +## Memory Pooling + +easyjson uses a buffer pool that allocates data in increasing chunks from 128 +to 32768 bytes. Chunks of 512 bytes and larger will be reused with the help of +`sync.Pool`. The maximum size of a chunk is bounded to reduce redundant memory +allocation and to allow larger reusable buffers. + +easyjson's custom allocation buffer pool is defined in the `easyjson/buffer` +package, and the default behavior pool behavior can be modified (if necessary) +through a call to `buffer.Init()` prior to any marshaling or unmarshaling. +Please see the [GoDoc listing](https://godoc.org/github.com/mailru/easyjson/buffer) +for more information. + +## Issues, Notes, and Limitations + +* easyjson is still early in its development. As such, there are likely to be + bugs and missing features when compared to `encoding/json`. In the case of a + missing feature or bug, please create a GitHub issue. Pull requests are + welcome! + +* Unlike `encoding/json`, object keys are case-sensitive. Case-insensitive + matching is not currently provided due to the significant performance hit + when doing case-insensitive key matching. In the future, case-insensitive + object key matching may be provided via an option to the generator. + +* easyjson makes use of `unsafe`, which simplifies the code and + provides significant performance benefits by allowing no-copy + conversion from `[]byte` to `string`. That said, `unsafe` is used + only when unmarshaling and parsing JSON, and any `unsafe` operations + / memory allocations done will be safely deallocated by + easyjson. Set the build tag `easyjson_nounsafe` to compile it + without `unsafe`. + +* easyjson is compatible with Google App Engine. The `appengine` build + tag (set by App Engine's environment) will automatically disable the + use of `unsafe`, which is not allowed in App Engine's Standard + Environment. Note that the use with App Engine is still experimental. + +* Floats are formatted using the default precision from Go's `strconv` package. + As such, easyjson will not correctly handle high precision floats when + marshaling/unmarshaling JSON. Note, however, that there are very few/limited + uses where this behavior is not sufficient for general use. That said, a + different package may be needed if precise marshaling/unmarshaling of high + precision floats to/from JSON is required. + +* While unmarshaling, the JSON parser does the minimal amount of work needed to + skip over unmatching parens, and as such full validation is not done for the + entire JSON value being unmarshaled/parsed. + +* Currently there is no true streaming support for encoding/decoding as + typically for many uses/protocols the final, marshaled length of the JSON + needs to be known prior to sending the data. Currently this is not possible + with easyjson's architecture. + +* easyjson parser and codegen based on reflection, so it wont works on `package main` + files, because they cant be imported by parser. + +## Benchmarks + +Most benchmarks were done using the example +[13kB example JSON](https://dev.twitter.com/rest/reference/get/search/tweets) +(9k after eliminating whitespace). This example is similar to real-world data, +is well-structured, and contains a healthy variety of different types, making +it ideal for JSON serialization benchmarks. + +Note: + +* For small request benchmarks, an 80 byte portion of the above example was + used. + +* For large request marshaling benchmarks, a struct containing 50 regular + samples was used, making a ~500kB output JSON. + +* Benchmarks are showing the results of easyjson's default behaviour, + which makes use of `unsafe`. + +Benchmarks are available in the repository and can be run by invoking `make`. + +### easyjson vs. encoding/json + +easyjson is roughly 5-6 times faster than the standard `encoding/json` for +unmarshaling, and 3-4 times faster for non-concurrent marshaling. Concurrent +marshaling is 6-7x faster if marshaling to a writer. + +### easyjson vs. ffjson + +easyjson uses the same approach for JSON marshaling as +[ffjson](https://github.com/pquerna/ffjson), but takes a significantly +different approach to lexing and parsing JSON during unmarshaling. This means +easyjson is roughly 2-3x faster for unmarshaling and 1.5-2x faster for +non-concurrent unmarshaling. + +As of this writing, `ffjson` seems to have issues when used concurrently: +specifically, large request pooling hurts `ffjson`'s performance and causes +scalability issues. These issues with `ffjson` can likely be fixed, but as of +writing remain outstanding/known issues with `ffjson`. + +easyjson and `ffjson` have similar performance for small requests, however +easyjson outperforms `ffjson` by roughly 2-5x times for large requests when +used with a writer. + +### easyjson vs. go/codec + +[go/codec](https://github.com/ugorji/go) provides +compile-time helpers for JSON generation. In this case, helpers do not work +like marshalers as they are encoding-independent. + +easyjson is generally 2x faster than `go/codec` for non-concurrent benchmarks +and about 3x faster for concurrent encoding (without marshaling to a writer). + +In an attempt to measure marshaling performance of `go/codec` (as opposed to +allocations/memcpy/writer interface invocations), a benchmark was done with +resetting length of a byte slice rather than resetting the whole slice to nil. +However, the optimization in this exact form may not be applicable in practice, +since the memory is not freed between marshaling operations. + +### easyjson vs 'ujson' python module + +[ujson](https://github.com/esnme/ultrajson) is using C code for parsing, so it +is interesting to see how plain golang compares to that. It is imporant to note +that the resulting object for python is slower to access, since the library +parses JSON object into dictionaries. + +easyjson is slightly faster for unmarshaling and 2-3x faster than `ujson` for +marshaling. + +### Benchmark Results + +`ffjson` results are from February 4th, 2016, using the latest `ffjson` and go1.6. +`go/codec` results are from March 4th, 2016, using the latest `go/codec` and go1.6. + +#### Unmarshaling + +| lib | json size | MB/s | allocs/op | B/op | +|:---------|:----------|-----:|----------:|------:| +| standard | regular | 22 | 218 | 10229 | +| standard | small | 9.7 | 14 | 720 | +| | | | | | +| easyjson | regular | 125 | 128 | 9794 | +| easyjson | small | 67 | 3 | 128 | +| | | | | | +| ffjson | regular | 66 | 141 | 9985 | +| ffjson | small | 17.6 | 10 | 488 | +| | | | | | +| codec | regular | 55 | 434 | 19299 | +| codec | small | 29 | 7 | 336 | +| | | | | | +| ujson | regular | 103 | N/A | N/A | + +#### Marshaling, one goroutine. + +| lib | json size | MB/s | allocs/op | B/op | +|:----------|:----------|-----:|----------:|------:| +| standard | regular | 75 | 9 | 23256 | +| standard | small | 32 | 3 | 328 | +| standard | large | 80 | 17 | 1.2M | +| | | | | | +| easyjson | regular | 213 | 9 | 10260 | +| easyjson* | regular | 263 | 8 | 742 | +| easyjson | small | 125 | 1 | 128 | +| easyjson | large | 212 | 33 | 490k | +| easyjson* | large | 262 | 25 | 2879 | +| | | | | | +| ffjson | regular | 122 | 153 | 21340 | +| ffjson** | regular | 146 | 152 | 4897 | +| ffjson | small | 36 | 5 | 384 | +| ffjson** | small | 64 | 4 | 128 | +| ffjson | large | 134 | 7317 | 818k | +| ffjson** | large | 125 | 7320 | 827k | +| | | | | | +| codec | regular | 80 | 17 | 33601 | +| codec*** | regular | 108 | 9 | 1153 | +| codec | small | 42 | 3 | 304 | +| codec*** | small | 56 | 1 | 48 | +| codec | large | 73 | 483 | 2.5M | +| codec*** | large | 103 | 451 | 66007 | +| | | | | | +| ujson | regular | 92 | N/A | N/A | + +\* marshaling to a writer, +\*\* using `ffjson.Pool()`, +\*\*\* reusing output slice instead of resetting it to nil + +#### Marshaling, concurrent. + +| lib | json size | MB/s | allocs/op | B/op | +|:----------|:----------|-----:|----------:|------:| +| standard | regular | 252 | 9 | 23257 | +| standard | small | 124 | 3 | 328 | +| standard | large | 289 | 17 | 1.2M | +| | | | | | +| easyjson | regular | 792 | 9 | 10597 | +| easyjson* | regular | 1748 | 8 | 779 | +| easyjson | small | 333 | 1 | 128 | +| easyjson | large | 718 | 36 | 548k | +| easyjson* | large | 2134 | 25 | 4957 | +| | | | | | +| ffjson | regular | 301 | 153 | 21629 | +| ffjson** | regular | 707 | 152 | 5148 | +| ffjson | small | 62 | 5 | 384 | +| ffjson** | small | 282 | 4 | 128 | +| ffjson | large | 438 | 7330 | 1.0M | +| ffjson** | large | 131 | 7319 | 820k | +| | | | | | +| codec | regular | 183 | 17 | 33603 | +| codec*** | regular | 671 | 9 | 1157 | +| codec | small | 147 | 3 | 304 | +| codec*** | small | 299 | 1 | 48 | +| codec | large | 190 | 483 | 2.5M | +| codec*** | large | 752 | 451 | 77574 | + +\* marshaling to a writer, +\*\* using `ffjson.Pool()`, +\*\*\* reusing output slice instead of resetting it to nil diff --git a/vendor/github.com/mailru/easyjson/go.mod b/vendor/github.com/mailru/easyjson/go.mod new file mode 100644 index 00000000000..7bc4a658442 --- /dev/null +++ b/vendor/github.com/mailru/easyjson/go.mod @@ -0,0 +1,3 @@ +module github.com/mailru/easyjson + +go 1.12 diff --git a/vendor/github.com/mailru/easyjson/helpers.go b/vendor/github.com/mailru/easyjson/helpers.go new file mode 100644 index 00000000000..b86b87d228a --- /dev/null +++ b/vendor/github.com/mailru/easyjson/helpers.go @@ -0,0 +1,78 @@ +// Package easyjson contains marshaler/unmarshaler interfaces and helper functions. +package easyjson + +import ( + "io" + "io/ioutil" + "net/http" + "strconv" + + "github.com/mailru/easyjson/jlexer" + "github.com/mailru/easyjson/jwriter" +) + +// Marshaler is an easyjson-compatible marshaler interface. +type Marshaler interface { + MarshalEasyJSON(w *jwriter.Writer) +} + +// Marshaler is an easyjson-compatible unmarshaler interface. +type Unmarshaler interface { + UnmarshalEasyJSON(w *jlexer.Lexer) +} + +// Optional defines an undefined-test method for a type to integrate with 'omitempty' logic. +type Optional interface { + IsDefined() bool +} + +// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied +// from a chain of smaller chunks. +func Marshal(v Marshaler) ([]byte, error) { + w := jwriter.Writer{} + v.MarshalEasyJSON(&w) + return w.BuildBytes() +} + +// MarshalToWriter marshals the data to an io.Writer. +func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { + jw := jwriter.Writer{} + v.MarshalEasyJSON(&jw) + return jw.DumpTo(w) +} + +// MarshalToHTTPResponseWriter sets Content-Length and Content-Type headers for the +// http.ResponseWriter, and send the data to the writer. started will be equal to +// false if an error occurred before any http.ResponseWriter methods were actually +// invoked (in this case a 500 reply is possible). +func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) { + jw := jwriter.Writer{} + v.MarshalEasyJSON(&jw) + if jw.Error != nil { + return false, 0, jw.Error + } + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(jw.Size())) + + started = true + written, err = jw.DumpTo(w) + return +} + +// Unmarshal decodes the JSON in data into the object. +func Unmarshal(data []byte, v Unmarshaler) error { + l := jlexer.Lexer{Data: data} + v.UnmarshalEasyJSON(&l) + return l.Error() +} + +// UnmarshalFromReader reads all the data in the reader and decodes as JSON into the object. +func UnmarshalFromReader(r io.Reader, v Unmarshaler) error { + data, err := ioutil.ReadAll(r) + if err != nil { + return err + } + l := jlexer.Lexer{Data: data} + v.UnmarshalEasyJSON(&l) + return l.Error() +} diff --git a/vendor/github.com/mailru/easyjson/raw.go b/vendor/github.com/mailru/easyjson/raw.go new file mode 100644 index 00000000000..81bd002e19f --- /dev/null +++ b/vendor/github.com/mailru/easyjson/raw.go @@ -0,0 +1,45 @@ +package easyjson + +import ( + "github.com/mailru/easyjson/jlexer" + "github.com/mailru/easyjson/jwriter" +) + +// RawMessage is a raw piece of JSON (number, string, bool, object, array or +// null) that is extracted without parsing and output as is during marshaling. +type RawMessage []byte + +// MarshalEasyJSON does JSON marshaling using easyjson interface. +func (v *RawMessage) MarshalEasyJSON(w *jwriter.Writer) { + if len(*v) == 0 { + w.RawString("null") + } else { + w.Raw(*v, nil) + } +} + +// UnmarshalEasyJSON does JSON unmarshaling using easyjson interface. +func (v *RawMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + *v = RawMessage(l.Raw()) +} + +// UnmarshalJSON implements encoding/json.Unmarshaler interface. +func (v *RawMessage) UnmarshalJSON(data []byte) error { + *v = data + return nil +} + +var nullBytes = []byte("null") + +// MarshalJSON implements encoding/json.Marshaler interface. +func (v RawMessage) MarshalJSON() ([]byte, error) { + if len(v) == 0 { + return nullBytes, nil + } + return v, nil +} + +// IsDefined is required for integration with omitempty easyjson logic. +func (v *RawMessage) IsDefined() bool { + return len(*v) > 0 +} diff --git a/vendor/github.com/tsenart/vegeta/lib/results_easyjson.go b/vendor/github.com/tsenart/vegeta/lib/results_easyjson.go deleted file mode 100644 index b1594bdc381..00000000000 --- a/vendor/github.com/tsenart/vegeta/lib/results_easyjson.go +++ /dev/null @@ -1,165 +0,0 @@ -// 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 (r *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": - r.Attack = string(in.String()) - case "seq": - r.Seq = uint64(in.Uint64()) - case "code": - r.Code = uint16(in.Uint16()) - case "timestamp": - if data := in.Raw(); in.Ok() { - in.AddError((r.Timestamp).UnmarshalJSON(data)) - } - case "latency": - r.Latency = time.Duration(in.Int64()) - case "bytes_out": - r.BytesOut = uint64(in.Uint64()) - case "bytes_in": - r.BytesIn = uint64(in.Uint64()) - case "error": - r.Error = string(in.String()) - case "body": - if in.IsNull() { - in.Skip() - r.Body = nil - } else { - r.Body = in.Bytes() - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} - -func (r 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(r.Attack)) - } - { - const prefix string = ",\"seq\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Uint64(uint64(r.Seq)) - } - { - const prefix string = ",\"code\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Uint16(uint16(r.Code)) - } - { - const prefix string = ",\"timestamp\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((r.Timestamp).MarshalJSON()) - } - { - const prefix string = ",\"latency\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int64(int64(r.Latency)) - } - { - const prefix string = ",\"bytes_out\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Uint64(uint64(r.BytesOut)) - } - { - const prefix string = ",\"bytes_in\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Uint64(uint64(r.BytesIn)) - } - { - const prefix string = ",\"error\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(r.Error)) - } - { - const prefix string = ",\"body\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Base64Bytes(r.Body) - } - out.RawByte('}') -} diff --git a/vendor/github.com/tsenart/vegeta/LICENSE b/vendor/github.com/tsenart/vegeta/v12/LICENSE similarity index 96% rename from vendor/github.com/tsenart/vegeta/LICENSE rename to vendor/github.com/tsenart/vegeta/v12/LICENSE index 441df0ab407..03ec4f5a824 100644 --- a/vendor/github.com/tsenart/vegeta/LICENSE +++ b/vendor/github.com/tsenart/vegeta/v12/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2016 Tomás Senart +Copyright (c) 2013-2020 Tomás Senart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/vendor/github.com/tsenart/vegeta/lib/attack.go b/vendor/github.com/tsenart/vegeta/v12/lib/attack.go similarity index 88% rename from vendor/github.com/tsenart/vegeta/lib/attack.go rename to vendor/github.com/tsenart/vegeta/v12/lib/attack.go index 51c4957db46..0294c628b72 100644 --- a/vendor/github.com/tsenart/vegeta/lib/attack.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/attack.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/url" + "strconv" "sync" "time" @@ -28,6 +29,7 @@ type Attacker struct { seqmu sync.Mutex seq uint64 began time.Time + chunked bool } const ( @@ -40,6 +42,9 @@ const ( // DefaultConnections is the default amount of max open idle connections per // target host. DefaultConnections = 10000 + // DefaultMaxConnections is the default amount of connections per target + // host. + DefaultMaxConnections = 0 // DefaultWorkers is the default initial number of workers used to carry an attack. DefaultWorkers = 10 // DefaultMaxWorkers is the default maximum number of workers used to carry an attack. @@ -81,6 +86,7 @@ func NewAttacker(opts ...func(*Attacker)) *Attacker { Dial: a.dialer.Dial, TLSClientConfig: DefaultTLSConfig, MaxIdleConnsPerHost: DefaultConnections, + MaxConnsPerHost: DefaultMaxConnections, }, } @@ -113,6 +119,21 @@ func Connections(n int) func(*Attacker) { } } +// MaxConnections returns a functional option which sets the number of maximum +// connections per target host. +func MaxConnections(n int) func(*Attacker) { + return func(a *Attacker) { + tr := a.client.Transport.(*http.Transport) + tr.MaxConnsPerHost = n + } +} + +// ChunkedBody returns a functional option which makes the attacker send the +// body of each request with the chunked transfer encoding. +func ChunkedBody(b bool) func(*Attacker) { + return func(a *Attacker) { a.chunked = b } +} + // Redirects returns a functional option which sets the maximum // number of redirects an Attacker will follow. func Redirects(n int) func(*Attacker) { @@ -229,6 +250,16 @@ func Client(c *http.Client) func(*Attacker) { return func(a *Attacker) { a.client = *c } } +// ProxyHeader returns a functional option that allows you to add your own +// Proxy CONNECT headers +func ProxyHeader(h http.Header) func(*Attacker) { + return func(a *Attacker) { + if tr, ok := a.client.Transport.(*http.Transport); ok { + tr.ProxyConnectHeader = h + } + } +} + // Attack reads its Targets from the passed Targeter and attacks them at // the rate specified by the Pacer. When the duration is zero the attack // runs until Stop is called. Results are sent to the returned channel as soon @@ -336,11 +367,24 @@ func (a *Attacker) hit(tr Targeter, name string) *Result { return &res } + res.Method = tgt.Method + res.URL = tgt.URL + req, err := tgt.Request() if err != nil { return &res } + if name != "" { + req.Header.Set("X-Vegeta-Attack", name) + } + + req.Header.Set("X-Vegeta-Seq", strconv.FormatUint(res.Seq, 10)) + + if a.chunked { + req.TransferEncoding = append(req.TransferEncoding, "chunked") + } + r, err := a.client.Do(req) if err != nil { return &res @@ -368,5 +412,7 @@ func (a *Attacker) hit(tr Targeter, name string) *Result { res.Error = r.Status } + res.Headers = r.Header + return &res } diff --git a/vendor/github.com/tsenart/vegeta/v12/lib/attack_fuzz.go b/vendor/github.com/tsenart/vegeta/v12/lib/attack_fuzz.go new file mode 100644 index 00000000000..e14eef61f07 --- /dev/null +++ b/vendor/github.com/tsenart/vegeta/v12/lib/attack_fuzz.go @@ -0,0 +1,154 @@ +// +build gofuzz + +package vegeta + +import ( + "encoding/binary" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "time" +) + +// FuzzAttackerTCP fuzzes binary responses to attacker. +func FuzzAttackerTCP(fuzz []byte) int { + // Ignore empty fuzz + if len(fuzz) == 0 { + return -1 + } + + // Start server + directory, err := ioutil.TempDir("/tmp", "fuzz") + if err != nil { + panic(err.Error()) + } + socket := fmt.Sprintf("%s/attacker.sock", directory) + listener, err := net.Listen("unix", socket) + if err != nil { + panic(err.Error()) + } + go func() { + connection, err := listener.Accept() + if err != nil { + panic(err.Error()) + } + _, err = connection.Write(fuzz) + if err != nil { + panic(err.Error()) + } + err = connection.Close() + if err != nil { + panic(err.Error()) + } + }() + defer listener.Close() + defer os.RemoveAll(directory) + + // Setup targeter + targeter := Targeter(func(target *Target) error { + target.Method = "GET" + target.URL = "http://vegeta.test" + return nil + }) + + // Deliver a single hit + attacker := NewAttacker( + UnixSocket(socket), + Workers(1), + MaxWorkers(1), + Timeout(time.Second), + KeepAlive(false), + ) + result := attacker.hit(targeter, "fuzz") + if result.Error != "" { + return 0 + } + return 1 +} + +// FuzzAttackerHTTP fuzzes valid HTTP responses to attacker. +func FuzzAttackerHTTP(fuzz []byte) int { + // Decode response + code, headers, body, ok := decodeFuzzResponse(fuzz) + if !ok { + return -1 + } + + // Start server + directory, err := ioutil.TempDir("/tmp", "fuzz") + if err != nil { + panic(err.Error()) + } + socket := fmt.Sprintf("%s/attacker.sock", directory) + listener, err := net.Listen("unix", socket) + if err != nil { + panic(err.Error()) + } + handler := func(response http.ResponseWriter, request *http.Request) { + for name, values := range headers { + for _, value := range values { + response.Header().Add(name, value) + } + } + response.WriteHeader(int(code)) + _, err := response.Write(body) + if err != nil { + panic(err.Error()) + } + } + server := http.Server{ + Handler: http.HandlerFunc(handler), + } + defer server.Close() + defer listener.Close() + defer os.RemoveAll(directory) + go server.Serve(listener) + + // Setup targeter + targeter := Targeter(func(target *Target) error { + target.Method = "GET" + target.URL = "http://vegeta.test" + return nil + }) + + // Deliver a single hit + attacker := NewAttacker( + UnixSocket(socket), + Workers(1), + MaxWorkers(1), + Timeout(time.Second), + KeepAlive(false), + ) + result := attacker.hit(targeter, "fuzz") + if result.Error != "" { + return 0 + } + return 1 +} + +func decodeFuzzResponse(fuzz []byte) ( + code int, + headers map[string][]string, + body []byte, + ok bool, +) { + if len(fuzz) < 2 { + return + } + headers = make(map[string][]string) + body = []byte{} + code = int(binary.LittleEndian.Uint16(fuzz[0:2])) + if len(fuzz) == 2 { + ok = true + return + } + fuzz, ok = decodeFuzzHeaders(fuzz[2:], headers) + if !ok { + return + } + body = fuzz + ok = true + return +} diff --git a/vendor/github.com/tsenart/vegeta/lib/histogram.go b/vendor/github.com/tsenart/vegeta/v12/lib/histogram.go similarity index 91% rename from vendor/github.com/tsenart/vegeta/lib/histogram.go rename to vendor/github.com/tsenart/vegeta/v12/lib/histogram.go index f8fd84462d6..acb546f19f3 100644 --- a/vendor/github.com/tsenart/vegeta/lib/histogram.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/histogram.go @@ -68,11 +68,15 @@ func (bs *Buckets) UnmarshalText(value []byte) error { if len(value) < 2 || value[0] != '[' || value[len(value)-1] != ']' { return fmt.Errorf("bad buckets: %s", value) } - for _, v := range strings.Split(string(value[1:len(value)-1]), ",") { + for i, v := range strings.Split(string(value[1:len(value)-1]), ",") { d, err := time.ParseDuration(strings.TrimSpace(v)) if err != nil { return err } + // add a default range of [0-Buckets[0]) if needed + if i == 0 && d > 0 { + *bs = append(*bs, 0) + } *bs = append(*bs, d) } if len(*bs) == 0 { diff --git a/vendor/github.com/tsenart/vegeta/lib/metrics.go b/vendor/github.com/tsenart/vegeta/v12/lib/metrics.go similarity index 94% rename from vendor/github.com/tsenart/vegeta/lib/metrics.go rename to vendor/github.com/tsenart/vegeta/v12/lib/metrics.go index 5b3cd626a07..5d7dbf9b25d 100644 --- a/vendor/github.com/tsenart/vegeta/lib/metrics.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/metrics.go @@ -89,6 +89,11 @@ func (m *Metrics) Add(r *Result) { // derived summary metrics which don't need to be run on every Add call. func (m *Metrics) Close() { m.init() + + if m.Requests == 0 { + return + } + m.Rate = float64(m.Requests) m.Throughput = float64(m.success) m.Duration = m.Latest.Sub(m.Earliest) @@ -104,6 +109,7 @@ func (m *Metrics) Close() { m.Success = float64(m.success) / float64(m.Requests) m.Latencies.Mean = time.Duration(float64(m.Latencies.Total) / float64(m.Requests)) m.Latencies.P50 = m.Latencies.Quantile(0.50) + m.Latencies.P90 = m.Latencies.Quantile(0.90) m.Latencies.P95 = m.Latencies.Quantile(0.95) m.Latencies.P99 = m.Latencies.Quantile(0.99) } @@ -130,12 +136,16 @@ type LatencyMetrics struct { Mean time.Duration `json:"mean"` // P50 is the 50th percentile request latency. P50 time.Duration `json:"50th"` + // P90 is the 90th percentile request latency. + P90 time.Duration `json:"90th"` // P95 is the 95th percentile request latency. P95 time.Duration `json:"95th"` // P99 is the 99th percentile request latency. P99 time.Duration `json:"99th"` // Max is the maximum observed request latency. Max time.Duration `json:"max"` + // Min is the minimum observed request latency. + Min time.Duration `json:"min"` estimator estimator } @@ -146,6 +156,9 @@ func (l *LatencyMetrics) Add(latency time.Duration) { if l.Total += latency; latency > l.Max { l.Max = latency } + if latency < l.Min || l.Min == 0 { + l.Min = latency + } l.estimator.Add(float64(latency)) } diff --git a/vendor/github.com/tsenart/vegeta/lib/pacer.go b/vendor/github.com/tsenart/vegeta/v12/lib/pacer.go similarity index 88% rename from vendor/github.com/tsenart/vegeta/lib/pacer.go rename to vendor/github.com/tsenart/vegeta/v12/lib/pacer.go index 97c8310be74..4c04d637fc6 100644 --- a/vendor/github.com/tsenart/vegeta/lib/pacer.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/pacer.go @@ -6,12 +6,17 @@ import ( "time" ) -// A Pacer defines the rate of hits during an Attack by -// returning the duration an Attacker should wait until -// hitting the next Target. If the second return value -// is true, the attack will terminate. +// A Pacer defines the rate of hits during an Attack. type Pacer interface { + // Pace returns the duration an Attacker should wait until + // hitting the next Target, given an already elapsed duration and + // completed hits. If the second return value is true, an attacker + // should stop sending hits. Pace(elapsed time.Duration, hits uint64) (wait time.Duration, stop bool) + + // Rate returns a Pacer's instantaneous hit rate (per seconds) + // at the given elapsed duration of an attack. + Rate(elapsed time.Duration) float64 } // A PacerFunc is a function adapter type that implements @@ -65,6 +70,13 @@ func (cp ConstantPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, return delta - elapsed, false } +// Rate returns a ConstantPacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration of an attack. Since it's constant, the return +// value is independent of the given elapsed duration. +func (cp ConstantPacer) Rate(elapsed time.Duration) float64 { + return cp.hitsPerNs() * 1e9 +} + // hitsPerNs returns the attack rate this ConstantPacer represents, in // fractional hits per nanosecond. func (cp ConstantPacer) hitsPerNs() float64 { @@ -184,6 +196,12 @@ func (sp SinePacer) Pace(elapsedTime time.Duration, elapsedHits uint64) (time.Du return nextHitIn, false } +// Rate returns a SinePacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration of an attack. +func (sp SinePacer) Rate(elapsed time.Duration) float64 { + return sp.hitsPerNs(elapsed) * 1e9 +} + // ampHits returns AP/2𝛑, which is the number of hits added or subtracted // from the Mean due to the Amplitude over a quarter of the Period, // i.e. from 0 → 𝛑/2 radians @@ -240,7 +258,7 @@ func (p LinearPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, bo return 0, false } - rate := p.rate(elapsed) + rate := p.Rate(elapsed) interval := math.Round(1e9 / rate) if n := uint64(interval); n != 0 && math.MaxInt64/n < hits { @@ -254,6 +272,15 @@ func (p LinearPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, bo return wait, false } +// Rate returns a LinearPacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration of an attack. +func (p LinearPacer) Rate(elapsed time.Duration) float64 { + a := p.Slope + x := elapsed.Seconds() + b := p.StartAt.hitsPerNs() * 1e9 + return a*x + b +} + // hits returns the number of hits that have been sent during an attack // lasting t nanoseconds. It returns a float so we can tell exactly how // much we've missed our target by when solving numerically in Pace. @@ -268,12 +295,3 @@ func (p LinearPacer) hits(t time.Duration) float64 { return (a*math.Pow(x, 2))/2 + b*x } - -// rate calculates the instantaneous rate of attack at -// t nanoseconds after the attack began. -func (p LinearPacer) rate(t time.Duration) float64 { - a := p.Slope - x := t.Seconds() - b := p.StartAt.hitsPerNs() * 1e9 - return a*x + b -} diff --git a/vendor/github.com/tsenart/vegeta/lib/reporters.go b/vendor/github.com/tsenart/vegeta/v12/lib/reporters.go similarity index 88% rename from vendor/github.com/tsenart/vegeta/lib/reporters.go rename to vendor/github.com/tsenart/vegeta/v12/lib/reporters.go index e86a8fbdfe4..aa7e4251399 100644 --- a/vendor/github.com/tsenart/vegeta/lib/reporters.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/reporters.go @@ -57,7 +57,7 @@ func NewHistogramReporter(h *Histogram) Reporter { func NewTextReporter(m *Metrics) Reporter { const fmtstr = "Requests\t[total, rate, throughput]\t%d, %.2f, %.2f\n" + "Duration\t[total, attack, wait]\t%s, %s, %s\n" + - "Latencies\t[mean, 50, 95, 99, max]\t%s, %s, %s, %s, %s\n" + + "Latencies\t[min, mean, 50, 90, 95, 99, max]\t%s, %s, %s, %s, %s, %s, %s\n" + "Bytes In\t[total, mean]\t%d, %.2f\n" + "Bytes Out\t[total, mean]\t%d, %.2f\n" + "Success\t[ratio]\t%.2f%%\n" + @@ -67,8 +67,16 @@ func NewTextReporter(m *Metrics) Reporter { tw := tabwriter.NewWriter(w, 0, 8, 2, ' ', tabwriter.StripEscape) if _, err = fmt.Fprintf(tw, fmtstr, m.Requests, m.Rate, m.Throughput, - m.Duration+m.Wait, m.Duration, m.Wait, - m.Latencies.Mean, m.Latencies.P50, m.Latencies.P95, m.Latencies.P99, m.Latencies.Max, + round(m.Duration+m.Wait), + round(m.Duration), + round(m.Wait), + round(m.Latencies.Min), + round(m.Latencies.Mean), + round(m.Latencies.P50), + round(m.Latencies.P90), + round(m.Latencies.P95), + round(m.Latencies.P99), + round(m.Latencies.Max), m.BytesIn.Total, m.BytesIn.Mean, m.BytesOut.Total, m.BytesOut.Mean, m.Success*100, @@ -104,6 +112,25 @@ func NewTextReporter(m *Metrics) Reporter { } } +var durations = [...]time.Duration{ + time.Hour, + time.Minute, + time.Second, + time.Millisecond, + time.Microsecond, + time.Nanosecond, +} + +// round to the next most precise unit +func round(d time.Duration) time.Duration { + for i, unit := range durations { + if d >= unit && i < len(durations)-1 { + return d.Round(durations[i+1]) + } + } + return d +} + // NewJSONReporter returns a Reporter that writes out Metrics as JSON. func NewJSONReporter(m *Metrics) Reporter { return func(w io.Writer) error { diff --git a/vendor/github.com/tsenart/vegeta/lib/results.go b/vendor/github.com/tsenart/vegeta/v12/lib/results.go similarity index 78% rename from vendor/github.com/tsenart/vegeta/lib/results.go rename to vendor/github.com/tsenart/vegeta/v12/lib/results.go index 2754cb51860..eacd42d0188 100644 --- a/vendor/github.com/tsenart/vegeta/lib/results.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/results.go @@ -7,12 +7,15 @@ import ( "encoding/csv" "encoding/gob" "io" + "net/http" + "net/textproto" "sort" "strconv" + "strings" "time" "github.com/mailru/easyjson/jlexer" - jwriter "github.com/mailru/easyjson/jwriter" + "github.com/mailru/easyjson/jwriter" ) func init() { @@ -30,6 +33,9 @@ type Result struct { BytesIn uint64 `json:"bytes_in"` Error string `json:"error"` Body []byte `json:"body"` + Method string `json:"method"` + URL string `json:"url"` + Headers http.Header `json:"headers"` } // End returns the time at which a Result ended. @@ -45,7 +51,32 @@ func (r Result) Equal(other Result) bool { r.BytesIn == other.BytesIn && r.BytesOut == other.BytesOut && r.Error == other.Error && - bytes.Equal(r.Body, other.Body) + bytes.Equal(r.Body, other.Body) && + r.Method == other.Method && + r.URL == other.URL && + headerEqual(r.Headers, other.Headers) +} + +func headerEqual(h1, h2 http.Header) bool { + if len(h1) != len(h2) { + return false + } + if h1 == nil || h2 == nil { + return h1 == nil && h2 == nil + } + for key, values1 := range h1 { + values2 := h2[key] + if len(values1) != len(values2) { + return false + } + for i := range values1 { + if values1[i] != values2[i] { + return false + } + } + } + + return true } // Results is a slice of Result type elements. @@ -150,8 +181,10 @@ func NewCSVEncoder(w io.Writer) Encoder { base64.StdEncoding.EncodeToString(r.Body), r.Attack, strconv.FormatUint(r.Seq, 10), + r.Method, + r.URL, + base64.StdEncoding.EncodeToString(headerBytes(r.Headers)), }) - if err != nil { return err } @@ -162,10 +195,19 @@ func NewCSVEncoder(w io.Writer) Encoder { } } +func headerBytes(h http.Header) []byte { + if h == nil { + return nil + } + var hdr bytes.Buffer + _ = h.Write(&hdr) + return append(hdr.Bytes(), '\r', '\n') +} + // NewCSVDecoder returns a Decoder that decodes CSV encoded Results. -func NewCSVDecoder(rd io.Reader) Decoder { - dec := csv.NewReader(rd) - dec.FieldsPerRecord = 9 +func NewCSVDecoder(r io.Reader) Decoder { + dec := csv.NewReader(r) + dec.FieldsPerRecord = 12 dec.TrimLeadingSpace = true return func(r *Result) error { @@ -201,23 +243,42 @@ func NewCSVDecoder(rd io.Reader) Decoder { } r.Error = rec[5] - r.Body, err = base64.StdEncoding.DecodeString(rec[6]) + if r.Body, err = base64.StdEncoding.DecodeString(rec[6]); err != nil { + return err + } r.Attack = rec[7] if r.Seq, err = strconv.ParseUint(rec[8], 10, 64); err != nil { return err } + r.Method = rec[9] + r.URL = rec[10] + + if rec[11] != "" { + pr := textproto.NewReader(bufio.NewReader( + base64.NewDecoder(base64.StdEncoding, strings.NewReader(rec[11])))) + hdr, err := pr.ReadMIMEHeader() + if err != nil { + return err + } + r.Headers = http.Header(hdr) + } + return err } } +//go:generate easyjson -no_std_marshalers -output_filename results_easyjson.go results.go +//easyjson:json +type jsonResult Result + // NewJSONEncoder returns an Encoder that dumps the given *Results as a JSON // object. func NewJSONEncoder(w io.Writer) Encoder { var jw jwriter.Writer return func(r *Result) error { - (*jsonResult)(r).encode(&jw) + (*jsonResult)(r).MarshalEasyJSON(&jw) if jw.Error != nil { return jw.Error } @@ -232,10 +293,10 @@ func NewJSONDecoder(r io.Reader) Decoder { rd := bufio.NewReader(r) return func(r *Result) (err error) { var jl jlexer.Lexer - if jl.Data, err = rd.ReadSlice('\n'); err != nil { + if jl.Data, err = rd.ReadBytes('\n'); err != nil { return err } - (*jsonResult)(r).decode(&jl) + (*jsonResult)(r).UnmarshalEasyJSON(&jl) return jl.Error() } } diff --git a/vendor/github.com/tsenart/vegeta/v12/lib/results_easyjson.go b/vendor/github.com/tsenart/vegeta/v12/lib/results_easyjson.go new file mode 100644 index 00000000000..3beb22f7d83 --- /dev/null +++ b/vendor/github.com/tsenart/vegeta/v12/lib/results_easyjson.go @@ -0,0 +1,223 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package vegeta + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" + http "net/http" + time "time" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonBd1621b8DecodeGithubComTsenartVegetaV12Lib(in *jlexer.Lexer, out *jsonResult) { + 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() + } + case "method": + out.Method = string(in.String()) + case "url": + out.URL = string(in.String()) + case "headers": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Headers = make(http.Header) + } else { + out.Headers = 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.Headers)[key] = v2 + in.WantComma() + } + in.Delim('}') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonBd1621b8EncodeGithubComTsenartVegetaV12Lib(out *jwriter.Writer, in jsonResult) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"attack\":" + out.RawString(prefix[1:]) + out.String(string(in.Attack)) + } + { + const prefix string = ",\"seq\":" + out.RawString(prefix) + out.Uint64(uint64(in.Seq)) + } + { + const prefix string = ",\"code\":" + out.RawString(prefix) + out.Uint16(uint16(in.Code)) + } + { + const prefix string = ",\"timestamp\":" + out.RawString(prefix) + out.Raw((in.Timestamp).MarshalJSON()) + } + { + const prefix string = ",\"latency\":" + out.RawString(prefix) + out.Int64(int64(in.Latency)) + } + { + const prefix string = ",\"bytes_out\":" + out.RawString(prefix) + out.Uint64(uint64(in.BytesOut)) + } + { + const prefix string = ",\"bytes_in\":" + out.RawString(prefix) + out.Uint64(uint64(in.BytesIn)) + } + { + const prefix string = ",\"error\":" + out.RawString(prefix) + out.String(string(in.Error)) + } + { + const prefix string = ",\"body\":" + out.RawString(prefix) + out.Base64Bytes(in.Body) + } + { + const prefix string = ",\"method\":" + out.RawString(prefix) + out.String(string(in.Method)) + } + { + const prefix string = ",\"url\":" + out.RawString(prefix) + out.String(string(in.URL)) + } + { + const prefix string = ",\"headers\":" + out.RawString(prefix) + if in.Headers == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { + out.RawString(`null`) + } else { + out.RawByte('{') + v6First := true + for v6Name, v6Value := range in.Headers { + 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('}') +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v jsonResult) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonBd1621b8EncodeGithubComTsenartVegetaV12Lib(w, v) +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *jsonResult) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonBd1621b8DecodeGithubComTsenartVegetaV12Lib(l, v) +} diff --git a/vendor/github.com/tsenart/vegeta/v12/lib/results_fuzz.go b/vendor/github.com/tsenart/vegeta/v12/lib/results_fuzz.go new file mode 100644 index 00000000000..fced203da4a --- /dev/null +++ b/vendor/github.com/tsenart/vegeta/v12/lib/results_fuzz.go @@ -0,0 +1,63 @@ +// +build gofuzz + +package vegeta + +import ( + "bytes" + "io" +) + +// FuzzResultsFormatDetection tests result list format detection. +func FuzzResultsFormatDetection(fuzz []byte) int { + decoder := DecoderFor(bytes.NewReader(fuzz)) + if decoder == nil { + return 0 + } + ok := readAllResults(decoder) + if !ok { + return 0 + } + return 1 +} + +// FuzzGobDecoder tests decoding a gob format result list. +func FuzzGobDecoder(fuzz []byte) int { + decoder := NewDecoder(bytes.NewReader(fuzz)) + ok := readAllResults(decoder) + if !ok { + return 0 + } + return 1 +} + +// FuzzCSVDecoder tests decoding a CSV format result list. +func FuzzCSVDecoder(fuzz []byte) int { + decoder := NewCSVDecoder(bytes.NewReader(fuzz)) + ok := readAllResults(decoder) + if !ok { + return 0 + } + return 1 +} + +// FuzzJSONDecoder tests decoding a JSON format result list. +func FuzzJSONDecoder(fuzz []byte) int { + decoder := NewJSONDecoder(bytes.NewReader(fuzz)) + ok := readAllResults(decoder) + if !ok { + return 0 + } + return 1 +} + +func readAllResults(decoder Decoder) (ok bool) { + for { + result := &Result{} + err := decoder.Decode(result) + if err == io.EOF { + return true + } else if err != nil { + return false + } + } +} diff --git a/vendor/github.com/tsenart/vegeta/lib/target.schema.json b/vendor/github.com/tsenart/vegeta/v12/lib/target.schema.json similarity index 100% rename from vendor/github.com/tsenart/vegeta/lib/target.schema.json rename to vendor/github.com/tsenart/vegeta/v12/lib/target.schema.json diff --git a/vendor/github.com/tsenart/vegeta/lib/targets.go b/vendor/github.com/tsenart/vegeta/v12/lib/targets.go similarity index 98% rename from vendor/github.com/tsenart/vegeta/lib/targets.go rename to vendor/github.com/tsenart/vegeta/v12/lib/targets.go index 41415ffb51f..2cb5dc2cfb5 100644 --- a/vendor/github.com/tsenart/vegeta/lib/targets.go +++ b/vendor/github.com/tsenart/vegeta/v12/lib/targets.go @@ -270,7 +270,7 @@ func NewHTTPTargeter(src io.Reader, body []byte, hdr http.Header) Targeter { } line = strings.TrimSpace(sc.Text()) - if len(line) != 0 && line[0] != '#'{ + if len(line) != 0 && line[0] != '#' { break } } @@ -300,6 +300,8 @@ func NewHTTPTargeter(src io.Reader, body []byte, hdr http.Header) Targeter { for sc.Scan() { if line = strings.TrimSpace(sc.Text()); line == "" { break + } else if strings.HasPrefix(line, "#") { + continue } else if strings.HasPrefix(line, "@") { if tgt.Body, err = ioutil.ReadFile(line[1:]); err != nil { return fmt.Errorf("bad body: %s", err) @@ -327,7 +329,7 @@ func NewHTTPTargeter(src io.Reader, body []byte, hdr http.Header) Targeter { } } -var httpMethodChecker = regexp.MustCompile("^[A-Z]+\\s") +var httpMethodChecker = regexp.MustCompile(`^[A-Z]+\s`) // A line starts with an http method when the first word is uppercase ascii // followed by a space. diff --git a/vendor/github.com/tsenart/vegeta/lib/targets_easyjson.go b/vendor/github.com/tsenart/vegeta/v12/lib/targets_easyjson.go similarity index 100% rename from vendor/github.com/tsenart/vegeta/lib/targets_easyjson.go rename to vendor/github.com/tsenart/vegeta/v12/lib/targets_easyjson.go diff --git a/vendor/github.com/tsenart/vegeta/v12/lib/targets_fuzz.go b/vendor/github.com/tsenart/vegeta/v12/lib/targets_fuzz.go new file mode 100644 index 00000000000..87a925fe1b2 --- /dev/null +++ b/vendor/github.com/tsenart/vegeta/v12/lib/targets_fuzz.go @@ -0,0 +1,68 @@ +// +build gofuzz + +package vegeta + +import ( + "bytes" + "net/http" +) + +// FuzzHTTPTargeter tests decoding an HTTP encoded target list. +func FuzzHTTPTargeter(fuzz []byte) int { + headers, body, fuzz, ok := decodeFuzzTargetDefaults(fuzz) + if !ok { + return -1 + } + targeter := NewHTTPTargeter( + bytes.NewReader(fuzz), + body, + headers, + ) + _, err := ReadAllTargets(targeter) + if err != nil { + return 0 + } + return 1 +} + +// FuzzJSONTargeter tests decoding a JSON encoded target list. +func FuzzJSONTargeter(fuzz []byte) int { + headers, body, fuzz, ok := decodeFuzzTargetDefaults(fuzz) + if !ok { + return -1 + } + targeter := NewJSONTargeter( + bytes.NewReader(fuzz), + body, + headers, + ) + _, err := ReadAllTargets(targeter) + if err != nil { + return 0 + } + return 1 +} + +func decodeFuzzTargetDefaults(fuzz []byte) ( + headers http.Header, + body []byte, + rest []byte, + ok bool, +) { + if len(fuzz) < 2 { + return + } + headers = make(map[string][]string) + body = []byte{} + rest = []byte{} + rest, ok = decodeFuzzHeaders(fuzz, headers) + if !ok { + return + } + if len(rest) == 0 { + ok = true + return + } + body, rest, ok = extractFuzzByteString(rest) + return +} diff --git a/vendor/github.com/tsenart/vegeta/v12/lib/util_fuzz.go b/vendor/github.com/tsenart/vegeta/v12/lib/util_fuzz.go new file mode 100644 index 00000000000..60c758feac1 --- /dev/null +++ b/vendor/github.com/tsenart/vegeta/v12/lib/util_fuzz.go @@ -0,0 +1,120 @@ +// +build gofuzz + +package vegeta + +func decodeFuzzHeaders(fuzz []byte, headers map[string][]string) ( + rest []byte, + ok bool, +) { + rest = fuzz + for { + if len(rest) == 0 { + // Consumed all fuzz + ok = true + return + } + if fuzz[0] == 0 { + // Headers terminated + if len(rest) == 1 { + rest = []byte{} + } else { + rest = rest[1:] + } + ok = true + return + } + if len(fuzz) == 1 { + // Invalid headers encoding + return + } + rest, ok = decodeFuzzHeader(rest[1:], headers) + if !ok { + return + } + } +} + +func decodeFuzzHeader(fuzz []byte, headers map[string][]string) ( + rest []byte, + ok bool, +) { + if len(fuzz) == 0 { + ok = true + return + } + name, rest, ok := extractFuzzString(fuzz) + if !ok { + return + } + value, rest, ok := extractFuzzString(rest) + if !ok { + return + } + if header, ok := headers[name]; ok { + headers[name] = append(header, value) + } else { + headers[name] = []string{value} + } + ok = true + return +} + +func extractFuzzString(fuzz []byte) ( + value string, + rest []byte, + ok bool, +) { + if len(fuzz) < 2 { + // Invalid string encoding + return + } + length := int(fuzz[0]) + if length == 0 { + // Invalid length + return + } + if len(fuzz) < (length + 1) { + // Insufficient fuzz + return + } + value = string(fuzz[1 : length+1]) + if len(fuzz) == (length + 1) { + // Consumed all fuzz + rest = []byte{} + } else { + // More fuzz + rest = fuzz[length+1:] + } + ok = true + return +} + +func extractFuzzByteString(fuzz []byte) ( + value []byte, + rest []byte, + ok bool, +) { + if len(fuzz) < 2 { + // Invalid byte string encoding + return + } + length := int(fuzz[0]) + if length == 0 { + // Invalid length + return + } + if len(fuzz) < (length + 1) { + // Insufficient fuzz + return + } + value = fuzz[1 : length+1] + if len(fuzz) == (length + 1) { + // Consumed all fuzz + rest = []byte{} + } else { + // More fuzz + rest = fuzz[length+1:] + } + ok = true + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7eec4176117..69bb18f8de2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -216,6 +216,7 @@ github.com/konsorten/go-windows-terminal-sequences github.com/lightstep/tracecontext.go/traceparent github.com/lightstep/tracecontext.go/tracestate # github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e +github.com/mailru/easyjson github.com/mailru/easyjson/buffer github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter @@ -291,9 +292,9 @@ github.com/spf13/pflag ## explicit github.com/stretchr/testify/assert github.com/stretchr/testify/require -# github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible +# github.com/tsenart/vegeta/v12 v12.8.4 ## explicit -github.com/tsenart/vegeta/lib +github.com/tsenart/vegeta/v12/lib # github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/bytebufferpool # github.com/wavesoftware/go-ensure v1.0.0