Showing preview only (772K chars total). Download the full file or copy to clipboard to get everything.
Repository: tsenart/vegeta
Branch: master
Commit: cf5811269046
Files: 70
Total size: 742.9 KB
Directory structure:
gitextract_wz3_b8r2/
├── .github/
│ ├── CODEOWNERS
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── CHANGELOG
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── attack.go
├── attack_nonwindows.go
├── attack_test.go
├── attack_windows.go
├── dump.go
├── encode.go
├── file.go
├── flags.go
├── go.mod
├── go.sum
├── internal/
│ ├── cmd/
│ │ ├── echosrv/
│ │ │ └── main.go
│ │ └── jsonschema/
│ │ └── main.go
│ └── resolver/
│ ├── resolver.go
│ └── resolver_test.go
├── lib/
│ ├── attack.go
│ ├── attack_fuzz.go
│ ├── attack_test.go
│ ├── histogram.go
│ ├── histogram_test.go
│ ├── lttb/
│ │ ├── lttb.go
│ │ └── lttb_test.go
│ ├── metrics.go
│ ├── metrics_test.go
│ ├── pacer.go
│ ├── pacer_test.go
│ ├── plot/
│ │ ├── assets/
│ │ │ ├── VERSIONS
│ │ │ ├── plot.html.tpl
│ │ │ └── uplot-plugins.js
│ │ ├── assets.go
│ │ ├── embed.go
│ │ ├── plot.go
│ │ ├── plot_test.go
│ │ ├── testdata/
│ │ │ └── TestPlot.golden.html
│ │ └── timeseries.go
│ ├── prom/
│ │ ├── grafana.json
│ │ ├── prom.go
│ │ └── prom_test.go
│ ├── reporters.go
│ ├── results.go
│ ├── results_easyjson.go
│ ├── results_fuzz.go
│ ├── results_test.go
│ ├── target.schema.json
│ ├── targets.go
│ ├── targets_easyjson.go
│ ├── targets_fuzz.go
│ ├── targets_test.go
│ └── util_fuzz.go
├── main.go
├── plot.go
├── report.go
├── report_nonwindows.go
├── report_windows.go
└── scripts/
└── load-ramping/
├── README.md
├── ramp-requests.plt
└── ramp-requests.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
* @tsenart @xla
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
Non trivial changes should be discussed with the project maintainers by
opening a [Feature Request](https://github.com/tsenart/vegeta/issues/new?template=feature_request.md),
clearly explaining rationale, background and possible implementation ideas.
Feel free to use to provide code in such discussions.
## Implementation
Work should happen in an open pull request with a WIP label
which gives visibility to the development process and provides
continuous integration feedback.
The pull request description must be well written and provide the necessary
context and background for review. If there's a proposal issue, it must be
referenced. When ready, remove the WIP label assign the PR a reviewer who will
review your PR.
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: 🐛 Bug Report
about: Report a bug to help Vegeta improve.
---
<!-- ⚠️ If you do not respect this template your bug report issue will be closed. -->
#### Version and Runtime
```
Paste the output of `vegeta -version` here.
If you are not running the latest version of Vegeta, please try upgrading because your issue may have already been fixed.
```
#### Expected Behaviour
<!-- What did you expect to see? -->
#### Actual Behaviour
<!-- What did you see instead? -->
#### Steps to Reproduce
<!-- Please list the full steps required to reproduce the bug. -->
1. Step 1
1. Step N
#### Additional Context
<!--
Are there any other related GitHub issues (open or closed) or Pull Requests that should be linked here?
Is there anything atypical to be known about your situation?
Anything else?
-->
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: 🚀 Feature Request
about: Propose a change to Vegeta💡!
---
<!-- ⚠️ If you do not respect this template your issue will be closed. -->
<!-- ⚠️ Make sure to browse the opened and closed issues before submit your issue. -->
#### Proposal
<!-- Write your feature request in the form of a proposal to be considered for implementation -->
#### Background
<!-- Describe the background problem or need that led to this feature request -->
#### Workarounds
<!-- Are there any current workarounds that you're using that others in similar positions should know about? -->
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: ❓ Question
about: Ask a question that isn't a feature request nor a bug report.
---
#### Question
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
#### Background
<!-- Required background information to understand the PR. Link here any related issues. -->
#### Checklist
- [ ] Git commit messages conform to [community standards](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- [ ] Each Git commit represents meaningful milestones or atomic units of work.
- [ ] Changed or added code is covered by appropriate tests.
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
tags:
- "v*.*.*"
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
version:
description: "Release version"
required: true
jobs:
qa:
name: Quality Assurance
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Format Check
run: |
set -euo pipefail
go install golang.org/x/tools/cmd/goimports@latest
goimports -w .
git diff --exit-code
- name: Vet
run: go vet ./...
- name: Test
run: go test -race ./...
build:
name: Build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
strategy:
matrix:
target:
- "windows/amd64"
- "windows/386"
- "windows/arm64"
- "linux/amd64"
- "linux/386"
- "linux/arm64"
- "linux/arm"
- "darwin/amd64"
- "darwin/arm64"
- "freebsd/386"
- "freebsd/amd64"
- "freebsd/arm"
- "openbsd/amd64"
- "openbsd/arm64"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Set up GOOS and GOARCH
id: setup_env
run: |
echo "goos=$(echo ${{ matrix.target }} | cut -d'/' -f1)" >> $GITHUB_OUTPUT
echo "goarch=$(echo ${{ matrix.target }} | cut -d'/' -f2)" >> $GITHUB_OUTPUT
- name: Build
env:
GOOS: ${{ steps.setup_env.outputs.goos }}
GOARCH: ${{ steps.setup_env.outputs.goarch }}
run: |
set -euo pipefail
make vegeta
VERSION=${GITHUB_REF#refs/tags/v}
NAME="vegeta_${VERSION}_${GOOS}_${GOARCH}"
if [[ "$GOOS" != "windows" ]]; then
tar -czf "$NAME.tar.gz" vegeta
else
zip "$NAME.zip" vegeta.exe
fi
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: vegeta_${{ steps.setup_env.outputs.goos }}_${{ steps.setup_env.outputs.goarch }}
path: |
*.zip
*.tar.gz
release:
name: Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [qa, build]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download Artifacts
uses: actions/download-artifact@v4
- name: Create checksums and sign them
id: sign
run: |
set -euo pipefail
mkdir dist
mv vegeta*/* dist/
cd dist
VERSION=${GITHUB_REF#refs/tags/v}
CHECKSUMS=vegeta_${VERSION}_checksums.txt
sha256sum * > $CHECKSUMS
echo "${{ secrets.VEGETA_GPG_KEY }}" | gpg --batch --yes --pinentry-mode loopback --import
gpg --export --armor > vegeta_${VERSION}_pubkey.asc
gpg --detach-sign -a $CHECKSUMS
echo "name=${VERSION}" >> $GITHUB_OUTPUT
- name: Generate release notes
id: release_notes
run: |
set -x
set -euo pipefail
CURRENT_VERSION=${GITHUB_REF#refs/tags/}
PREV_VERSION=$(git describe --tags --abbrev=0 $CURRENT_VERSION^)
RELEASE_NOTES=${{ github.workspace }}/release-notes.txt
printf "## Changelog\n\n" > $RELEASE_NOTES
git log ${PREV_VERSION}..${CURRENT_VERSION} --oneline --abbrev-commit >> $RELEASE_NOTES
cat $RELEASE_NOTES
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
name: ${{ steps.sign.outputs.version }}
body_path: ${{ github.workspace }}/release-notes.txt
files: |
dist/*
tag_name: ${{ steps.sign.outputs.version }}
draft: true
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
vegeta
vegeta.test
vegeta-*.tar.gz
lib/lib.test
dist
vendor
*.gob
*.lz
.DS_Store
================================================
FILE: CHANGELOG
================================================
2018-05-18: v7.0.0
Include response body in hit results (#279)
Added support for h2c requests (HTTP/2 without TLS) (#261)
Prevent "null" Metrics.Errors JSON encoding (#277)
Add option to override HTTP Proxy on Attacker (#234)
2017-03-19: v6.3.0
Mark responses as success in no redirect following mode (#222)
2017-03-04: v6.2.0
Allow any upper-case ASCII word to be an HTTP method (#217)
Correctly compute Metrics.Rate with sub-second duration results (#208)
2016-08-26: v6.1.1
Respect case sensitivity in target file header names (#195, #191)
2016-04-03: v6.1.0
Add HTTP2 support
2015-11-27: v6.0.0
Insecure attack flag (#160)
Client certificates (#156)
Infinite attacks (#155)
Allow empty lines between targets (#147)
2015-09-19: v5.9.0
Bounded memory streaming reporters (#136)
2015-09-04: v5.8.1
Fix support for DELETE methods in targets
2015-08-11: v5.8.0
Change reporters quantile estimation method to match R's 8th type.
2015-05-23: v5.7.1
Revert end-to-end attack timeout change
2015-05-23: v5.7.0
Allow case sensitve headers in attacks
2015-04-15: v5.6.3
Expose connections flag in the attack command
Add global cpu and heap profiling flags
Measure actual attack rate and print it in relevant reporters
Major performance improvements that allow much higher attack rates
2015-04-02: v5.6.2
Update dygraph to latest version
Improve plot reporter screenshot rendering by using html2canvas.js
Improve plot reporter performance
2015-03-23: v5.6.1
Allow spaces in hist reporter flag format
2015-03-12: v5.6.0
Set default dumper to "json" in the dump command.
Add --version to global vegeta command flags.
Fix response body leak regression introduced in v5.5.3.
2015-03-11: v5.5.3
Always read response bodies for each request.
Homebrew install instructions.
2015-01-3: v5.5.2
Refactor core request logic and simplify tests with a 4x speedup.
2015-01-2: v5.5.1
Treat bad status codes as errors.
2014-11-21: v5.5.0
Implement dump command with CSV and JSON record format.
Optionally ignore redirects and treat them as successes.
2014-11-16: v5.4.0
Add histogram reporter to the report command.
2014-11-16: v5.3.0
Add support for extended targets dsl that supports per-target headers and body.
Target file comments support has been removed.
2014-11-7: v5.2.0
Don't treat 3xx status codes as errors.
Add -keepalive flag to the attack command.
2014-11-3: v5.1.1
Add FreeBSD and Windows releases.
Fix non termination bug in the report command. #85
================================================
FILE: Dockerfile
================================================
FROM golang:1.20-alpine3.18 AS BUILD
RUN apk add make build-base git
WORKDIR /vegeta
# cache dependencies
ADD go.mod /vegeta
ADD go.sum /vegeta
RUN go mod download
ADD . /vegeta
RUN make generate
RUN make vegeta
FROM alpine:3.18.0
COPY --from=BUILD /vegeta/vegeta /bin/vegeta
ENTRYPOINT ["vegeta"]
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013-2023 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
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Makefile
================================================
COMMIT=$(shell git rev-parse HEAD)
VERSION=$(shell git describe --tags --exact-match --always)
DATE=$(shell date +'%FT%TZ%z')
vegeta: generate
CGO_ENABLED=0 go build -v -a -tags=netgo \
-ldflags '-s -w -extldflags "-static" -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.Date=$(DATE)'
generate: GOARCH := $(shell go env GOHOSTARCH)
generate: GOOS := $(shell go env GOHOSTOS)
generate:
go install github.com/mailru/easyjson/...@latest
go get github.com/shurcooL/vfsgen
go install github.com/shurcooL/vfsgen/...@latest
go generate ./...
================================================
FILE: README.md
================================================
# Vegeta [](https://github.com/tsenart/vegeta/actions) [](https://goreportcard.com/report/github.com/tsenart/vegeta) [](https://pkg.go.dev/github.com/tsenart/vegeta/v12/lib) [](https://gitter.im/tsenart/vegeta?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](#donate)
Vegeta is a versatile HTTP load testing tool built out of a need to drill
HTTP services with a constant request rate. [It's over 9000!](https://en.wikipedia.org/wiki/It's_Over_9000)

## Features
- Usable as a command line tool and a Go library.
- CLI designed with UNIX composability in mind.
- [Avoids](https://github.com/tsenart/vegeta/pull/92/files#r20198929) nasty [Coordinated Omission](http://highscalability.com/blog/2015/10/5/your-load-generator-is-probably-lying-to-you-take-the-red-pi.html).
- Extensive reporting functionality.
- Simple to use for [distributed load testing](https://kubernetes.io/blog/2015/11/one-million-requests-per-second-dependable-and-dynamic-distributed-systems-at-scale/).
- Easy to install and run (static binary, package managers, etc).
## Install
### Pre-compiled executables
Get them [here](http://github.com/tsenart/vegeta/releases).
### macOS
You can install Vegeta using the [Homebrew](https://github.com/Homebrew/homebrew/):
```shell
$ brew update && brew install vegeta
```
Or with [MacPorts](https://www.macports.org/):
```shell
$ port install vegeta
```
### Arch Linux
```shell
$ pacman -S vegeta
```
### FreeBSD
On FreeBSD you can install Vegeta with the built in package manager because there is a [Vegeta Package](https://www.freshports.org/benchmarks/vegeta) available.
```shell
$ pkg install vegeta
```
### Source
```shell
git clone https://github.com/tsenart/vegeta
cd vegeta
make vegeta
mv vegeta ~/bin # Or elsewhere, up to you.
```
## Versioning
Both the library and the CLI are versioned with [SemVer v2.0.0](https://semver.org/spec/v2.0.0.html).
After [v8.0.0](https://github.com/tsenart/vegeta/tree/v8.0.0), the two components
are versioned separately to better isolate breaking changes to each.
CLI releases are tagged with `cli/vMAJOR.MINOR.PATCH` and published on the [GitHub releases page](https://github.com/tsenart/vegeta/releases).
As for the library, new versions are tagged with both `lib/vMAJOR.MINOR.PATCH` and `vMAJOR.MINOR.PATCH`.
The latter tag is required for compatibility with `go mod`.
## Contributing
See [CONTRIBUTING.md](.github/CONTRIBUTING.md).
## Usage manual
```console
Usage: vegeta [global flags] <command> [command flags]
global flags:
-cpus int
Number of CPUs to use (default = number of cpus)
-profile string
Enable profiling of [cpu, heap]
-version
Print version and exit
attack command:
-body string
Requests body file
-cert string
TLS client PEM encoded certificate file
-chunked
Send body with chunked transfer encoding
-connect-to value
A mapping of (ip|host):port to use instead of a target URL's (ip|host):port. Can be repeated multiple times.
Identical src:port with different dst:port will round-robin over the different dst:port pairs.
Example: google.com:80:localhost:6060
-connections int
Max open idle connections per target host (default 10000)
-dns-ttl value
Cache DNS lookups for the given duration [-1 = disabled, 0 = forever] (default 0s)
-duration duration
Duration of the test [0 = forever]
-format string
Targets format [http, json] (default "http")
-h2c
Send HTTP/2 requests without TLS encryption
-header value
Request header
-http2
Send HTTP/2 requests when supported by the server (default true)
-insecure
Ignore invalid server TLS certificates
-keepalive
Use persistent connections (default true)
-key string
TLS client PEM encoded private key file
-laddr value
Local IP address (default 0.0.0.0)
-lazy
Read targets lazily
-max-body value
Maximum number of bytes to capture from response bodies. [-1 = no limit] (default -1)
-max-connections int
Max connections per target host
-max-workers uint
Maximum number of workers (default 18446744073709551615)
-name string
Attack name
-output string
Output file (default "stdout")
-prometheus-addr string
Prometheus exporter listen address [empty = disabled]. Example: 0.0.0.0:8880
-proxy-header value
Proxy CONNECT header
-rate value
Number of requests per time unit [0 = infinity] (default 50/1s)
-redirects int
Number of redirects to follow. -1 will not follow but marks as success (default 10)
-resolvers value
List of addresses (ip:port) to use for DNS resolution. Disables use of local system DNS. (comma separated list)
-root-certs value
TLS root certificate files (comma separated list)
-session-tickets
Enable TLS session resumption using session tickets
-targets string
Targets file (default "stdin")
-timeout duration
Requests timeout (default 30s)
-unix-socket string
Connect over a unix socket. This overrides the host address in target URLs
-workers uint
Initial number of workers (default 10)
encode command:
-output string
Output file (default "stdout")
-to string
Output encoding [csv, gob, json] (default "json")
plot command:
-output string
Output file (default "stdout")
-threshold int
Threshold of data points above which series are downsampled. (default 4000)
-title string
Title and header of the resulting HTML page (default "Vegeta Plot")
report command:
-buckets string
Histogram buckets, e.g.: "[0,1ms,10ms]"
-every duration
Report interval
-output string
Output file (default "stdout")
-type string
Report type to generate [text, json, hist[buckets], hdrplot] (default "text")
examples:
echo "GET http://localhost/" | vegeta attack -duration=5s | tee results.bin | vegeta report
vegeta report -type=json results.bin > metrics.json
cat results.bin | vegeta plot > plot.html
cat results.bin | vegeta report -type="hist[0,100ms,200ms,300ms]"
```
#### `-cpus`
Specifies the number of CPUs to be used internally.
It defaults to the amount of CPUs available in the system.
#### `-profile`
Specifies which profiler to enable during execution. Both _cpu_ and
_heap_ profiles are supported. It defaults to none.
#### `-version`
Prints the version and exits.
### `attack` command
#### `-body`
Specifies the file whose content will be set as the body of every
request unless overridden per attack target, see `-targets`.
#### `-cert`
Specifies the PEM encoded TLS client certificate file to be used with HTTPS requests.
If `-key` isn't specified, it will be set to the value of this flag.
#### `-chunked`
Specifies whether to send request bodies with the chunked transfer encoding.
#### `-connections`
Specifies the maximum number of idle open connections per target host.
#### `-dns-ttl`
Specifies the duration to cache DNS lookups for. A zero value caches forever.
A negative value disables caching altogether.
#### `-max-connections`
Specifies the maximum number of connections per target host.
#### `-duration`
Specifies the amount of time to issue request to the targets.
The internal concurrency structure's setup has this value as a variable.
The actual run time of the test can be longer than specified due to the
responses delay. Use 0 for an infinite attack.
#### `-format`
Specifies the targets format to decode.
##### `json` format
The JSON format makes integration with programs that produce targets dynamically easier.
Each target is one JSON object in its own line. The method and url fields are required.
If present, the body field must be base64 encoded. The generated [JSON Schema](lib/target.schema.json)
defines the format in detail.
```bash
jq -ncM '{method: "GET", url: "http://goku", body: "Punch!" | @base64, header: {"Content-Type": ["text/plain"]}}' |
vegeta attack -format=json -rate=100 | vegeta encode
```
##### `http` format
The http format almost resembles the plain-text HTTP message format defined in
[RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) but it
doesn't support in-line HTTP bodies, only references to files that are loaded and used
as request bodies (as exemplified below).
Although targets in this format can be produced by other programs, it was originally
meant to be used by people writing targets by hand for simple use cases.
Here are a few examples of valid targets files in the http format:
###### Simple targets
```
GET http://goku:9090/path/to/dragon?item=ball
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success
```
###### Targets with custom headers
```
GET http://user:password@goku:9090/path/to
X-Account-ID: 8675309
DELETE http://goku:9090/path/to/remove
Confirmation-Token: 90215
Authorization: Token DEADBEEF
```
###### Targets with custom bodies
```
POST http://goku:9090/things
@/path/to/newthing.json
PATCH http://goku:9090/thing/71988591
@/path/to/thing-71988591.json
```
###### Targets with custom bodies and headers
```
POST http://goku:9090/things
X-Account-ID: 99
@/path/to/newthing.json
```
###### Add comments
Lines starting with `#` are ignored.
```
# get a dragon ball
GET http://goku:9090/path/to/dragon?item=ball
# specify a test account
X-Account-ID: 99
```
#### `-h2c`
Specifies that HTTP2 requests are to be sent over TCP without TLS encryption.
#### `-header`
Specifies a request header to be used in all targets defined, see `-targets`.
You can specify as many as needed by repeating the flag.
#### `-http2`
Specifies whether to enable HTTP/2 requests to servers which support it.
#### `-insecure`
Specifies whether to ignore invalid server TLS certificates.
#### `-keepalive`
Specifies whether to reuse TCP connections between HTTP requests.
#### `-key`
Specifies the PEM encoded TLS client certificate private key file to be
used with HTTPS requests.
#### `-laddr`
Specifies the local IP address to be used.
#### `-lazy`
Specifies whether to read the input targets lazily instead of eagerly.
This allows streaming targets into the attack command and reduces memory
footprint.
The trade-off is one of added latency in each hit against the targets.
#### `-max-body`
Specifies the maximum number of bytes to capture from the body of each
response. Remaining unread bytes will be fully read but discarded.
Set to -1 for no limit. It knows how to interpret values like these:
- `"10 MB"` -> `10MB`
- `"10240 g"` -> `10TB`
- `"2000"` -> `2000B`
- `"1tB"` -> `1TB`
- `"5 peta"` -> `5PB`
- `"28 kilobytes"` -> `28KB`
- `"1 gigabyte"` -> `1GB`
#### `-name`
Specifies the name of the attack to be recorded in responses.
#### `-output`
Specifies the output file to which the binary results will be written
to. Made to be piped to the report command input. Defaults to stdout.
#### `-rate`
Specifies the request rate per time unit to issue against
the targets. The actual request rate can vary slightly due to things like
garbage collection, but overall it should stay very close to the specified.
If no time unit is provided, 1s is used.
A `-rate` of `0` or `infinity` means vegeta will send requests as fast as possible.
Use together with `-max-workers` to model a fixed set of concurrent users sending
requests serially (i.e. waiting for a response before sending the next request).
Setting `-max-workers` to a very high number while setting `-rate=0` can result in
vegeta consuming too many resources and crashing. Use with care.
#### `-redirects`
Specifies the max number of redirects followed on each request. The
default is 10. When the value is -1, redirects are not followed but
the response is marked as successful.
#### `-resolvers`
Specifies custom DNS resolver addresses to use for name resolution instead of
the ones configured by the operating system. Works only on non Windows systems.
#### `-root-certs`
Specifies the trusted TLS root CAs certificate files as a comma separated
list. If unspecified, the default system CAs certificates will be used.
#### `-session-tickets`
Specifies whether to support TLS session resumption using session tickets.
#### `-targets`
Specifies the file from which to read targets, defaulting to stdin.
See the [`-format`](#-format) section to learn about the different target formats.
#### `-timeout`
Specifies the timeout for each request. A value of `0` disables timeouts.
#### `-workers`
Specifies the initial number of workers used in the attack. The actual
number of workers will increase if necessary in order to sustain the
requested rate, unless it'd go beyond `-max-workers`.
#### `-max-workers`
Specifies the maximum number of workers used in the attack. It can be used to
control the concurrency level used by an attack.
### `report` command
```console
Usage: vegeta report [options] [<file>...]
Outputs a report of attack results.
Arguments:
<file> A file with vegeta attack results encoded with one of
the supported encodings (gob | json | csv) [default: stdin]
Options:
--type Which report type to generate (text | json | hist[buckets] | hdrplot).
[default: text]
--buckets Histogram buckets, e.g.: '[0,1ms,10ms]'
--every Write the report to --output at every given interval (e.g 100ms)
The default of 0 means the report will only be written after
all results have been processed. [default: 0]
--output Output file [default: stdout]
Examples:
echo "GET http://:80" | vegeta attack -rate=10/s > results.gob
echo "GET http://:80" | vegeta attack -rate=100/s | vegeta encode > results.json
vegeta report results.*
```
#### `report -type=text`
```console
Requests [total, rate, throughput] 1200, 120.00, 65.87
Duration [total, attack, wait] 10.094965987s, 9.949883921s, 145.082066ms
Latencies [min, mean, 50, 95, 99, max] 90.438129ms, 113.172398ms, 108.272568ms, 140.18235ms, 247.771566ms, 264.815246ms
Bytes In [total, mean] 3714690, 3095.57
Bytes Out [total, mean] 0, 0.00
Success [ratio] 55.42%
Status Codes [code:count] 0:535 200:665
Error Set:
Get http://localhost:6060: dial tcp 127.0.0.1:6060: connection refused
Get http://localhost:6060: read tcp 127.0.0.1:6060: connection reset by peer
Get http://localhost:6060: dial tcp 127.0.0.1:6060: connection reset by peer
Get http://localhost:6060: write tcp 127.0.0.1:6060: broken pipe
Get http://localhost:6060: net/http: transport closed before response was received
Get http://localhost:6060: http: can't write HTTP request on broken connection
```
The `Requests` row shows:
- The `total` number of issued requests.
- The real request `rate` sustained during the `attack` period.
- The `throughput` of successful requests over the `total` period.
The `Duration` row shows:
- The `attack` time taken issuing all requests (`total` - `wait`)
- The `wait` time waiting for the response to the last issued request (`total` - `attack`)
- The `total` time taken in the attack (`attack` + `wait`)
Latency is the amount of time taken for a response to a request to be read (including the `-max-body` bytes from the response body).
- `min` is the minimum latency of all requests in an attack.
- `mean` is the [arithmetic mean / average](https://en.wikipedia.org/wiki/Arithmetic_mean) of the latencies of all requests in an attack.
- `50`, `90`, `95`, `99` are the 50th, 90th, 95th and 99th [percentiles](https://en.wikipedia.org/wiki/Percentile), respectively, of the latencies of all requests in an attack. To understand more about why these are useful, I recommend [this article](https://bravenewgeek.com/everything-you-know-about-latency-is-wrong/) from @tylertreat.
- `max` is the maximum latency of all requests in an attack.
The `Bytes In` and `Bytes Out` rows shows:
- The `total` number of bytes sent (out) or received (in) with the request or response bodies.
- The `mean` number of bytes sent (out) or received (in) with the request or response bodies.
The `Success` ratio shows the percentage of requests whose responses didn't error and had status codes between **200** and **400** (non-inclusive).
The `Status Codes` row shows a histogram of status codes. `0` status codes mean a request failed to be sent.
The `Error Set` shows a unique set of errors returned by all issued requests. These include requests that got non-successful response status code.
#### `report -type=json`
All duration like fields are in nanoseconds.
```json
{
"latencies": {
"total": 237119463,
"mean": 2371194,
"50th": 2854306,
"90th": 3228223,
"95th": 3478629,
"99th": 3530000,
"max": 3660505,
"min": 1949582
},
"buckets": {
"0": 9952,
"1000000": 40,
"2000000": 6,
"3000000": 0,
"4000000": 0,
"5000000": 2
},
"bytes_in": {
"total": 606700,
"mean": 6067
},
"bytes_out": {
"total": 0,
"mean": 0
},
"earliest": "2015-09-19T14:45:50.645818631+02:00",
"latest": "2015-09-19T14:45:51.635818575+02:00",
"end": "2015-09-19T14:45:51.639325797+02:00",
"duration": 989999944,
"wait": 3507222,
"requests": 100,
"rate": 101.01010672380401,
"throughput": 101.00012489812,
"success": 1,
"status_codes": {
"200": 100
},
"errors": []
}
```
In the `buckets` field, each key is a nanosecond value representing the lower bound of a bucket.
The upper bound is implied by the next higher bucket.
Upper bounds are non-inclusive.
The highest bucket is the overflow bucket; it has no upper bound.
The values are counts of how many requests fell into that particular bucket.
If the `-buckets` parameter is not present, the `buckets` field is omitted.
#### `report -type=hist`
Computes and prints a text based histogram for the given buckets.
Each bucket upper bound is non-inclusive.
```console
cat results.bin | vegeta report -type='hist[0,2ms,4ms,6ms]'
Bucket # % Histogram
[0, 2ms] 6007 32.65% ########################
[2ms, 4ms] 5505 29.92% ######################
[4ms, 6ms] 2117 11.51% ########
[6ms, +Inf] 4771 25.93% ###################
```
#### `report -type=hdrplot`
Writes out results in a format plottable by https://hdrhistogram.github.io/HdrHistogram/plotFiles.html.
```
Value(ms) Percentile TotalCount 1/(1-Percentile)
0.076715 0.000000 0 1.000000
0.439370 0.100000 200 1.111111
0.480836 0.200000 400 1.250000
0.495559 0.300000 599 1.428571
0.505101 0.400000 799 1.666667
0.513059 0.500000 999 2.000000
0.516664 0.550000 1099 2.222222
0.520455 0.600000 1199 2.500000
0.525008 0.650000 1299 2.857143
0.530174 0.700000 1399 3.333333
0.534891 0.750000 1499 4.000000
0.537572 0.775000 1548 4.444444
0.540340 0.800000 1598 5.000000
0.543763 0.825000 1648 5.714286
0.547164 0.850000 1698 6.666667
0.551432 0.875000 1748 8.000000
0.553444 0.887500 1773 8.888889
0.555774 0.900000 1798 10.000000
0.558454 0.912500 1823 11.428571
0.562123 0.925000 1848 13.333333
0.565563 0.937500 1873 16.000000
0.567831 0.943750 1886 17.777778
0.570617 0.950000 1898 20.000000
0.574522 0.956250 1911 22.857143
0.579046 0.962500 1923 26.666667
0.584426 0.968750 1936 32.000000
0.586695 0.971875 1942 35.555556
0.590451 0.975000 1948 40.000000
0.597543 0.978125 1954 45.714286
0.605637 0.981250 1961 53.333333
0.613564 0.984375 1967 64.000000
0.620393 0.985938 1970 71.113640
0.629121 0.987500 1973 80.000000
0.638060 0.989062 1976 91.424392
0.648085 0.990625 1979 106.666667
0.659689 0.992188 1982 128.008193
0.665870 0.992969 1984 142.227279
0.672985 0.993750 1986 160.000000
0.680101 0.994531 1987 182.848784
0.687810 0.995313 1989 213.356091
0.695729 0.996094 1990 256.016385
0.730641 0.996484 1991 284.414107
0.785516 0.996875 1992 320.000000
0.840392 0.997266 1993 365.764448
1.009646 0.997656 1993 426.621160
1.347020 0.998047 1994 512.032770
1.515276 0.998242 1994 568.828214
1.683532 0.998437 1995 639.795266
1.887487 0.998633 1995 731.528895
2.106249 0.998828 1996 853.242321
2.325011 0.999023 1996 1023.541453
2.434952 0.999121 1996 1137.656428
2.544894 0.999219 1996 1280.409731
2.589510 0.999316 1997 1461.988304
2.605192 0.999414 1997 1706.484642
2.620873 0.999512 1997 2049.180328
2.628713 0.999561 1997 2277.904328
2.636394 0.999609 1997 2557.544757
2.644234 0.999658 1997 2923.976608
2.652075 0.999707 1997 3412.969283
2.658916 0.999756 1998 4098.360656
2.658916 0.999780 1998 4545.454545
2.658916 0.999805 1998 5128.205128
2.658916 0.999829 1998 5847.953216
2.658916 0.999854 1998 6849.315068
2.658916 0.999878 1998 8196.721311
2.658916 0.999890 1998 9090.909091
2.658916 0.999902 1998 10204.081633
2.658916 0.999915 1998 11764.705882
2.658916 0.999927 1998 13698.630137
2.658916 0.999939 1998 16393.442623
2.658916 0.999945 1998 18181.818182
2.658916 0.999951 1998 20408.163265
2.658916 0.999957 1998 23255.813953
2.658916 0.999963 1998 27027.027027
2.658916 0.999969 1998 32258.064516
2.658916 0.999973 1998 37037.037037
2.658916 0.999976 1998 41666.666667
2.658916 0.999979 1998 47619.047619
2.658916 0.999982 1998 55555.555556
2.658916 0.999985 1998 66666.666667
2.658916 0.999986 1998 71428.571429
2.658916 0.999988 1998 83333.333333
2.658916 0.999989 1998 90909.090909
2.658916 0.999991 1998 111111.111111
2.658916 0.999992 1998 125000.000000
2.658916 0.999993 1998 142857.142858
2.658916 0.999994 1998 166666.666668
2.658916 0.999995 1998 199999.999999
2.658916 0.999996 1998 250000.000000
2.658916 0.999997 1998 333333.333336
2.658916 0.999998 1998 500000.000013
2.658916 0.999999 1998 999999.999971
2.658916 1.000000 1998 10000000.000000
```
### `encode` command
```
Usage: vegeta encode [options] [<file>...]
Encodes vegeta attack results from one encoding to another.
The supported encodings are Gob (binary), CSV and JSON.
Each input file may have a different encoding which is detected
automatically.
The CSV encoder doesn't write a header. The columns written by it are:
1. Unix timestamp in nanoseconds since epoch
2. HTTP status code
3. Request latency in nanoseconds
4. Bytes out
5. Bytes in
6. Error
7. Base64 encoded response body
8. Attack name
9. Sequence number of request
10. Method
11. URL
12. Base64 encoded response headers
Arguments:
<file> A file with vegeta attack results encoded with one of
the supported encodings (gob | json | csv) [default: stdin]
Options:
--to Output encoding (gob | json | csv) [default: json]
--output Output file [default: stdout]
Examples:
echo "GET http://:80" | vegeta attack -rate=1/s > results.gob
cat results.gob | vegeta encode | jq -c 'del(.body)' | vegeta encode -to gob
```
### `plot` command

```
Usage: vegeta plot [options] [<file>...]
Outputs an HTML time series plot of request latencies over time.
The X axis represents elapsed time in seconds from the beginning
of the earliest attack in all input files. The Y axis represents
request latency in milliseconds.
Click and drag to select a region to zoom into. Double click to zoom out.
Choose a different number on the bottom left corner input field
to change the moving average window size (in data points).
Arguments:
<file> A file output by running vegeta attack [default: stdin]
Options:
--title Title and header of the resulting HTML page.
[default: Vegeta Plot]
--threshold Threshold of data points to downsample series to.
Series with less than --threshold number of data
points are not downsampled. [default: 4000]
Examples:
echo "GET http://:80" | vegeta attack -name=50qps -rate=50 -duration=5s > results.50qps.bin
cat results.50qps.bin | vegeta plot > plot.50qps.html
echo "GET http://:80" | vegeta attack -name=100qps -rate=100 -duration=5s > results.100qps.bin
vegeta plot results.50qps.bin results.100qps.bin > plot.html
```
## Usage: Generated targets
Apart from accepting a static list of targets, Vegeta can be used together with another program that generates them in a streaming fashion. Here's an example of that using the `jq` utility that generates targets with an incrementing id in their body.
```console
jq -ncM 'while(true; .+1) | {method: "POST", url: "http://:6060", body: {id: .} | @base64 }' | \
vegeta attack -rate=50/s -lazy -format=json -duration=30s | \
tee results.bin | \
vegeta report
```
## Usage: Distributed attacks
Whenever your load test can't be conducted due to Vegeta hitting machine limits
such as open files, memory, CPU or network bandwidth, it's a good idea to use Vegeta in a distributed manner.
In a hypothetical scenario where the desired attack rate is 60k requests per second,
let's assume we have 3 machines with `vegeta` installed.
Make sure open file descriptor and process limits are set to a high number for your user **on each machine**
using the `ulimit` command.
We're ready to start the attack. All we need to do is to divide the intended rate by the number of machines,
and use that number on each attack. Here we'll use [pdsh](https://code.google.com/p/pdsh/) for orchestration.
```shell
$ PDSH_RCMD_TYPE=ssh pdsh -b -w '10.0.1.1,10.0.2.1,10.0.3.1' \
'echo "GET http://target/" | vegeta attack -rate=20000 -duration=60s > result.bin'
```
After the previous command finishes, we can gather the result files to use on our report.
```shell
$ for machine in 10.0.1.1 10.0.2.1 10.0.3.1; do
scp $machine:~/result.bin $machine.bin &
done
```
The `report` command accepts multiple result files.
It'll read and sort them by timestamp before generating reports.
```console
vegeta report *.bin
```
Another way to gather results in distributed tests is to use the built-in Prometheus Exporter and configure a Prometheus Server to get test results from all Vegeta instances. See `attack` option "prometheus-addr" for more details and a complete example in the section "Prometheus Support".
## Usage: Real-time Analysis
If you are a happy user of iTerm, you can integrate vegeta with [jplot](https://github.com/rs/jplot) using [jaggr](https://github.com/rs/jaggr) to plot a vegeta report in real-time in the comfort of your terminal:
```
echo 'GET http://localhost:8080' | \
vegeta attack -rate 5000 -duration 10m | vegeta encode | \
jaggr @count=rps \
hist\[100,200,300,400,500\]:code \
p25,p50,p95:latency \
sum:bytes_in \
sum:bytes_out | \
jplot rps+code.hist.100+code.hist.200+code.hist.300+code.hist.400+code.hist.500 \
latency.p95+latency.p50+latency.p25 \
bytes_in.sum+bytes_out.sum
```

## Usage: Library
The library versioning follows [SemVer v2.0.0](https://semver.org/spec/v2.0.0.html).
Since [lib/v9.0.0](https://github.com/tsenart/vegeta/tree/lib/v9.0.0), the library and cli
are versioned separately to better isolate breaking changes to each component.
See [Versioning](#Versioning) for more details on git tag naming schemes and compatibility
with `go mod`.
```go
package main
import (
"fmt"
"time"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
func main() {
rate := vegeta.Rate{Freq: 100, Per: time.Second}
duration := 4 * time.Second
targeter := vegeta.NewStaticTargeter(vegeta.Target{
Method: "GET",
URL: "http://localhost:9100/",
})
attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack(targeter, rate, duration, "Big Bang!") {
metrics.Add(res)
}
metrics.Close()
fmt.Printf("99th percentile: %s\n", metrics.Latencies.P99)
}
```
#### Limitations
There will be an upper bound of the supported `rate` which varies on the
machine being used.
You could be CPU bound (unlikely), memory bound (more likely) or
have system resource limits being reached which ought to be tuned for
the process execution. The important limits for us are file descriptors
and processes. On a UNIX system you can get and set the current
soft-limit values for a user.
```shell
$ ulimit -n # file descriptors
2560
$ ulimit -u # processes / threads
709
```
Just pass a new number as the argument to change it.
## Prometheus support
Vegeta has a built-in Prometheus Exporter that may be enabled during attacks so that you can point any Prometheus instance to Vegeta attack processes and monitor attack metrics.
To enable the Prometheus Exporter on the command line, set the "prometheus-addr" flag.
A Prometheus HTTP endpoint will be available only during the lifespan of an attack and will be closed right after the attack is finished.
The following metrics are exposed:
* `request_bytes_in` - bytes count received from targeted servers by "url", "method" and "status"
* `request_bytes_out` - bytes count sent to targeted server by "url", "method" and "status"
* `request_seconds` - histogram with request latency and counters by "url", "method" and "status"
* `request_fail_count` - count of failed requests by "url", "method", "status" and "message"
<image src="lib/prom/prometheus-sample.png" width="500" />
Check file [lib/prom/grafana.json](lib/prom/grafana.json) with the source of this sample dashboard in Grafana.
### Limitations
1. Prometheus scrapes metrics from a running vegeta attack process and assigns timestamps to samples on its server. This means result timestamps aren't accurate (i.e. they're scraping time, not result time).
2. Configuring Prometheus to scrape vegeta needs to happen out-of-band. That's a hassle!
3. Since there's no coordination between a vegeta attack process and a Prometheus server, an attack process will finish before Prometheus has the chance to scrape the latest observations.
Why aren't we using pushgateway instead? See [this comment](https://github.com/tsenart/vegeta/pull/534#issuecomment-1629943731).
There's [an issue](https://github.com/tsenart/vegeta/issues/637) tracking the proper solution to all these limitations which is a remote write integration.
## License
See [LICENSE](LICENSE).
## Donate
If you use and love Vegeta, please consider sending some Satoshi to
`1MDmKC51ve7Upxt75KoNM6x1qdXHFK6iW2`. In case you want to be mentioned as a
sponsor, let me know!
[](#donate)
================================================
FILE: attack.go
================================================
package main
import (
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/tsenart/vegeta/v12/internal/resolver"
vegeta "github.com/tsenart/vegeta/v12/lib"
prom "github.com/tsenart/vegeta/v12/lib/prom"
)
func attackCmd() command {
fs := flag.NewFlagSet("vegeta attack", flag.ExitOnError)
opts := &attackOpts{
headers: headers{http.Header{}},
proxyHeaders: headers{http.Header{}},
laddr: localAddr{&vegeta.DefaultLocalAddr},
rate: vegeta.Rate{Freq: 50, Per: time.Second},
maxBody: vegeta.DefaultMaxBody,
promAddr: "0.0.0.0:8880",
}
fs.StringVar(&opts.name, "name", "", "Attack name")
fs.StringVar(&opts.targetsf, "targets", "stdin", "Targets file")
fs.StringVar(&opts.format, "format", vegeta.HTTPTargetFormat,
fmt.Sprintf("Targets format [%s]", strings.Join(vegeta.TargetFormats, ", ")))
fs.StringVar(&opts.outputf, "output", "stdout", "Output file")
fs.StringVar(&opts.bodyf, "body", "", "Requests body file")
fs.BoolVar(&opts.chunked, "chunked", false, "Send body with chunked transfer encoding")
fs.StringVar(&opts.certf, "cert", "", "TLS client PEM encoded certificate file")
fs.StringVar(&opts.keyf, "key", "", "TLS client PEM encoded private key file")
fs.Var(&opts.rootCerts, "root-certs", "TLS root certificate files (comma separated list)")
fs.BoolVar(&opts.http2, "http2", true, "Send HTTP/2 requests when supported by the server")
fs.BoolVar(&opts.h2c, "h2c", false, "Send HTTP/2 requests without TLS encryption")
fs.BoolVar(&opts.insecure, "insecure", false, "Ignore invalid server TLS certificates")
fs.BoolVar(&opts.lazy, "lazy", false, "Read targets lazily")
fs.DurationVar(&opts.duration, "duration", 0, "Duration of the test [0 = forever]")
fs.DurationVar(&opts.timeout, "timeout", vegeta.DefaultTimeout, "Requests timeout")
fs.Uint64Var(&opts.workers, "workers", vegeta.DefaultWorkers, "Initial number of workers")
fs.Uint64Var(&opts.maxWorkers, "max-workers", vegeta.DefaultMaxWorkers, "Maximum number of workers")
fs.IntVar(&opts.connections, "connections", vegeta.DefaultConnections, "Max open idle connections per target host")
fs.IntVar(&opts.maxConnections, "max-connections", vegeta.DefaultMaxConnections, "Max connections per target host")
fs.IntVar(&opts.redirects, "redirects", vegeta.DefaultRedirects, "Number of redirects to follow. -1 will not follow but marks as success")
fs.Var(&maxBodyFlag{&opts.maxBody}, "max-body", "Maximum number of bytes to capture from response bodies. [-1 = no limit]")
fs.Var(&rateFlag{&opts.rate}, "rate", "Number of requests per time unit [0 = infinity]")
fs.Var(&opts.headers, "header", "Request header")
fs.Var(&opts.proxyHeaders, "proxy-header", "Proxy CONNECT header")
fs.Var(&opts.laddr, "laddr", "Local IP address")
fs.BoolVar(&opts.keepalive, "keepalive", true, "Use persistent connections")
fs.StringVar(&opts.unixSocket, "unix-socket", "", "Connect over a unix socket. This overrides the host address in target URLs")
fs.StringVar(&opts.promAddr, "prometheus-addr", "", "Prometheus exporter listen address [empty = disabled]. Example: 0.0.0.0:8880")
fs.Var(&dnsTTLFlag{&opts.dnsTTL}, "dns-ttl", "Cache DNS lookups for the given duration [-1 = disabled, 0 = forever]")
fs.BoolVar(&opts.sessionTickets, "session-tickets", false, "Enable TLS session resumption using session tickets")
fs.Var(&connectToFlag{&opts.connectTo}, "connect-to", "A mapping of (ip|host):port to use instead of a target URL's (ip|host):port. Can be repeated multiple times.\nIdentical src:port with different dst:port will round-robin over the different dst:port pairs.\nExample: google.com:80:localhost:6060")
systemSpecificFlags(fs, opts)
return command{fs, func(args []string) error {
fs.Parse(args)
return attack(opts)
}}
}
var (
errZeroRate = errors.New("rate frequency and time unit must be bigger than zero")
errBadCert = errors.New("bad certificate")
)
// attackOpts aggregates the attack function command options
type attackOpts struct {
name string
targetsf string
format string
outputf string
bodyf string
certf string
keyf string
rootCerts csl
http2 bool
h2c bool
insecure bool
lazy bool
chunked bool
duration time.Duration
timeout time.Duration
rate vegeta.Rate
workers uint64
maxWorkers uint64
connections int
maxConnections int
redirects int
maxBody int64
headers headers
proxyHeaders headers
laddr localAddr
keepalive bool
resolvers csl
unixSocket string
promAddr string
dnsTTL time.Duration
sessionTickets bool
connectTo map[string][]string
}
// attack validates the attack arguments, sets up the
// required resources, launches the attack and writes the results
func attack(opts *attackOpts) (err error) {
if opts.maxWorkers == vegeta.DefaultMaxWorkers && opts.rate.Freq == 0 {
return fmt.Errorf("-rate=0 requires setting -max-workers")
}
if len(opts.resolvers) > 0 {
res, err := resolver.NewResolver(opts.resolvers)
if err != nil {
return err
}
net.DefaultResolver = res
}
net.DefaultResolver.PreferGo = true
files := map[string]io.Reader{}
for _, filename := range []string{opts.targetsf, opts.bodyf} {
if filename == "" {
continue
}
f, err := file(filename, false)
if err != nil {
return fmt.Errorf("error opening %s: %s", filename, err)
}
defer f.Close()
files[filename] = f
}
var body []byte
if bodyf, ok := files[opts.bodyf]; ok {
if body, err = io.ReadAll(bodyf); err != nil {
return fmt.Errorf("error reading %s: %s", opts.bodyf, err)
}
}
var (
tr vegeta.Targeter
src = files[opts.targetsf]
hdr = opts.headers.Header
proxyHdr = opts.proxyHeaders.Header
)
switch opts.format {
case vegeta.JSONTargetFormat:
tr = vegeta.NewJSONTargeter(src, body, hdr)
case vegeta.HTTPTargetFormat:
tr = vegeta.NewHTTPTargeter(src, body, hdr)
default:
return fmt.Errorf("format %q isn't one of [%s]",
opts.format, strings.Join(vegeta.TargetFormats, ", "))
}
if !opts.lazy {
targets, err := vegeta.ReadAllTargets(tr)
if err != nil {
return err
}
tr = vegeta.NewStaticTargeter(targets...)
}
out, err := file(opts.outputf, true)
if err != nil {
return fmt.Errorf("error opening %s: %s", opts.outputf, err)
}
defer out.Close()
tlsc, err := tlsConfig(opts.insecure, opts.certf, opts.keyf, opts.rootCerts)
if err != nil {
return err
}
var pm *prom.Metrics
if opts.promAddr != "" {
pm = prom.NewMetrics()
r := prometheus.NewRegistry()
if err := pm.Register(r); err != nil {
return fmt.Errorf("error registering prometheus metrics: %s", err)
}
srv := http.Server{
Addr: opts.promAddr,
Handler: prom.NewHandler(r, time.Now().UTC()),
}
defer srv.Close()
go srv.ListenAndServe()
}
atk := vegeta.NewAttacker(
vegeta.Redirects(opts.redirects),
vegeta.Timeout(opts.timeout),
vegeta.LocalAddr(*opts.laddr.IPAddr),
vegeta.TLSConfig(tlsc),
vegeta.Workers(opts.workers),
vegeta.MaxWorkers(opts.maxWorkers),
vegeta.KeepAlive(opts.keepalive),
vegeta.Connections(opts.connections),
vegeta.MaxConnections(opts.maxConnections),
vegeta.HTTP2(opts.http2),
vegeta.H2C(opts.h2c),
vegeta.MaxBody(opts.maxBody),
vegeta.UnixSocket(opts.unixSocket),
vegeta.ProxyHeader(proxyHdr),
vegeta.ChunkedBody(opts.chunked),
vegeta.DNSCaching(opts.dnsTTL),
vegeta.ConnectTo(opts.connectTo),
vegeta.SessionTickets(opts.sessionTickets),
)
res := atk.Attack(tr, opts.rate, opts.duration, opts.name)
enc := vegeta.NewEncoder(out)
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
return processAttack(atk, res, enc, sig, pm)
}
func processAttack(
atk *vegeta.Attacker,
res <-chan *vegeta.Result,
enc vegeta.Encoder,
sig <-chan os.Signal,
pm *prom.Metrics,
) error {
for {
select {
case <-sig:
if stopSent := atk.Stop(); !stopSent {
// Exit immediately on second signal.
return nil
}
case r, ok := <-res:
if !ok {
return nil
}
if pm != nil {
pm.Observe(r)
}
if err := enc.Encode(r); err != nil {
return err
}
}
}
}
// tlsConfig builds a *tls.Config from the given options.
func tlsConfig(insecure bool, certf, keyf string, rootCerts []string) (*tls.Config, error) {
var err error
files := map[string][]byte{}
filenames := append([]string{certf, keyf}, rootCerts...)
for _, f := range filenames {
if f != "" {
if files[f], err = os.ReadFile(f); err != nil {
return nil, err
}
}
}
c := tls.Config{InsecureSkipVerify: insecure}
if cert, ok := files[certf]; ok {
key, ok := files[keyf]
if !ok {
key = cert
}
certificate, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}
c.Certificates = append(c.Certificates, certificate)
c.BuildNameToCertificate()
}
if len(rootCerts) > 0 {
c.RootCAs = x509.NewCertPool()
for _, f := range rootCerts {
if !c.RootCAs.AppendCertsFromPEM(files[f]) {
return nil, errBadCert
}
}
}
return &c, nil
}
================================================
FILE: attack_nonwindows.go
================================================
//go:build !windows
// +build !windows
package main
import "flag"
func systemSpecificFlags(fs *flag.FlagSet, opts *attackOpts) {
fs.Var(&opts.resolvers, "resolvers", "List of addresses (ip:port) to use for DNS resolution. Disables use of local system DNS. (comma separated list)")
}
================================================
FILE: attack_test.go
================================================
package main
import (
"bufio"
"bytes"
"io"
"net/http"
"net/http/httptest"
"os"
"reflect"
"sync"
"testing"
"time"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
func TestHeadersSet(t *testing.T) {
h := headers{
Header: make(http.Header),
}
for i, tt := range []struct {
key, val string
want []string
}{
{"key", "value", []string{"value"}},
{"key", "value", []string{"value", "value"}},
{"Key", "Value", []string{"Value"}},
{"KEY", "VALUE", []string{"VALUE"}},
} {
if err := h.Set(tt.key + ": " + tt.val); err != nil {
t.Error(err)
} else if got := h.Header[tt.key]; !reflect.DeepEqual(got, tt.want) {
t.Errorf("test #%d, '%s: %s': got: %+v, want: %+v", i, tt.key, tt.val, got, tt.want)
}
}
}
func decodeMetrics(buf bytes.Buffer) (vegeta.Metrics, error) {
var metrics vegeta.Metrics
dec := vegeta.NewDecoder(bufio.NewReader(&buf))
for {
var r vegeta.Result
if err := dec.Decode(&r); err != nil {
if err == io.EOF {
break
}
return metrics, err
}
metrics.Add(&r)
}
metrics.Close()
return metrics, nil
}
func TestAttackSignalOnce(t *testing.T) {
t.Parallel()
const (
signalDelay = 300 * time.Millisecond // Delay before stopping.
clientTimeout = 1 * time.Second // This, plus delay, is the max time for the attack.
serverTimeout = 2 * time.Second // Must be more than clientTimeout.
attackDuration = 10 * time.Second // The attack should never take this long.
)
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(serverTimeout) // Server.Close() will block for this long on shutdown.
}),
)
defer server.Close()
tr := vegeta.NewStaticTargeter(vegeta.Target{Method: "GET", URL: server.URL})
atk := vegeta.NewAttacker(vegeta.Timeout(clientTimeout))
rate := vegeta.Rate{Freq: 10, Per: time.Second} // Every 100ms.
var buf bytes.Buffer
writer := bufio.NewWriter(&buf)
enc := vegeta.NewEncoder(writer)
sig := make(chan os.Signal, 1)
res := atk.Attack(tr, rate, attackDuration, "")
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
processAttack(atk, res, enc, sig, nil)
}()
// Allow more than one request to have started before stopping.
time.Sleep(signalDelay)
sig <- os.Interrupt
wg.Wait()
writer.Flush()
metrics, err := decodeMetrics(buf)
if err != nil {
t.Error(err)
}
if got, min := metrics.Requests, uint64(2); got < min {
t.Errorf("not enough requests recorded. got %+v, min: %+v", got, min)
}
if got, want := metrics.Success, 0.0; got != want {
t.Errorf("all requests should fail. got %+v, want: %+v", got, want)
}
if got, max := metrics.Duration, clientTimeout; got > max {
t.Errorf("attack duration too long. got %+v, max: %+v", got, max)
}
if got, want := metrics.Wait.Round(time.Second), clientTimeout; got != want {
t.Errorf("attack wait doesn't match timeout. got %+v, want: %+v", got, want)
}
}
func TestAttackSignalTwice(t *testing.T) {
t.Parallel()
const (
attackDuration = 10 * time.Second // The attack should never take this long.
)
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
)
defer server.Close()
tr := vegeta.NewStaticTargeter(vegeta.Target{Method: "GET", URL: server.URL})
atk := vegeta.NewAttacker()
rate := vegeta.Rate{Freq: 1, Per: time.Second}
var buf bytes.Buffer
writer := bufio.NewWriter(&buf)
enc := vegeta.NewEncoder(writer)
sig := make(chan os.Signal, 1)
res := atk.Attack(tr, rate, attackDuration, "")
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
processAttack(atk, res, enc, sig, nil)
}()
// Exit as soon as possible.
sig <- os.Interrupt
sig <- os.Interrupt
wg.Wait()
writer.Flush()
metrics, err := decodeMetrics(buf)
if err != nil {
t.Error(err)
}
if got, max := metrics.Duration, time.Second; got > max {
t.Errorf("attack duration too long. got %+v, max: %+v", got, max)
}
}
================================================
FILE: attack_windows.go
================================================
package main
import "flag"
func systemSpecificFlags(fs *flag.FlagSet, opts *attackOpts) {}
================================================
FILE: dump.go
================================================
package main
import (
"fmt"
)
func dumpCmd() command {
return command{fn: func([]string) error {
return fmt.Errorf("vegeta dump has been deprecated and succeeded by the vegeta encode command")
}}
}
================================================
FILE: encode.go
================================================
package main
import (
"flag"
"fmt"
"io"
"os"
"os/signal"
"strings"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
const (
encodingCSV = "csv"
encodingGob = "gob"
encodingJSON = "json"
)
const encodeUsage = `Usage: vegeta encode [options] [<file>...]
Encodes vegeta attack results from one encoding to another.
The supported encodings are Gob (binary), CSV and JSON.
Each input file may have a different encoding which is detected
automatically.
The CSV encoder doesn't write a header. The columns written by it are:
1. Unix timestamp in nanoseconds since epoch
2. HTTP status code
3. Request latency in nanoseconds
4. Bytes out
5. Bytes in
6. Error
7. Base64 encoded response body
8. Attack name
9. Sequence number of request
10. Method
11. URL
12. Base64 encoded response headers
Arguments:
<file> A file with vegeta attack results encoded with one of
the supported encodings (gob | json | csv) [default: stdin]
Options:
--to Output encoding (gob | json | csv) [default: json]
--output Output file [default: stdout]
Examples:
echo "GET http://:80" | vegeta attack -rate=1/s > results.gob
cat results.gob | vegeta encode | jq -c 'del(.body)' | vegeta encode -to gob
`
func encodeCmd() command {
encs := "[" + strings.Join([]string{encodingCSV, encodingGob, encodingJSON}, ", ") + "]"
fs := flag.NewFlagSet("vegeta encode", flag.ExitOnError)
to := fs.String("to", encodingJSON, "Output encoding "+encs)
output := fs.String("output", "stdout", "Output file")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "%s\n", encodeUsage)
}
return command{fs, func(args []string) error {
fs.Parse(args)
files := fs.Args()
if len(files) == 0 {
files = append(files, "stdin")
}
return encode(files, *to, *output)
}}
}
func encode(files []string, to, output string) error {
dec, mc, err := decoder(files)
defer mc.Close()
if err != nil {
return err
}
out, err := file(output, true)
if err != nil {
return err
}
defer out.Close()
var enc vegeta.Encoder
switch to {
case encodingCSV:
enc = vegeta.NewCSVEncoder(out)
case encodingGob:
enc = vegeta.NewEncoder(out)
case encodingJSON:
enc = vegeta.NewJSONEncoder(out)
default:
return fmt.Errorf("encode: unknown encoding %q", to)
}
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, os.Interrupt)
for {
select {
case <-sigch:
return nil
default:
}
var r vegeta.Result
if err = dec.Decode(&r); err != nil {
if err == io.EOF {
break
}
return err
} else if err = enc.Encode(&r); err != nil {
return err
}
}
return nil
}
================================================
FILE: file.go
================================================
package main
import (
"errors"
"fmt"
"io"
"os"
"strings"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
func file(name string, create bool) (*os.File, error) {
switch name {
case "stdin":
return os.Stdin, nil
case "stdout":
return os.Stdout, nil
default:
if create {
return os.Create(name)
}
return os.Open(name)
}
}
func decoder(files []string) (vegeta.Decoder, io.Closer, error) {
closer := make(multiCloser, 0, len(files))
decs := make([]vegeta.Decoder, 0, len(files))
for _, f := range files {
rc, err := file(f, false)
if err != nil {
return nil, closer, err
}
dec := vegeta.DecoderFor(rc)
if dec == nil {
return nil, closer, fmt.Errorf("encode: can't detect encoding of %q", f)
}
decs = append(decs, dec)
closer = append(closer, rc)
}
return vegeta.NewRoundRobinDecoder(decs...), closer, nil
}
type multiCloser []io.Closer
func (mc multiCloser) Close() error {
var errs []string
for _, c := range mc {
if err := c.Close(); err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "; "))
}
return nil
}
================================================
FILE: flags.go
================================================
package main
import (
"bytes"
"fmt"
"math"
"net"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/c2h5oh/datasize"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
// headers is the http.Header used in each target request
// it is defined here to implement the flag.Value interface
// in order to support multiple identical flags for request header
// specification
type headers struct{ http.Header }
func (h headers) String() string {
buf := &bytes.Buffer{}
if err := h.Write(buf); err != nil {
return ""
}
return buf.String()
}
// Set implements the flag.Value interface for a map of HTTP Headers.
func (h headers) Set(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("header '%s' has a wrong format", value)
}
key, val := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
if key == "" || val == "" {
return fmt.Errorf("header '%s' has a wrong format", value)
}
// Add key/value directly to the http.Header (map[string][]string).
// http.Header.Add() canonicalizes keys but vegeta is used
// to test systems that require case-sensitive headers.
h.Header[key] = append(h.Header[key], val)
return nil
}
// localAddr implements the Flag interface for parsing net.IPAddr
type localAddr struct{ *net.IPAddr }
func (ip *localAddr) Set(value string) (err error) {
ip.IPAddr, err = net.ResolveIPAddr("ip", value)
return
}
// csl implements the flag.Value interface for comma separated lists
type csl []string
func (l *csl) Set(v string) error {
*l = strings.Split(v, ",")
return nil
}
func (l csl) String() string { return strings.Join(l, ",") }
type rateFlag struct{ *vegeta.Rate }
func (f *rateFlag) Set(v string) (err error) {
if v == "infinity" {
return nil
}
ps := strings.SplitN(v, "/", 2)
switch len(ps) {
case 1:
ps = append(ps, "1s")
case 0:
return fmt.Errorf("-rate format %q doesn't match the \"freq/duration\" format (i.e. 50/1s)", v)
}
f.Freq, err = strconv.Atoi(ps[0])
if err != nil {
return err
}
if f.Freq == 0 {
return nil
}
switch ps[1] {
case "ns", "us", "µs", "ms", "s", "m", "h":
ps[1] = "1" + ps[1]
}
f.Per, err = time.ParseDuration(ps[1])
return err
}
func (f *rateFlag) String() string {
if f.Rate == nil {
return ""
}
return fmt.Sprintf("%d/%s", f.Freq, f.Per)
}
type maxBodyFlag struct{ n *int64 }
func (f *maxBodyFlag) Set(v string) (err error) {
if v == "-1" {
*(f.n) = -1
return nil
}
var ds datasize.ByteSize
if err = ds.UnmarshalText([]byte(v)); err != nil {
return err
}
if ds > math.MaxInt64 {
return fmt.Errorf("-max-body=%d overflows int64", ds)
}
*(f.n) = int64(ds)
return nil
}
func (f *maxBodyFlag) String() string {
if f.n == nil {
return ""
} else if *(f.n) == -1 {
return "-1"
}
return datasize.ByteSize(*(f.n)).String()
}
type dnsTTLFlag struct{ ttl *time.Duration }
func (f *dnsTTLFlag) Set(v string) (err error) {
if v == "-1" {
*(f.ttl) = -1
return nil
}
*(f.ttl), err = time.ParseDuration(v)
return err
}
func (f *dnsTTLFlag) String() string {
if f.ttl == nil {
return ""
} else if *(f.ttl) == -1 {
return "-1"
}
return f.ttl.String()
}
const connectToFormat = "src:port:dst:port"
type connectToFlag struct {
addrMap *map[string][]string
}
func (c *connectToFlag) String() string {
if c.addrMap == nil {
return ""
}
addrMappings := make([]string, 0, len(*c.addrMap))
for k, v := range *c.addrMap {
addrMappings = append(addrMappings, k+":"+strings.Join(v, ","))
}
sort.Strings(addrMappings)
return strings.Join(addrMappings, ";")
}
func (c *connectToFlag) Set(s string) error {
if c.addrMap == nil {
return nil
}
if *c.addrMap == nil {
*c.addrMap = make(map[string][]string)
}
parts := strings.Split(s, ":")
if len(parts) != 4 {
return fmt.Errorf("invalid -connect-to %q, expected format: %s", s, connectToFormat)
}
srcAddr := parts[0] + ":" + parts[1]
dstAddr := parts[2] + ":" + parts[3]
// Parse source address
if _, _, err := net.SplitHostPort(srcAddr); err != nil {
return fmt.Errorf("invalid source address expression [%s], expected address:port", srcAddr)
}
// Parse destination address
if _, _, err := net.SplitHostPort(dstAddr); err != nil {
return fmt.Errorf("invalid destination address expression [%s], expected address:port", dstAddr)
}
(*c.addrMap)[srcAddr] = append((*c.addrMap)[srcAddr], dstAddr)
return nil
}
================================================
FILE: go.mod
================================================
module github.com/tsenart/vegeta/v12
go 1.22
require (
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
github.com/bmizerany/perks v0.0.0-20230307044200-03f9df79da1e
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654
github.com/dgryski/go-lttb v0.0.0-20230207170358-f8fc36cdbff1
github.com/google/go-cmp v0.6.0
github.com/influxdata/tdigest v0.0.1
github.com/mailru/easyjson v0.7.7
github.com/miekg/dns v1.1.61
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/prometheus v0.53.1
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529
github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d
github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3
golang.org/x/net v0.27.0
pgregory.net/rapid v1.1.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/iancoleman/orderedmap v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.23.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
================================================
FILE: go.sum
================================================
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog=
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmizerany/perks v0.0.0-20230307044200-03f9df79da1e h1:mWOqoK5jV13ChKf/aF3plwQ96laasTJgZi4f1aSOu+M=
github.com/bmizerany/perks v0.0.0-20230307044200-03f9df79da1e/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY=
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654 h1:XOPLOMn/zT4jIgxfxSsoXPxkrzz0FaCHwp33x5POJ+Q=
github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E=
github.com/dgryski/go-lttb v0.0.0-20230207170358-f8fc36cdbff1 h1:dxwR3CStJdJamsIoMPCmxuIfBAPTgmzvFax+MvFav3M=
github.com/dgryski/go-lttb v0.0.0-20230207170358-f8fc36cdbff1/go.mod h1:UwftcHUI/qTYvLAxrWmANuRckf8+08O3C3hwStvkhDU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww=
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY=
github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.47.2 h1:jWcnuQHz1o1Wu3MZ6nMJDuTI0kU5yJp9pkxh8XEkNvI=
github.com/prometheus/prometheus v0.47.2/go.mod h1:J/bmOSjgH7lFxz2gZhrWEZs2i64vMS+HIuZfmYNhJ/M=
github.com/prometheus/prometheus v0.53.1 h1:B0xu4VuVTKYrIuBMn/4YSUoIPYxs956qsOfcS4rqCuA=
github.com/prometheus/prometheus v0.53.1/go.mod h1:RZDkzs+ShMBDkAPQkLEaLBXpjmDcjhNxU2drUVPgKUU=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs=
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs=
github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3 h1:pcQGQzTwCg//7FgVywqge1sW9Yf8VMsMdG58MI5kd8s=
github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca h1:PupagGYwj8+I4ubCxcmcBRk3VlUWtTg5huQpZR9flmE=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
================================================
FILE: internal/cmd/echosrv/main.go
================================================
package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"flag"
"io"
"log"
"net/http"
"net/http/httputil"
"os"
"sync/atomic"
"time"
)
func main() {
dump := flag.Bool("dump", false, "Dump HTTP requests to stdout")
sleep := flag.Duration("sleep", 0, "Time to sleep per request")
work := flag.Int("work", 0, "Artificial work load iteration count")
flag.Parse()
count := uint64(0)
go func(last time.Time) {
ticks := time.Tick(time.Second)
for range ticks {
rate := float64(atomic.SwapUint64(&count, 0)) / time.Since(last).Seconds()
last = time.Now()
log.Printf("Rate: %.3f/s", rate)
}
}(time.Now())
http.ListenAndServe(flag.Arg(0), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer atomic.AddUint64(&count, 1)
time.Sleep(*sleep)
if _, err := hash(*work); err != nil {
log.Printf("Error: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
bs, _ := httputil.DumpRequest(r, true)
out := io.Writer(w)
if *dump {
out = io.MultiWriter(w, os.Stdout)
}
_, _ = out.Write(bs)
}))
}
func hash(n int) (string, error) {
if n == 0 {
return "", nil
}
var buf bytes.Buffer
_, err := io.CopyN(&buf, rand.Reader, 1024*1024) // 1MB
if err != nil {
return "", err
}
data := buf.Bytes()
for i := 0; i < n; i++ {
hash := sha256.Sum256(data)
data = hash[:]
}
return base64.URLEncoding.EncodeToString(data), nil
}
================================================
FILE: internal/cmd/jsonschema/main.go
================================================
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"github.com/alecthomas/jsonschema"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
func main() {
types := map[string]interface{}{
"Target": &vegeta.Target{},
}
valid := strings.Join(keys(types), ", ")
fs := flag.NewFlagSet("jsonschema", flag.ContinueOnError)
typ := fs.String("type", "", fmt.Sprintf("Vegeta type to generate a JSON schema for [%s]", valid))
out := fs.String("output", "stdout", "Output file")
if err := fs.Parse(os.Args[1:]); err != nil {
die("%s", err)
}
t, ok := types[*typ]
if !ok {
die("invalid type %q not in [%s]", *typ, valid)
}
schema, err := json.MarshalIndent(jsonschema.Reflect(t), "", " ")
if err != nil {
die("%s", err)
}
switch *out {
case "stdout":
_, err = os.Stdout.Write(schema)
default:
err = os.WriteFile(*out, schema, 0644)
}
if err != nil {
die("%s", err)
}
}
func die(s string, args ...interface{}) {
fmt.Fprintf(os.Stderr, s, args...)
os.Exit(1)
}
func keys(types map[string]interface{}) (ks []string) {
for k := range types {
ks = append(ks, k)
}
return ks
}
================================================
FILE: internal/resolver/resolver.go
================================================
package resolver
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"sync/atomic"
)
type resolver struct {
addrs []string
dialer *net.Dialer
idx uint64
}
// NewResolver - create a new instance of a dns resolver for plugging
// into net.DefaultResolver. Addresses should be a list of
// ip addrs and optional port numbers, separated by colon.
// For example: 1.2.3.4:53 and 1.2.3.4 are both valid. In the absence
// of a port number, 53 will be used instead.
func NewResolver(addrs []string) (*net.Resolver, error) {
if len(addrs) == 0 {
return nil, errors.New("must specify at least resolver address")
}
cleanAddrs, err := normalizeAddrs(addrs)
if err != nil {
return nil, err
}
return &net.Resolver{
PreferGo: true,
Dial: (&resolver{addrs: cleanAddrs, dialer: &net.Dialer{}}).dial,
}, nil
}
func normalizeAddrs(addrs []string) ([]string, error) {
normal := make([]string, len(addrs))
for i, addr := range addrs {
// if addr has no port, give it 53
if !strings.Contains(addr, ":") {
addr += ":53"
}
// validate addr is a valid host:port
host, portstr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// validate valid port.
_, err = strconv.ParseUint(portstr, 10, 16)
if err != nil {
return nil, err
}
// make sure host is an ip.
ip := net.ParseIP(host)
if ip == nil {
return nil, fmt.Errorf("host %s is not an IP address", host)
}
normal[i] = addr
}
return normal, nil
}
// ignore the third parameter, as this represents the dns server address that
// we are overriding.
func (r *resolver) dial(ctx context.Context, network, _ string) (net.Conn, error) {
return r.dialer.DialContext(ctx, network, r.address())
}
func (r *resolver) address() string {
return r.addrs[atomic.AddUint64(&r.idx, 1)%uint64(len(r.addrs))]
}
================================================
FILE: internal/resolver/resolver_test.go
================================================
package resolver
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/miekg/dns"
)
const (
fakeDomain = "acme.notadomain"
)
func TestResolver(t *testing.T) {
dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
m := &dns.Msg{}
m.SetReply(r)
localIP := net.ParseIP("127.0.0.1")
defer func() {
err := w.WriteMsg(m)
if err != nil {
t.Logf("got error writing dns message: %s", err)
}
}()
if len(r.Question) == 0 {
m.RecursionAvailable = true
m.SetRcode(r, dns.RcodeRefused)
return
}
q := r.Question[0]
if q.Name == fakeDomain+"." {
m.Answer = []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 1,
},
A: localIP,
}}
} else {
m.SetRcode(r, dns.RcodeNameError)
}
})
const payload = "there is no cloud, just someone else's computer"
done := make(chan struct{})
ds := dns.Server{
Addr: "127.0.0.1:0",
Net: "udp",
UDPSize: dns.MinMsgSize,
ReadTimeout: 2 * time.Second,
WriteTimeout: 2 * time.Second,
NotifyStartedFunc: func() { close(done) },
}
go func() {
err := ds.ListenAndServe()
if err != nil {
t.Logf("got error during dns ListenAndServe: %s", err)
}
}()
defer func() {
_ = ds.Shutdown()
}()
// wait for notify function to be called, ensuring ds.PacketConn is not nil.
<-done
res, err := NewResolver([]string{ds.PacketConn.LocalAddr().String()})
if err != nil {
t.Fatalf("error from NewResolver: %s", err)
}
net.DefaultResolver = res
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, payload)
}))
defer ts.Close()
tsurl, _ := url.Parse(ts.URL)
_, hport, err := net.SplitHostPort(tsurl.Host)
if err != nil {
t.Fatalf("could not parse port from httptest url %s: %s", ts.URL, err)
}
tsurl.Host = net.JoinHostPort(fakeDomain, hport)
resp, err := http.Get(tsurl.String())
if err != nil {
t.Fatalf("failed resolver round trip: %s", err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read respose body")
}
if strings.TrimSpace(string(body)) != payload {
t.Errorf("body mismatch, got: '%s', expected: '%s'", body, payload)
}
}
func TestNormalizeAddrs(t *testing.T) {
for _, tc := range []struct {
name string
in []string
out []string
err error
}{
{
name: "default port 53",
in: []string{"127.0.0.1"},
out: []string{"127.0.0.1:53"},
},
{
name: "invalid host port",
in: []string{"127.0.0.1.boom:53"},
err: errors.New("host 127.0.0.1.boom is not an IP address"),
},
{
name: "invalid port",
in: []string{"127.0.0.1:999999999"},
err: errors.New(`strconv.ParseUint: parsing "999999999": value out of range`),
},
{
name: "invalid IP",
in: []string{"127.0.0.500:53"},
err: errors.New(`host 127.0.0.500 is not an IP address`),
},
{
name: "normalized",
in: []string{"127.0.0.1", "8.8.8.8:9000", "1.1.1.1"},
out: []string{"127.0.0.1:53", "8.8.8.8:9000", "1.1.1.1:53"},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
addrs, err := normalizeAddrs(tc.in)
if have, want := addrs, tc.out; !reflect.DeepEqual(have, want) {
t.Errorf("have addrs: %v, want: %v", have, want)
}
if have, want := fmt.Sprint(err), fmt.Sprint(tc.err); have != want {
t.Errorf("have err: %v, want: %v", have, want)
}
})
}
}
================================================
FILE: lib/attack.go
================================================
package vegeta
import (
"context"
"crypto/tls"
"fmt"
"io"
"math"
"math/rand"
"net"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/rs/dnscache"
"golang.org/x/net/http2"
)
// Attacker is an attack executor which wraps an http.Client
type Attacker struct {
dialer *net.Dialer
client http.Client
stopch chan struct{}
stopOnce sync.Once
workers uint64
maxWorkers uint64
maxBody int64
redirects int
seqmu sync.Mutex
seq uint64
began time.Time
chunked bool
}
const (
// DefaultRedirects is the default number of times an Attacker follows
// redirects.
DefaultRedirects = 10
// DefaultTimeout is the default amount of time an Attacker waits for a request
// before it times out.
DefaultTimeout = 30 * time.Second
// 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.
DefaultMaxWorkers = math.MaxUint64
// DefaultMaxBody is the default max number of bytes to be read from response bodies.
// Defaults to no limit.
DefaultMaxBody = int64(-1)
// NoFollow is the value when redirects are not followed but marked successful
NoFollow = -1
)
var (
// DefaultLocalAddr is the default local IP address an Attacker uses.
DefaultLocalAddr = net.IPAddr{IP: net.IPv4zero}
// DefaultTLSConfig is the default tls.Config an Attacker uses.
DefaultTLSConfig = &tls.Config{InsecureSkipVerify: false}
)
// NewAttacker returns a new Attacker with default options which are overridden
// by the optionally provided opts.
func NewAttacker(opts ...func(*Attacker)) *Attacker {
a := &Attacker{
stopch: make(chan struct{}),
stopOnce: sync.Once{},
workers: DefaultWorkers,
maxWorkers: DefaultMaxWorkers,
maxBody: DefaultMaxBody,
}
a.dialer = &net.Dialer{
LocalAddr: &net.TCPAddr{IP: DefaultLocalAddr.IP, Zone: DefaultLocalAddr.Zone},
KeepAlive: 30 * time.Second,
}
a.client = http.Client{
Timeout: DefaultTimeout,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: a.dialer.DialContext,
TLSClientConfig: DefaultTLSConfig,
MaxIdleConnsPerHost: DefaultConnections,
MaxConnsPerHost: DefaultMaxConnections,
},
}
for _, opt := range opts {
opt(a)
}
return a
}
// Workers returns a functional option which sets the initial number of workers
// an Attacker uses to hit its targets. More workers may be spawned dynamically
// to sustain the requested rate in the face of slow responses and errors.
func Workers(n uint64) func(*Attacker) {
return func(a *Attacker) { a.workers = n }
}
// MaxWorkers returns a functional option which sets the maximum number of workers
// an Attacker can use to hit its targets.
func MaxWorkers(n uint64) func(*Attacker) {
return func(a *Attacker) { a.maxWorkers = n }
}
// Connections returns a functional option which sets the number of maximum idle
// open connections per target host.
func Connections(n int) func(*Attacker) {
return func(a *Attacker) {
tr := a.client.Transport.(*http.Transport)
tr.MaxIdleConnsPerHost = n
}
}
// 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) {
return func(a *Attacker) {
a.redirects = n
a.client.CheckRedirect = func(_ *http.Request, via []*http.Request) error {
switch {
case n == NoFollow:
return http.ErrUseLastResponse
case n < len(via):
return fmt.Errorf("stopped after %d redirects", n)
default:
return nil
}
}
}
}
// Proxy returns a functional option which sets the `Proxy` field on
// the http.Client's Transport
func Proxy(proxy func(*http.Request) (*url.URL, error)) func(*Attacker) {
return func(a *Attacker) {
tr := a.client.Transport.(*http.Transport)
tr.Proxy = proxy
}
}
// Timeout returns a functional option which sets the maximum amount of time
// an Attacker will wait for a request to be responded to and completely read.
func Timeout(d time.Duration) func(*Attacker) {
return func(a *Attacker) {
a.client.Timeout = d
}
}
// LocalAddr returns a functional option which sets the local address
// an Attacker will use with its requests.
func LocalAddr(addr net.IPAddr) func(*Attacker) {
return func(a *Attacker) {
tr := a.client.Transport.(*http.Transport)
a.dialer.LocalAddr = &net.TCPAddr{IP: addr.IP, Zone: addr.Zone}
tr.DialContext = a.dialer.DialContext
}
}
// KeepAlive returns a functional option which toggles KeepAlive
// connections on the dialer and transport.
func KeepAlive(keepalive bool) func(*Attacker) {
return func(a *Attacker) {
tr := a.client.Transport.(*http.Transport)
tr.DisableKeepAlives = !keepalive
if !keepalive {
a.dialer.KeepAlive = 0
tr.DialContext = a.dialer.DialContext
}
}
}
// TLSConfig returns a functional option which sets the *tls.Config for a
// Attacker to use with its requests.
func TLSConfig(c *tls.Config) func(*Attacker) {
return func(a *Attacker) {
tr := a.client.Transport.(*http.Transport)
tr.TLSClientConfig = c
}
}
// HTTP2 returns a functional option which enables or disables HTTP/2 support
// on requests performed by an Attacker.
func HTTP2(enabled bool) func(*Attacker) {
return func(a *Attacker) {
if tr := a.client.Transport.(*http.Transport); enabled {
http2.ConfigureTransport(tr)
} else {
tr.ForceAttemptHTTP2 = false
tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
}
}
}
// H2C returns a functional option which enables H2C support on requests
// performed by an Attacker
func H2C(enabled bool) func(*Attacker) {
return func(a *Attacker) {
if tr := a.client.Transport.(*http.Transport); enabled {
a.client.Transport = &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return tr.DialContext(ctx, network, addr)
},
}
}
}
}
// MaxBody returns a functional option which limits the max number of bytes
// read from response bodies. Set to -1 to disable any limits.
func MaxBody(n int64) func(*Attacker) {
return func(a *Attacker) { a.maxBody = n }
}
// UnixSocket changes the dialer for the attacker to use the specified unix socket file
func UnixSocket(socket string) func(*Attacker) {
return func(a *Attacker) {
if tr, ok := a.client.Transport.(*http.Transport); socket != "" && ok {
tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socket)
}
}
}
}
// SessionTickets returns a functional option which configures usage of session
// tickets for TLS session resumption.
func SessionTickets(enabled bool) func(*Attacker) {
return func(a *Attacker) {
if enabled {
cf := a.client.Transport.(*http.Transport).TLSClientConfig
cf.SessionTicketsDisabled = false
cf.ClientSessionCache = tls.NewLRUClientSessionCache(0)
}
}
}
// Client returns a functional option that allows you to bring your own http.Client
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
}
}
}
// ConnectTo returns a functional option which makes the attacker use the
// passed in map to translate target addr:port pairs. When used with DNSCaching,
// it must be used after it.
func ConnectTo(addrMap map[string][]string) func(*Attacker) {
return func(a *Attacker) {
if len(addrMap) == 0 {
return
}
tr, ok := a.client.Transport.(*http.Transport)
if !ok {
return
}
dial := tr.DialContext
if dial == nil {
dial = a.dialer.DialContext
}
type roundRobin struct {
addrs []string
n int
}
connectTo := make(map[string]*roundRobin, len(addrMap))
for k, v := range addrMap {
connectTo[k] = &roundRobin{addrs: v}
}
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
if cm, ok := connectTo[addr]; ok {
cm.n = (cm.n + 1) % len(cm.addrs)
addr = cm.addrs[cm.n]
}
return dial(ctx, network, addr)
}
}
}
// DNSCaching returns a functional option that enables DNS caching for
// the given ttl. When ttl is zero cached entries will never expire.
// When ttl is non-zero, this will start a refresh go-routine that updates
// the cache every ttl interval. This go-routine will be stopped when the
// attack is stopped.
// When the ttl is negative, no caching will be performed.
func DNSCaching(ttl time.Duration) func(*Attacker) {
return func(a *Attacker) {
if ttl < 0 {
return
}
if tr, ok := a.client.Transport.(*http.Transport); ok {
dial := tr.DialContext
if dial == nil {
dial = a.dialer.DialContext
}
resolver := &dnscache.Resolver{}
if ttl != 0 {
go func() {
refresh := time.NewTicker(ttl)
defer refresh.Stop()
for {
select {
case <-refresh.C:
resolver.Refresh(true)
case <-a.stopch:
return
}
}
}()
}
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
tr.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ips, err := resolver.LookupHost(ctx, host)
if err != nil {
return nil, err
}
if len(ips) == 0 {
return nil, &net.DNSError{Err: "no such host", Name: addr}
}
// Pick a random IP from each IP family and dial each concurrently.
// The first that succeeds wins, the other gets canceled.
rng.Shuffle(len(ips), func(i, j int) { ips[i], ips[j] = ips[j], ips[i] })
ips = firstOfEachIPFamily(ips)
type result struct {
conn net.Conn
err error
}
ch := make(chan result, len(ips))
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for _, ip := range ips {
go func(ip string) {
conn, err := dial(ctx, network, net.JoinHostPort(ip, port))
if err == nil {
cancel()
}
ch <- result{conn, err}
}(ip)
}
for i := 0; i < cap(ch); i++ {
if r := <-ch; conn == nil {
conn, err = r.conn, r.err
}
}
return conn, err
}
}
}
}
// firstOfEachIPFamily returns the first IP of each IP family in the input slice.
func firstOfEachIPFamily(ips []string) []string {
if len(ips) == 0 {
return ips
}
var (
lastV4 bool
each = ips[:0]
)
for i := 0; i < len(ips) && len(each) < 2; i++ {
ip := net.ParseIP(ips[i])
if ip == nil {
continue
}
isV4 := ip.To4() != nil
if len(each) == 0 || isV4 != lastV4 {
each = append(each, ips[i])
lastV4 = isV4
}
}
return each
}
type attack struct {
name string
began time.Time
seqmu sync.Mutex
seq uint64
}
// 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
// as they arrive and will have their Attack field set to the given name.
func (a *Attacker) Attack(tr Targeter, p Pacer, du time.Duration, name string) <-chan *Result {
var wg sync.WaitGroup
workers := a.workers
if workers > a.maxWorkers {
workers = a.maxWorkers
}
atk := &attack{
name: name,
began: time.Now(),
}
results := make(chan *Result)
ticks := make(chan struct{})
for i := uint64(0); i < workers; i++ {
wg.Add(1)
go a.attack(tr, atk, &wg, ticks, results)
}
go func() {
defer func() {
close(ticks)
wg.Wait()
close(results)
a.Stop()
}()
count := uint64(0)
for {
elapsed := time.Since(atk.began)
if du > 0 && elapsed > du {
return
}
wait, stop := p.Pace(elapsed, count)
if stop {
return
}
time.Sleep(wait)
if workers < a.maxWorkers {
select {
case ticks <- struct{}{}:
count++
continue
case <-a.stopch:
return
default:
// all workers are blocked. start one more and try again
workers++
wg.Add(1)
go a.attack(tr, atk, &wg, ticks, results)
}
}
select {
case ticks <- struct{}{}:
count++
case <-a.stopch:
return
}
}
}()
return results
}
// Stop stops the current attack. The return value indicates whether this call
// has signalled the attack to stop (`true` for the first call) or whether it
// was a noop because it has been previously signalled to stop (`false` for any
// subsequent calls).
func (a *Attacker) Stop() bool {
select {
case <-a.stopch:
return false
default:
a.stopOnce.Do(func() { close(a.stopch) })
return true
}
}
func (a *Attacker) attack(tr Targeter, atk *attack, workers *sync.WaitGroup, ticks <-chan struct{}, results chan<- *Result) {
defer workers.Done()
for range ticks {
results <- a.hit(tr, atk)
}
}
func (a *Attacker) hit(tr Targeter, atk *attack) *Result {
var (
res = Result{Attack: atk.name}
tgt Target
err error
)
//
// Subtleness ahead! We need to compute the result timestamp in
// the same critical section that protects the increment of the sequence
// number because we want the same total ordering of timestamps and sequence
// numbers. That is, we wouldn't want two results A and B where A.seq > B.seq
// but A.timestamp < B.timestamp.
//
// Additionally, we calculate the result timestamp based on the same beginning
// timestamp using the Add method, which will use monotonic time calculations.
//
atk.seqmu.Lock()
res.Timestamp = atk.began.Add(time.Since(atk.began))
res.Seq = atk.seq
atk.seq++
atk.seqmu.Unlock()
defer func() {
res.Latency = time.Since(res.Timestamp)
if err != nil {
res.Error = err.Error()
}
}()
if err = tr(&tgt); err != nil {
a.Stop()
return &res
}
res.Method = tgt.Method
res.URL = tgt.URL
req, err := tgt.Request()
if err != nil {
return &res
}
if atk.name != "" {
req.Header.Set("X-Vegeta-Attack", atk.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
}
defer r.Body.Close()
body := io.Reader(r.Body)
if a.maxBody >= 0 {
body = io.LimitReader(r.Body, a.maxBody)
}
if res.Body, err = io.ReadAll(body); err != nil {
return &res
} else if _, err = io.Copy(io.Discard, r.Body); err != nil {
return &res
}
res.BytesIn = uint64(len(res.Body))
if req.ContentLength != -1 {
res.BytesOut = uint64(req.ContentLength)
}
if res.Code = uint16(r.StatusCode); res.Code < 200 || res.Code >= 400 {
res.Error = r.Status
}
res.Headers = r.Header
return &res
}
================================================
FILE: lib/attack_fuzz.go
================================================
//go:build gofuzz
// +build gofuzz
package vegeta
import (
"encoding/binary"
"fmt"
"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 := os.MkdirTemp("/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 := os.MkdirTemp("/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
}
================================================
FILE: lib/attack_test.go
================================================
package vegeta
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func TestAttackRate(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
)
defer server.Close()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
rate := Rate{Freq: 100, Per: time.Second}
atk := NewAttacker()
var hits uint64
for range atk.Attack(tr, rate, 1*time.Second, "") {
hits++
}
if got, want := hits, uint64(rate.Freq); got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
}
func TestAttackDuration(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
)
defer server.Close()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
atk := NewAttacker()
rate := Rate{Freq: 100, Per: time.Second}
var m Metrics
for res := range atk.Attack(tr, rate, rate.Per, "") {
m.Add(res)
}
m.Close()
if got, want := m.Requests, uint64(rate.Freq); got != want {
t.Errorf("got %v hits, want: %v", got, want)
} else if got, want := m.Duration.Round(time.Second), time.Second; got != want {
t.Errorf("got duration %s, want %s", got, want)
}
}
func TestTLSConfig(t *testing.T) {
atk := NewAttacker()
got := atk.client.Transport.(*http.Transport).TLSClientConfig
if want := (&tls.Config{InsecureSkipVerify: false}); !reflect.DeepEqual(got, want) {
t.Fatalf("got: %+v, want: %+v", got, want)
}
}
func TestRedirects(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/redirect", 302)
}),
)
defer server.Close()
redirects := 2
atk := NewAttacker(Redirects(redirects))
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
want := fmt.Sprintf("stopped after %d redirects", redirects)
if got := res.Error; !strings.HasSuffix(got, want) {
t.Fatalf("want: '%v' in '%v'", want, got)
}
}
func TestNoFollow(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/redirect-here", 302)
}),
)
defer server.Close()
atk := NewAttacker(Redirects(NoFollow))
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if res.Error != "" {
t.Fatalf("got err: %v", res.Error)
}
if res.Code != 302 {
t.Fatalf("res.Code => %d, want %d", res.Code, 302)
}
}
func TestTimeout(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
<-time.After(20 * time.Millisecond)
}),
)
defer server.Close()
atk := NewAttacker(Timeout(10 * time.Millisecond))
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
want := "Client.Timeout exceeded while awaiting headers"
if got := res.Error; !strings.Contains(got, want) {
t.Fatalf("want: '%v' in '%v'", want, got)
}
if res.Latency == 0 {
t.Fatal("Latency wasn't captured with a timed-out result")
}
}
func TestLocalAddr(t *testing.T) {
t.Parallel()
addr, err := net.ResolveIPAddr("ip", "127.0.0.1")
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
t.Fatal(err)
} else if want := addr.String(); got != want {
t.Fatalf("wrong source address. got %v, want: %v", got, want)
}
}),
)
defer server.Close()
atk := NewAttacker(LocalAddr(*addr))
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
atk.hit(tr, &attack{name: "", began: time.Now()})
}
func TestKeepAlive(t *testing.T) {
t.Parallel()
atk := NewAttacker(KeepAlive(false))
if got, want := atk.dialer.KeepAlive, time.Duration(0); got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
got := atk.client.Transport.(*http.Transport).DisableKeepAlives
if want := true; got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
}
// This test cannot be run in parallel with TestTLSConfig() because ClientSessionCache
// is designed to be called concurrently from different goroutines.
func TestSessionTickets(t *testing.T) {
atk := NewAttacker(SessionTickets(true))
cf := atk.client.Transport.(*http.Transport).TLSClientConfig
got, want := cf.SessionTicketsDisabled, false
if got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
if cf.ClientSessionCache == nil {
t.Fatalf("ClientSessionCache is nil")
}
}
func TestConnections(t *testing.T) {
t.Parallel()
atk := NewAttacker(Connections(23))
got := atk.client.Transport.(*http.Transport).MaxIdleConnsPerHost
if want := 23; got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
}
func TestStatusCodeErrors(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}),
)
defer server.Close()
atk := NewAttacker()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if got, want := res.Error, "400 Bad Request"; got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
}
func TestBadTargeterError(t *testing.T) {
t.Parallel()
atk := NewAttacker()
tr := func(*Target) error { return io.EOF }
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if got, want := res.Error, io.EOF.Error(); got != want {
t.Fatalf("got: %v, want: %v", got, want)
}
}
func TestResponseBodyCapture(t *testing.T) {
t.Parallel()
want := []byte("VEGETA")
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(want)
}),
)
defer server.Close()
atk := NewAttacker()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if got := res.Body; !bytes.Equal(got, want) {
t.Fatalf("got: %v, want: %v", got, want)
}
}
func TestProxyOption(t *testing.T) {
t.Parallel()
body := []byte("PROXIED!")
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(body)
}),
)
defer server.Close()
proxyURL, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
atk := NewAttacker(Proxy(func(r *http.Request) (*url.URL, error) {
return proxyURL, nil
}))
tr := NewStaticTargeter(Target{Method: "GET", URL: "http://127.0.0.2"})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if got, want := res.Error, ""; got != want {
t.Errorf("got error: %q, want %q", got, want)
}
if got, want := res.Body, body; !bytes.Equal(got, want) {
t.Errorf("got body: %q, want: %q", got, want)
}
}
func TestMaxBody(t *testing.T) {
t.Parallel()
body := []byte("VEGETA")
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(body)
}),
)
defer server.Close()
for i := DefaultMaxBody; i < int64(len(body)); i++ {
maxBody := i
t.Run(fmt.Sprint(maxBody), func(t *testing.T) {
atk := NewAttacker(MaxBody(maxBody))
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
want := body
if maxBody >= 0 {
want = want[:maxBody]
}
if got := res.Body; !bytes.Equal(got, want) {
t.Fatalf("got: %s, want: %s", got, want)
}
})
}
}
func TestUnixSocket(t *testing.T) {
t.Parallel()
body := []byte("IT'S A UNIX SYSTEM, I KNOW THIS")
socketDir, err := os.MkdirTemp("", "vegata")
if err != nil {
t.Fatal("Failed to create socket dir", err)
}
defer os.RemoveAll(socketDir)
socketFile := filepath.Join(socketDir, "test.sock")
unixListener, err := net.Listen("unix", socketFile)
if err != nil {
t.Fatal("Failed to listen on unix socket", err)
}
server := http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(body)
}),
}
defer server.Close()
go server.Serve(unixListener)
start := time.Now()
for {
if time.Since(start) > 1*time.Second {
t.Fatal("Server didn't listen on unix socket in time")
}
_, err := os.Stat(socketFile)
if err == nil {
break
} else if os.IsNotExist(err) {
time.Sleep(10 * time.Millisecond)
} else {
t.Fatal("unexpected error from unix socket", err)
}
}
atk := NewAttacker(UnixSocket(socketFile))
tr := NewStaticTargeter(Target{Method: "GET", URL: "http://anyserver/"})
res := atk.hit(tr, &attack{name: "", began: time.Now()})
if !bytes.Equal(res.Body, body) {
t.Fatalf("got: %s, want: %s", string(res.Body), string(body))
}
}
func TestClient(t *testing.T) {
t.Parallel()
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{IP: DefaultLocalAddr.IP, Zone: DefaultLocalAddr.Zone},
KeepAlive: 30 * time.Second,
}
client := &http.Client{
Timeout: 1 * time.Nanosecond,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
TLSClientConfig: DefaultTLSConfig,
MaxIdleConnsPerHost: DefaultConnections,
},
}
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {}
}),
)
defer server.Close()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
atk := NewAttacker(Client(client))
resp := atk.hit(tr, &attack{name: "TEST", began: time.Now()})
if !strings.Contains(resp.Error, "Client.Timeout exceeded while awaiting headers") {
t.Errorf("Expected timeout error")
}
}
func TestVegetaHeaders(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(r.Header)
}),
)
defer server.Close()
tr := NewStaticTargeter(Target{Method: "GET", URL: server.URL})
a := NewAttacker()
atk := &attack{name: "ig-bang", began: time.Now()}
for seq := 0; seq < 5; seq++ {
res := a.hit(tr, atk)
var hdr http.Header
if err := json.Unmarshal(res.Body, &hdr); err != nil {
t.Fatal(err)
}
if have, want := hdr.Get("X-Vegeta-Attack"), atk.name; have != want {
t.Errorf("X-Vegeta-Attack: have %q, want %q", have, want)
}
if have, want := hdr.Get("X-Vegeta-Seq"), strconv.Itoa(seq); have != want {
t.Errorf("X-Vegeta-Seq: have %q, want %q", have, want)
}
}
}
// https://github.com/tsenart/vegeta/issues/649
func TestDNSCaching_Issue649(t *testing.T) {
defer func() {
if err := recover(); err != nil {
t.Fatalf("panic: %v", err)
}
}()
tr := NewStaticTargeter(Target{Method: "GET", URL: "https://[2a00:1450:4005:802::200e]"})
atk := NewAttacker(DNSCaching(0))
_ = atk.hit(tr, &attack{name: "TEST", began: time.Now()})
}
func TestFirstOfEachIPFamily(t *testing.T) {
tests := []struct {
name string
input []string
want []string
}{
{
name: "empty list",
input: []string{},
want: []string{},
},
{
name: "single IPv4",
input: []string{"192.168.1.1"},
want: []string{"192.168.1.1"},
},
{
name: "single IPv6",
input: []string{"fe80::1"},
want: []string{"fe80::1"},
},
{
name: "multiple IPv6",
input: []string{"fe80::1", "fe80::2"},
want: []string{"fe80::1"},
},
{
name: "one IPv4 and one IPv6",
input: []string{"192.168.1.1", "fe80::1"},
want: []string{"192.168.1.1", "fe80::1"},
},
{
name: "one IPv6 and one IPv4",
input: []string{"fe80::1", "192.168.1.1"},
want: []string{"fe80::1", "192.168.1.1"},
},
{
name: "multiple IPs with alternating versions",
input: []string{"192.168.1.1", "fe80::1", "192.168.1.2", "fe80::2"},
want: []string{"192.168.1.1", "fe80::1"},
},
{
name: "multiple IPs with same versions",
input: []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"},
want: []string{"192.168.1.1"},
},
{
name: "multiple IPs with non-alternating versions",
input: []string{"192.168.1.1", "fe80::1", "192.168.1.2", "192.168.1.3", "fe80::2"},
want: []string{"192.168.1.1", "fe80::1"},
},
{
name: "invalid IP addresses",
input: []string{"invalid", "192.168.1.1", "fe80::1"},
want: []string{"192.168.1.1", "fe80::1"},
},
{
name: "IPv4 with embedded IPv6",
input: []string{"192.168.1.1", "::ffff:c000:280", "fe80::1"},
want: []string{"192.168.1.1", "fe80::1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := firstOfEachIPFamily(tt.input)
if len(result) != len(tt.want) {
t.Fatalf("want %v, got %v", tt.want, result)
}
if diff := cmp.Diff(tt.want, result); diff != "" {
t.Errorf("unexpected result (-want +got):\n%s", diff)
}
})
}
}
func TestAttackConnectTo(t *testing.T) {
t.Parallel()
var mu sync.Mutex
hits := make(map[string]int)
srvs := make(map[string]int)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
hits[r.Host]++
mu.Unlock()
})
addrs := make([]string, 3)
for i := range addrs {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
addrs[i] = ln.Addr().String()
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
srvs[ln.Addr().String()]++
mu.Unlock()
handler.ServeHTTP(w, r)
}))
srv.Listener = ln
srv.Start()
t.Cleanup(srv.Close)
}
tr := NewStaticTargeter(
Target{Method: "GET", URL: "http://sapo.pt:80"},
Target{Method: "GET", URL: "http://sapo.pt:80"},
Target{Method: "GET", URL: "http://sapo.pt:80"},
Target{Method: "GET", URL: "http://" + addrs[0]},
)
atk := NewAttacker(
KeepAlive(false),
ConnectTo(map[string][]string{"sapo.pt:80": addrs}),
)
a := &attack{name: "TEST", began: time.Now()}
for i := 0; i < 4; i++ {
resp := atk.hit(tr, a)
if resp.Error != "" {
t.Fatal(resp.Error)
}
}
want := map[string]int{"sapo.pt:80": 3, addrs[0]: 1}
if diff := cmp.Diff(want, hits); diff != "" {
t.Errorf("unexpected hits (-want +got):\n%s", diff)
}
want = map[string]int{addrs[0]: 2, addrs[1]: 1, addrs[2]: 1}
if diff := cmp.Diff(want, srvs); diff != "" {
t.Errorf("unexpected hits (-want +got):\n%s", diff)
}
}
================================================
FILE: lib/histogram.go
================================================
package vegeta
import (
"bytes"
"fmt"
"strings"
"time"
)
// Buckets represents an Histogram's latency buckets.
type Buckets []time.Duration
// Histogram is a bucketed latency Histogram.
type Histogram struct {
Buckets Buckets
Counts []uint64
Total uint64
}
// Add implements the Add method of the Report interface by finding the right
// Bucket for the given Result latency and increasing its count by one as well
// as the total count.
func (h *Histogram) Add(r *Result) {
if len(h.Counts) != len(h.Buckets) {
h.Counts = make([]uint64, len(h.Buckets))
}
var i int
for ; i < len(h.Buckets)-1; i++ {
if r.Latency >= h.Buckets[i] && r.Latency < h.Buckets[i+1] {
break
}
}
h.Total++
h.Counts[i]++
}
// MarshalJSON returns a JSON encoding of the buckets and their counts.
func (h *Histogram) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
// Custom marshalling to guarantee order.
buf.WriteString("{")
for i := range h.Buckets {
if i > 0 {
buf.WriteString(", ")
}
if _, err := fmt.Fprintf(&buf, "\"%d\": %d", h.Buckets[i], h.Counts[i]); err != nil {
return nil, err
}
}
buf.WriteString("}")
return buf.Bytes(), nil
}
// Nth returns the nth bucket represented as a string.
func (bs Buckets) Nth(i int) (left, right string) {
if i >= len(bs)-1 {
return bs[i].String(), "+Inf"
}
return bs[i].String(), bs[i+1].String()
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
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 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 {
return fmt.Errorf("bad buckets: %s", value)
}
return nil
}
================================================
FILE: lib/histogram_test.go
================================================
package vegeta
import (
"reflect"
"testing"
"time"
)
func TestHistogram_Add(t *testing.T) {
t.Parallel()
hist := Histogram{
Buckets: []time.Duration{
0,
10 * time.Millisecond,
25 * time.Millisecond,
50 * time.Millisecond,
100 * time.Millisecond,
1000 * time.Millisecond,
},
}
for _, d := range []time.Duration{
5 * time.Millisecond,
15 * time.Millisecond,
30 * time.Millisecond,
75 * time.Millisecond,
200 * time.Millisecond,
2000 * time.Millisecond,
} {
hist.Add(&Result{Latency: d})
}
if got, want := hist.Counts, []uint64{1, 1, 1, 1, 1, 1}; !reflect.DeepEqual(got, want) {
t.Errorf("Counts: got: %v, want: %v", got, want)
}
if got, want := hist.Total, uint64(6); got != want {
t.Errorf("Total: got %v, want: %v", got, want)
}
}
func TestBuckets_UnmarshalText(t *testing.T) {
t.Parallel()
for value, want := range map[string]string{
"": "bad buckets: ",
" ": "bad buckets: ",
"{0, 2}": "bad buckets: {0, 2}",
"[]": `time: invalid duration ""`,
"[0, 2]": `time: missing unit in duration "2"`,
} {
if got := (&Buckets{}).UnmarshalText([]byte(value)).Error(); got != want {
t.Errorf("got: %v, want: %v", got, want)
}
}
for value, want := range map[string]Buckets{
"[0,5ms]": {0, 5 * time.Millisecond},
"[0, 5ms]": {0, 5 * time.Millisecond},
"[ 0,5ms, 10m ]": {0, 5 * time.Millisecond, 10 * time.Minute},
"[3ms,10ms]": {0, 3 * time.Millisecond, 10 * time.Millisecond},
} {
var got Buckets
if err := got.UnmarshalText([]byte(value)); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
}
}
}
================================================
FILE: lib/lttb/lttb.go
================================================
package lttb
import "errors"
// A Point in a line chart.
type Point struct{ X, Y float64 }
// An Iter is an iterator function that returns
// count number of Points or an error.
type Iter func(count int) ([]Point, error)
// Downsample `count` number of data points retrieved from the given iterator
// function to contain only `threshold` number of points while maintaining close
// visual similarity to the original data. The algorithm is called
// Largest-Triangle-Three-Buckets and is described in:
// https://skemman.is/bitstream/1946/15343/3/SS_MSthesis.pdf
//
// This implementation grew out of https://github.com/dgryski/go-lttb
// to limit memory usage by leveraging iterators.
func Downsample(count, threshold int, it Iter) ([]Point, error) {
if threshold >= count || threshold == 0 {
points, err := it(count)
return points, err
}
if threshold < 3 {
return nil, errors.New("lttb: min threshold is 3")
}
// Bucket size. Leave room for start and end data points
size := float64(count-2) / float64(threshold-2)
// Get the first point and the current bucket.
points, err := it(int(1 + size))
if err != nil {
return nil, err
}
samples := make([]Point, 0, threshold)
samples = append(samples, points[0]) // Always add the first point
current := points[1:]
for i := 0; i < threshold-2; i++ {
// Calculate bucket boundaries (non inclusive hi)
lo := int(float64(i+1)*size) + 1
hi := int(float64(i+2)*size) + 1
next, err := it(hi - lo)
if err != nil {
return nil, err
}
samples = append(samples, sample(samples[len(samples)-1], current, next))
current = next
}
// Always add the last point unmodified
if points, err = it(count - len(samples)); err != nil {
return nil, err
} else if len(points) == 0 {
points = current
}
if len(points) > 0 {
samples = append(samples, points[len(points)-1])
}
return samples, nil
}
func sample(a Point, current, next []Point) (b Point) {
// Calculate point c as the average point of all points in the next bucket.
var c Point
for i := range next {
c.X, c.Y = c.X+next[i].X, c.Y+next[i].Y
}
length := float64(len(next))
c.X, c.Y = c.X/length, c.Y/length
// Find index of point b that together with points a and c forms the largest triangle
// amongst all points in the current bucket.
var largest float64
var index int
for i, p := range current {
// Calculate triangle area over three buckets
area := (a.X-c.X)*(p.Y-a.Y) - (a.X-p.X)*(c.Y-a.Y)
// We only care about the relative area here. Calling math.Abs() is slower than squaring.
if area *= area; area > largest {
largest, index = area, i
}
}
return current[index]
}
================================================
FILE: lib/lttb/lttb_test.go
================================================
package lttb
import (
"fmt"
"reflect"
"sync"
"testing"
"unsafe"
golttb "github.com/dgryski/go-lttb"
"github.com/google/go-cmp/cmp"
)
func TestDownsample(t *testing.T) {
t.Parallel()
for _, threshold := range []int{0, len(points[0]), len(points[0]) + 1} {
have, err := Downsample(len(points[0]), threshold, newIterator(points[0]))
if err != nil {
t.Fatalf("threshold-%d: got err: %v", threshold, err)
}
want := points[0]
if diff := cmp.Diff(have, want, cmp.AllowUnexported(Point{})); diff != "" {
t.Errorf("threshold-%d: %s", threshold, diff)
}
}
for i := 1; i < 3; i++ {
threshold := i
_, err := Downsample(len(points[0]), threshold, newIterator(points[0]))
if have, want := fmt.Sprint(err), "lttb: min threshold is 3"; have != want {
t.Errorf("threshold-%d: have err: %v, want %v", threshold, have, want)
}
}
var wg sync.WaitGroup
for i, ps := range points {
for threshold := 3; threshold < len(ps); threshold++ {
wg.Add(1)
i, ps, threshold := i, ps, threshold
go func() {
defer wg.Done()
msg := func(fmtstr string, args ...interface{}) string {
return fmt.Sprintf(
"points=%d len=%d threshold=%d: "+fmtstr,
append([]interface{}{i, len(ps), threshold}, args...)...,
)
}
ours, err := Downsample(len(ps), threshold, newIterator(ps))
if err != nil {
t.Error(msg("error: %v", err))
}
if have, want := len(ours), threshold; have != want {
t.Error(msg("len(samples) != threshold: have %d, want %d", have, want))
}
if have, want := ours[0], ps[0]; have != want {
t.Error(msg("samples[0] != data[0]: have %v, want %v", have, want))
}
if have, want := ours[len(ours)-1], ps[len(ps)-1]; have != want {
t.Error(msg("samples[-1] != data[-1]: have %v, want %v", have, want))
}
// Test LTTB algorithm's equivalence to dgrisky/go-lttb
in := *(*[]golttb.Point[float64])(unsafe.Pointer(&ps)) // #skipcq: GSC-G103
out := golttb.LTTB(in, threshold)
theirs := *(*[]Point)(unsafe.Pointer(&out)) // #skipcq: GSC-G103
if !reflect.DeepEqual(ours, theirs) {
t.Error(msg(cmp.Diff(ours, theirs, cmp.AllowUnexported(Point{}))))
}
}()
}
}
wg.Wait()
}
func BenchmarkLTTB(b *testing.B) {
data := *(*[]golttb.Point[float64])(unsafe.Pointer(&points[0])) // #skipcq: GSC-G103
b.Run("dgryski", func(b *testing.B) {
for i := 0; i < b.N; i++ {
golttb.LTTB(data, 1000)
}
})
b.Run("tsenart", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Downsample(len(data), 1000, newIterator(points[0]))
}
})
}
func newIterator(data []Point) Iter {
return func(count int) ([]Point, error) {
if count > len(data) {
count = len(data)
}
ps := data[:count]
data = data[count:]
return ps, nil
}
}
// From https://raw.githubusercontent.com/sveinn-steinarsson/flot-downsample/master/demo_data.js
var points = [][]Point{
{
{0, 29.357995947822218},
{1, 29.40932479606209},
{2, 29.28168582006162},
{3, 30.409965579108867},
{4, 30.7726859735917},
{5, 30.839942247539028},
{6, 30.760611642264667},
{7, 31.203663004229718},
{8, 31.38899603525572},
{9, 30.890299916955737},
{10, 30.467811944911556},
{11, 30.596837868069542},
{12, 30.59789593509767},
{13, 30.19693062465079},
{14, 29.89081330734553},
{15, 29.54668002901058},
{16, 29.54890739422219},
{17, 30.53743760171474},
{18, 30.74066032317061},
{19, 30.3774450601516},
{20, 30.095148889986568},
{21, 30.057979182917986},
{22, 30.364655421168525},
{23, 30.293450053773604},
{24, 30.14578230340987},
{25, 30.277772879951996},
{26, 30.3711931235659},
{27, 30.355932660992572},
{28, 29.994740831603046},
{29, 29.938012885023657},
{30, 29.557268760451187},
{31, 29.16161297604625},
{32, 29.6921163421055},
{33, 30.246270628292226},
{34, 30.210955154680928},
{35, 29.382964668934058},
{36, 29.52892023906641},
{37, 29.719742065732202},
{38, 30.013856636945924},
{39, 29.818636169776926},
{40, 27.931224826933345},
{41, 28.103057873678374},
{42, 28.276025902356782},
{43, 28.193497487789774},
{44, 28.22099171488288},
{45, 28.06872431241809},
{46, 27.239835885250365},
{47, 26.606504358317906},
{48, 27.220308136213916},
{49, 25.80790898978574},
{50, 26.060334838062595},
{51, 25.89611036822564},
{52, 25.824433242208308},
{53, 25.89212193260212},
{54, 27.28963669872412},
{55, 27.208259984780806},
{56, 27.298211480886604},
{57, 27.056163307023077},
{58, 26.434701869643924},
{59, 26.21938188318228},
{60, 26.402877759468772},
{61, 26.326019357967294},
{62, 27.592635104460253},
{63, 27.597468405434316},
{64, 27.533410081050647},
{65, 27.30713964830731},
{66, 27.170789712751404},
{67, 27.610166301145746},
{68, 27.619847332319885},
{69, 27.55974343996647},
{70, 28.168803492093716},
{71, 28.284229862452584},
{72, 28.292638086760352},
{73, 28.523718178262335},
{74, 28.52268591410558},
{75, 28.576637206595993},
{76, 27.763715461946813},
{77, 27.48488075310065},
{78, 27.304006460281794},
{79, 26.902025619666333},
{80, 26.690162079132378},
{81, 26.52277286738866},
{82, 26.09406789919051},
{83, 26.029039984983562},
{84, 26.145181108429618},
{85, 24.75335912549342},
{86, 24.787772395126176},
{87, 24.966652093726385},
{88, 25.33621421606084},
{89, 25.29283081429076},
{90, 25.427452306231707},
{91, 25.34334450564347},
{92, 25.971939771661045},
{93, 25.907910638400836},
{94, 25.971895864171003},
{95, 26.448419417928317},
{96, 26.461057396494514},
{97, 26.332417666691025},
{98, 26.17395667752311},
{99, 25.318121919340044},
{100, 26.092918888834987},
{101, 26.080903284583307},
{102, 26.65713935644778},
{103, 26.90227335392346},
{104, 27.14904791481757},
{105, 27.45775156727039},
{106, 28.476933177720472},
{107, 27.899526884805073},
{108, 27.274094274806096},
{109, 27.007320452528134},
{110, 27.767594298696466},
{111, 28.669197835798027},
{112, 28.78287095361503},
{113, 28.744062941208348},
{114, 29.266136201836588},
{115, 29.29631346563548},
{116, 29.30576721224685},
{117, 28.92417256867908},
{118, 29.172020952087326},
{119, 29.36572118529946},
{120, 29.407882394097168},
{121, 29.24581675687127},
{122, 30.374496989862006},
{123, 29.75867484229172},
{124, 29.724977349500893},
{125, 29.766232845230032},
{126, 29.755425025733203},
{127, 29.891515813029994},
{128, 29.224842590902917},
{129, 29.19894861750696},
{130, 28.877260649054524},
{131, 29.100160763856657},
{132, 29.11303194254891},
{133, 29.471499370130353},
{134, 29.725430707066725},
{135, 29.875645922022283},
{136, 29.32336837740634},
{137, 29.659223458914788},
{138, 29.916122460150415},
{139, 29.947591268372232},
{140, 29.912720660976237},
{141, 30.030001305562784},
{142, 30.01603062688162},
{143, 30.206305874273855},
{144, 29.654798308068155},
{145, 29.51005186796388},
{146, 29.479249298103124},
{147, 29.50465455692649},
{148, 28.668390496723326},
{149, 29.70235193454973},
{150, 29.651621644229916},
{151, 29.514646394709878},
{152, 29.506006233800203},
{153, 29.85094476486748},
{154, 29.815453456016304},
{155, 29.942433471924208},
{156, 29.690047803042003},
{157, 29.081114874079432},
{158, 29.064499954878283},
{159, 29.17427761652929},
{160, 28.48135454514647},
{161, 28.43614347392514},
{162, 28.615830690136196},
{163, 27.244815449582045},
{164, 28.030839937141593},
{165, 28.09433740820739},
{166, 28.134090510807432},
{167, 28.333335545414524},
{168, 28.187860385951467},
{169, 28.275647207865163},
{170, 28.110663780904385},
{171, 27.941108395946074},
{172, 27.155839334130572},
{173, 27.486338387798014},
{174, 27.822551655905976},
{175, 27.863745721980674},
{176, 27.51492123912736},
{177, 27.83845450366903},
{178, 27.143346163904276},
{179, 26.470510229480666},
{180, 27.030260699355054},
{181, 26.530046883178517},
{182, 26.061343714664623},
{183, 26.451944204073293},
{184, 26.430516873002972},
{185, 27.54560649645601},
{186, 27.607455732128237},
{187, 27.3151823435893},
{188, 27.015508353290546},
{189, 27.564104270774138},
{190, 27.401004407024764},
{191, 27.158236015306873},
{192, 27.15560082391509},
{193, 27.052060260660955},
{194, 27.170854273897024},
{195, 27.764649442110628},
{196, 28.37485762180034},
{197, 28.693644963914128},
{198, 28.639988945921225},
{199, 28.55415913033328},
{200, 28.227269873938933},
{201, 28.17193587609251},
{202, 28.326544342536973},
{203, 27.64207374329132},
{204, 28.28132114918632},
{205, 28.87895845286964},
{206, 28.188530355189847},
{207, 27.543826104728634},
{208, 27.518668557969406},
{209, 27.501005443892083},
{210, 27.4034023213343},
{211, 27.81919230719886},
{212, 28.51752545491846},
{213, 28.45642871817993},
{214, 28.281434136230057},
{215, 28.955222450571647},
{216, 28.817758413114476},
{217, 28.512855374048605},
{218, 28.53649013938214},
{219, 28.037741647416063},
{220, 28.758762367232816},
{221, 28.411225526795217},
{222, 28.081382658095393},
{223, 28.018197966678386},
{224, 27.669840575416934},
{225, 27.729482622798493},
{226, 27.44804950151258},
{227, 27.057357503159633},
{228, 27.02667664651584},
{229, 26.99034079421472},
{230, 27.624460374012415},
{231, 26.6107079586933},
{232, 26.586632740266342},
{233, 26.50503068257422},
{234, 26.528094947562206},
{235, 27.42586110225183},
{236, 27.58607997646959},
{237, 27.119751888180552},
{238, 27.08687612699868},
{239, 26.50890594997075},
{240, 27.31126847238326},
{241, 27.241646652930587},
{242, 27.206837899664286},
{243, 27.806910729195003},
{244, 27.943871889687294},
{245, 27.682133734768627},
{246, 27.717567280916626},
{247, 28.50754775235408},
{248, 28.522618646129153},
{249, 28.119450170073687},
{250, 28.142446798473102},
{251, 27.493875384325104},
{252, 27.232866008532874},
{253, 26.815819408391537},
{254, 26.66806605894335},
{255, 26.832795316319906},
{256, 26.6709072973403},
{257, 26.805339614467922},
{258, 25.87709141573906},
{259, 25.78663848060838},
{260, 26.292688529709856},
{261, 26.13540568260593},
{262, 26.950679537489023},
{263, 26.74360016328177},
{264, 25.880978465490344},
{265, 26.625349316487267},
{266, 27.726596358113618},
{267, 27.678660195691705},
{268, 27.712915567796777},
{269, 27.721698306913026},
{270, 27.905267233295028},
{271, 27.957187159455156},
{272, 27.858194094400584},
{273, 28.081932932040928},
{274, 27.735044748607958},
{275, 27.032793497721116},
{276, 27.507939311361685},
{277, 27.574645954142067},
{278, 27.574051095225684},
{279, 27.759761781417595},
{280, 27.365795276665352},
{281, 26.617667215269847},
{282, 26.798097033417232},
{283, 26.195989054524656},
{284, 26.976938134909243},
{285, 26.89198848346594},
{286, 27.147250351373707},
{287, 27.43948527543756},
{288, 28.0008367927751},
{289, 28.231697069321097},
{290, 28.7967810312443},
{291, 29.512759843423506},
{292, 29.41555351035952},
{293, 30.70537467842703},
{294, 30.332009655179892},
{295, 30.368538690398843},
{296, 30.73440265654596},
{297, 30.967799052657455},
{298, 31.22392323854322},
{299, 31.122470949743033},
{300, 31.140432351354303},
{301, 31.13729855237942},
{302, 30.716789453632746},
{303, 31.055606178854195},
{304, 31.025310612429436},
{305, 30.746317663776093},
{306, 31.16989662941391},
{307, 29.623226075091324},
{308, 29.11707721446215},
{309, 29.671462419401518},
{310, 29.32656872224774},
{311, 29.360034344664133},
{312, 30.193073601940906},
{313, 30.051282853224887},
{314, 30.094809176026143},
{315, 30.62616938793058},
{316, 31.24738345775188},
{317, 31.288951935975373},
{318, 30.793680071961138},
{319, 30.855230722079575},
{320, 30.172789122099953},
{321, 30.16588226960243},
{322, 30.02896795596743},
{323, 28.710256908027688},
{324, 28.788445957400086},
{325, 29.14084131034792},
{326, 29.154793392113767},
{327, 29.509956518244064},
{328, 29.919766479376083},
{329, 30.809790278228583},
{330, 31.51994434827819},
{331, 31.14360887134284},
{332, 30.960009728867895},
{333, 31.007757592227232},
{334, 29.912529162927882},
{335, 30.430549132609936},
{336, 30.286270410087386},
{337, 30.29853067098949},
{338, 30.28484502358319},
{339, 30.2649148646112},
{340, 30.269814084876984},
{341, 29.48743777656781},
{342, 29.25324229055582},
{343, 29.3224085984498},
{344, 28.845749148292715},
{345, 28.25734414947841},
{346, 28.22244460496951},
{347, 28.212727797755832},
{348, 28.099709252761244},
{349, 28.157686542165766},
{350, 28.15110192590233},
{351, 28.242118337655835},
{352, 29.53367482995351},
{353, 29.532585019641683},
{354, 29.943653842668887},
{355, 28.64740678713178},
{356, 28.627878421859094},
{357, 28.85948017439502},
{358, 29.421837208187565},
{359, 29.213674689162733},
{360, 29.588524659441383},
{361, 29.871736651370572},
{362, 29.97536581410366},
{363, 29.090848126781193},
{364, 29.07367624649577},
{365, 28.936182688349078},
{366, 28.478980562612755},
{367, 27.701450632319055},
{368, 27.70314463302395},
{369, 27.690633468393905},
{370, 27.712418470005847},
{371, 28.04360951096915},
{372, 27.899319369636174},
{373, 27.67953057101954},
{374, 27.33713099741098},
{375, 27.715275282261704},
{376, 27.09457980740444},
{377, 26.564904718909652},
{378, 26.851805720484524},
{379, 27.17971899490677},
{380, 27.203035420946204},
{381, 26.98833921789535},
{382, 27.3376969508508},
{383, 27.36673199620505},
{384, 27.22367712016426},
{385, 27.295774446517406},
{386, 27.24426813335004},
{387, 27.804345559349205},
{388, 27.81107241888124},
{389, 28.007141902081127},
{390, 28.196204812447014},
{391, 27.570724094739713},
{392, 28.353947742891283},
{393, 28.264285808134655},
{394, 28.001544074943496},
{395, 28.073693599174273},
{396, 28.13948170648531},
{397, 27.7246917685699},
{398, 27.372056917732422},
{399, 26.76664288679182},
{400, 26.726530330089947},
{401, 26.73659456496834},
{402, 26.35282363879538},
{403, 26.953655414803745},
{404, 27.037728660238816},
{405, 26.391122852260175},
{406, 26.45637305967239},
{407, 25.78486318168484},
{408, 25.92078676697798},
{409, 26.4970571420541},
{410, 25.86809752383717},
{411, 26.534413662015773},
{412, 27.226357547185447},
{413, 26.71252553894152},
{414, 26.435002451423312},
{415, 26.921455461481504},
{416, 27.06045686220195},
{417, 27.09418382303274},
{418, 28.011098852919908},
{419, 27.896959438752912},
{420, 27.914042725695705},
{421, 27.547368437091734},
{422, 26.710013475304418},
{423, 26.67332778875601},
{424, 26.624659424882857},
{425, 26.57172379809563},
{426, 26.846697174621756},
{427, 25.8834697613218},
{428, 26.0400273638686},
{429, 25.922770964698366},
{430, 26.550977646141284},
{431, 26.7196171595444},
{432, 26.57693368973477},
{433, 26.754937488902705},
{434, 26.68923906981248},
{435, 27.578054073124644},
{436, 28.491944955522996},
{437, 29.462067517762534},
{438, 29.049663449716007},
{439, 29.29125398211083},
{440, 29.716399938469106},
{441, 29.697795912329543},
{442, 28.842426341018918},
{443, 27.995794268014496},
{444, 27.568707173494055},
{445, 27.572194900322412},
{446, 28.94876289646488},
{447, 28.827587778805206},
{448, 28.20122725800809},
{449, 28.982543824618322},
{450, 28.98764510982713},
{451, 28.476638374330257},
{452, 27.8971845311884},
{453, 28.357599004387694},
{454, 29.550067169716076},
{455, 29.059986913307316},
{456, 29.44698201092504},
{457, 29.444893222605785},
{458, 30.2987283695555},
{459, 30.490753195685443},
{460, 30.3712818585665},
{461, 31.03092246398632},
{462, 31.1306416649066},
{463, 31.204020755490298},
{464, 31.302761670091414},
{465, 31.287884426895985},
{466, 30.49414678626753},
{467, 30.9842218570926},
{468, 30.88247586217915},
{469, 31.65627890749247},
{470, 31.300373482363312},
{471, 31.009412915052188},
{472, 31.135319385771062},
{473, 31.142947189076295},
{474, 31.095825728106682},
{475, 31.402733294236572},
{476, 31.348161546083656},
{477, 31.600766880442425},
{478, 31.51732023898528},
{479, 31.218212979786024},
{480, 31.431310499487758},
{481, 31.536799819023997},
{482, 31.590576295671646},
{483, 31.49429616322668},
{484, 32.1816892011556},
{485, 32.61239327037908},
{486, 32.69902576477615},
{487, 32.89146487332367},
{488, 33.87564290993047},
{489, 33.89045560640269},
{490, 33.8295470579556},
{491, 33.83070162926511},
{492, 33.57726370546161},
{493, 33.80516778960143},
{494, 34.26212065396207},
{495, 34.4134739195819},
{496, 34.72554255082181},
{497, 34.7082104992009},
{498, 34.60279005425693},
{499, 34.231204914439},
{500, 34.099389551023904},
{501, 34.22803109888905},
{502, 34.610384365507166},
{503, 34.902573683307146},
{504, 35.0720672663373},
{505, 35.02095234082352},
{506, 35.21029830385259},
{507, 36.39939489896903},
{508, 36.49626843650683},
{509, 36.91759867136517},
{510, 36.62252163480901},
{511, 36.637052547917165},
{512, 37.07289132044249},
{513, 37.943749079996564},
{514, 37.31678988480502},
{515, 37.10207023840552},
{516, 37.50103188910752},
{517, 36.711556296699854},
{518, 36.69985201593515},
{519, 35.88305508619134},
{520, 35.819177348929244},
{521, 35.238662967688704},
{522, 35.204473848884604},
{523, 35.73171925218891},
{524, 37.10654099191614},
{525, 36.97832117698606},
{526, 37.88944422270623},
{527, 37.566765945668905},
{528, 36.952108419930035},
{529, 37.16172877345256},
{530, 36.79432449958657},
{531, 36.83537718548058},
{532, 37.241795032269486},
{533, 36.98172720447497},
{534, 37.12848852369968},
{535, 37.144201171899056},
{536, 37.291084012425635},
{537, 37.20361280452033},
{538, 37.14673041600966},
{539, 36.56539045560211},
{540, 36.46052858989325},
{541, 36.577035838031236},
{542, 36.413434939980604},
{543, 36.830541819855966},
{544, 36.708601164617306},
{545, 36.754028252130254},
{546, 36.86880281045058},
{547, 36.20522653559122},
{548, 36.19016853939169},
{549, 35.60660078018533},
{550, 35.58380480655832},
{551, 36.15574897354542},
{552, 35.96603587272573},
{553, 36.01846266557714},
{554, 36.1246059100681},
{555, 35.776226062342324},
{556, 35.53756438274776},
{557, 36.09285691162272},
{558, 36.024821241563764},
{559, 36.07242575850486},
{560, 36.902047436744915},
{561, 36.783371618493575},
{562, 36.97312505169229},
{563, 38.33987731208528},
{564, 38.12211125979531},
{565, 37.867344065570286},
{566, 37.91294220403035},
{567, 37.90421561454514},
{568, 37.52932323225818},
{569, 38.09065950561944},
{570, 38.3481668904528},
{571, 38.38853417076884},
{572, 38.82875488776441},
{573, 39.30100206552399},
{574, 39.22872668781665},
{575, 39.26377992972712},
{576, 39.11286987148877},
{577, 38.55996270262097},
{578, 39.53759984840164},
{579, 39.434972831723044},
{580, 39.58456959406775},
{581, 40.02469156333131},
{582, 39.83799518416734},
{583, 41.066310308184526},
{584, 41.249674158569356},
{585, 41.84405324758257},
{586, 42.44197850928179},
{587, 42.44435586698777},
{588, 42.51450858652831},
{589, 42.497079884180856},
{590, 41.86201338457368},
{591, 41.97430910917171},
{592, 42.38604395890263},
{593, 42.77701512105045},
{594, 42.917712727399795},
{595, 41.92733574585986},
{596, 41.98001270512809},
{597, 42.21709441156598},
{598, 41.73101118068132},
{599, 41.511566903501254},
{600, 41.151843749654674},
{601, 40.936735208747976},
{602, 41.07670022261416},
{603, 41.00310883262705},
{604, 41.175657930592564},
{605, 39.65057441921113},
{606, 40.59197307871199},
{607, 40.19328008308628},
{608, 39.96642122821799},
{609, 39.980554983282815},
{610, 39.198802516126015},
{611, 40.28881956306739},
{612, 40.835001630650424},
{613, 40.96840019049191},
{614, 41.47803284199829},
{615, 42.31117692587696},
{616, 42.56501744522036},
{617, 40.75017426985965},
{618, 40.54474737773533},
{619, 40.32299207284596},
{620, 40.59233541049153},
{621, 39.799369403233875},
{622, 39.90740256156506},
{623, 39.84656589770128},
{624, 40.51863738080248},
{625, 40.36927897222197},
{626, 39.577466020927666},
{627, 40.24059624379228},
{628, 39.888654784809695},
{629, 39.06459566315487},
{630, 39.21824625899819},
{631, 39.707949621228416},
{632, 39.6862655989382},
{633, 39.232670266668386},
{634, 38.72226788949448},
{635, 38.90143970392931},
{636, 38.58114412526509},
{637, 37.47764070721714},
{638, 38.14287868141234},
{639, 37.95489640397961},
{640, 37.978479310316544},
{641, 38.08870157682401},
{642, 37.86650279398312},
{643, 37.83158400391423},
{644, 37.661339861002425},
{645, 37.77028468310174},
{646, 38.19873503550377},
{647, 37.84181306063926},
{648, 36.15953352691078},
{649, 36.15954745676219},
{650, 36.70039654101666},
{651, 36.747404928048674},
{652, 36.45440618086992},
{653, 36.50375344553345},
{654, 36.12499971175383},
{655, 36.25540639161984},
{656, 36.14128341022631},
{657, 36.13550126300937},
{658, 36.47636290489859},
{659, 36.52934581511136},
{660, 36.180684191314654},
{661, 36.1345774586021},
{662, 35.88734753480093},
{663, 35.910114094303935},
{664, 37.494624894014834},
{665, 37.388322481490356},
{666, 37.51949607161456},
{667, 37.42672416426224},
{668, 37.607431895150505},
{669, 38.1169043451165},
{670, 38.198701944392226},
{671, 37.672228180889036},
{672, 37.573597996143555},
{673, 37.61475549631909},
{674, 37.704351969731974},
{675, 36.526503465131775},
{676, 37.14283711760895},
{677, 37.042216595367044},
{678, 37.19943347657908},
{679, 37.030753837557455},
{680, 37.29832109446188},
{681, 37.34909191845969},
{682, 37.396667764315495},
{683, 37.71513970238973},
{684, 37.035963822400284},
{685, 37.09132966834279},
{686, 36.671363246669195},
{687, 36.725271547538746},
{688, 36.732765304609515},
{689, 37.049756926857675},
{690, 37.100425916489115},
{691, 37.80849557841213},
{692, 38.00370168917147},
{693, 38.65064301128746},
{694, 38.16137008804746},
{695, 38.16628690738749},
{696, 38.509868983636345},
{697, 38.51329094834429},
{698, 38.63409846835321},
{699, 38.33551139578646},
{700, 37.86178126970245},
{701, 37.20625176875522},
{702, 37.13864013016608},
{703, 36.598956314019325},
{704, 36.73225170870823},
{705, 36.645909925459954},
{706, 36.931378033672665},
{707, 37.15159698187774},
{708, 37.20499450218726},
{709, 37.312534314076736},
{710, 38.234122213566266},
{711, 38.23482019038691},
{712, 39.50821685803287},
{713, 39.3687472311753},
{714, 40.43514365409954},
{715, 40.44682389295688},
{716, 39.81067959969004},
{717, 39.4239320658717},
{718, 39.54620136509768},
{719, 39.70807547297041},
{720, 39.66237013592385},
{721, 39.72023820319751},
{722, 40.02829005394066},
{723, 39.9923106822397},
{724, 39.88229973919716},
{725, 39.577778632304906},
{726, 39.42713453659046},
{727, 39.43664948339558},
{728, 39.90314312163109},
{729, 40.00741574107013},
{730, 40.7177664595581},
{731, 40.72045099279074},
{732, 38.3054003174184},
{733, 37.63394119070095},
{734, 37.190128597342806},
{735, 37.63802392396217},
{736, 37.09659077679385},
{737, 37.08430082665866},
{738, 37.031926029911546},
{739, 36.92762266554352},
{740, 36.768283960686276},
{741, 36.80986118421022},
{742, 37.00023584840072},
{743, 37.37757227329164},
{744, 37.65237108287256},
{745, 37.65431119266612},
{746, 37.78202911987313},
{747, 37.26755576516154},
{748, 37.253878362362926},
{749, 37.25181149353707},
{750, 38.046853987139436},
{751, 38.52088550994239},
{752, 38.50060736503108},
{753, 39.721168829902766},
{754, 39.714977806533284},
{755, 41.2027128451164},
{756, 41.271353835056736},
{757, 41.42756394972893},
{758, 41.20791889996719},
{759, 41.51542890247571},
{760, 41.533271125006856},
{761, 41.57431095381387},
{762, 42.43297879649776},
{763, 42.697486616671064},
{764, 42.943817792210965},
{765, 43.00326844639773},
{766, 43.04122373477804},
{767, 42.72385932125831},
{768, 42.91563938083615},
{769, 43.09789890970447},
{770, 43.21939147903337},
{771, 42.593500985806116},
{772, 42.552074422506266},
{773, 41.96625047640962},
{774, 42.43760021996765},
{775, 42.15036608724706},
{776, 42.01801170238841},
{777, 42.108251206065354},
{778, 42.13720797898185},
{779, 41.734510693754125},
{780, 41.98861197273539},
{781, 41.855890804590906},
{782, 41.83653956517087},
{783, 41.84729249879647},
{784, 41.34451591903922},
{785, 41.198937855641844},
{786, 40.9934126996872},
{787, 40.33193886547389},
{788, 40.42177093298795},
{789, 40.56423832118387},
{790, 40.56930078212437},
{791, 40.44001792153413},
{792, 39.36239492015202},
{793, 39.2299512438198},
{794, 39.98930528782557},
{795, 39.78753261124546},
{796, 39.7771218730521},
{797, 40.08078557739916},
{798, 39.9026881045777},
{799, 39.846598822934695},
{800, 39.819165988548605},
{801, 39.779536346223246},
{802, 39.417184051357914},
{803, 39.04248228046798},
{804, 39.04467471746209},
{805, 39.19976668706493},
{806, 38.62608414650186},
{807, 38.56950606795273},
{808, 38.11427821927686},
{809, 38.003564811967195},
{810, 38.9186775339263},
{811, 39.32280711240079},
{812, 39.37066040646152},
{813, 39.35771550162152},
{814, 39.82593927687213},
{815, 39.41511912781052},
{816, 39.632089125382805},
{817, 39.7013716770543},
{818, 39.71871324205181},
{819, 39.3691037472924},
{820, 39.4630528375144},
{821, 39.463557590573124},
{822, 39.84149848240294},
{823, 39.731112944493475},
{824, 39.46553273230596},
{825, 38.983001317774516},
{826, 39.111806633972954},
{827, 39.11241986582127},
{828, 39.060815486719235},
{829, 38.62106612409818},
{830, 38.45726527644363},
{831, 38.46243114268653},
{832, 37.81089603641852},
{833, 37.06489698994548},
{834, 35.65801215219413},
{835, 35.598044467413054},
{836, 35.51269552874315},
{837, 35.26479970268285},
{838, 36.55531335895278},
{839, 36.707855369730034},
{840, 36.5489032234404},
{841, 36.3485272462068},
{842, 36.365162732119266},
{843, 36.175883792606705},
{844, 36.16643352096541},
{845, 36.721302046157795},
{846, 36.738751647960406},
{847, 36.499637035175965},
{848, 36.461882187600125},
{849, 36.474313661197286},
{850, 36.138203284641186},
{851, 36.47458889999187},
{852, 36.9122685068765},
{853, 36.84193719836966},
{854, 36.016869149964144},
{855, 35.908626743361204},
{856, 35.853057192524155},
{857, 35.49400703601318},
{858, 35.23328368215224},
{859, 35.43381027728788},
{860, 35.441912396566295},
{861, 36.11980562386126},
{862, 35.987081872648865},
{863, 35.354742046128464},
{864, 35.11437170180045},
{865, 35.10075744627144},
{866, 35.33061979174065},
{867, 35.26826901332004},
{868, 34.76420766370852},
{869, 34.36825504722903},
{870, 33.95310090031108},
{871, 34.76854906618263},
{872, 34.96941220214851},
{873, 34.89082477498721},
{874, 34.930226673548425},
{875, 34.818344629526955},
{876, 33.46752591731415},
{877, 32.4461932776035},
{878, 32.63093881621515},
{879, 32.571549274273316},
{880, 31.70273743751098},
{881, 31.560570134656707},
{882, 30.11896242237412},
{883, 29.976753491813355},
{884, 30.151470049420453},
{885, 29.968667652811302},
{886, 29.914108861900175},
{887, 29.315297425726456},
{888, 29.465096537128055},
{889, 30.38412313034993},
{890, 30.529005936656326},
{891, 30.377111661676715},
{892, 30.690112657456975},
{893, 31.19665096563887},
{894, 30.23180166165202},
{895, 30.214575260830543},
{896, 30.61887791868095},
{897, 31.69516453152183},
{898, 31.724657127738052},
{899, 31.3260343161325},
{900, 31.448576476567858},
{901, 31.384197051367188},
{902, 31.42382604514501},
{903, 31.77933470325809},
{904, 31.54527210384609},
{905, 31.662859065404803},
{906, 31.952233276070345},
{907, 31.710429406503827},
{908, 31.69910928793987},
{909, 31.26054806750292},
{910, 31.252913740901814},
{911, 31.069356216279907},
{912, 31.450376052945543},
{913, 31.446602308578885},
{914, 32.88848724097737},
{915, 32.97405474992589},
{916, 32.95989543002121},
{917, 33.50228160406862},
{918, 33.57376811625785},
{919, 33.49455873288756},
{920, 32.739675795181945},
{921, 32.69683391724602},
{922, 33.81401934792278},
{923, 33.68504119872676},
{924, 33.08473835369315},
{925, 32.71783936639953},
{926, 33.576629314879405},
{927, 33.496669377208725},
{928, 32.32323980202774},
{929, 32.19540083945545},
{930, 31.681562294745188},
{931, 31.21936553988134},
{932, 32.02350301138198},
{933, 31.99391696064781},
{934, 31.222294641434715},
{935, 31.49393327555392},
{936, 31.60562877897029},
{937, 31.719747501964054},
{938, 31.631337228109494},
{939, 31.876599195053196},
{940, 31.839975677295737},
{941, 31.57998061624259},
{942, 32.247156373569744},
{943, 33.13453225404717},
{944, 33.57148226581986},
{945, 33.67881606804257},
{946, 34.27627744671606},
{947, 34.2667236377486},
{948, 35.0394972266412},
{949, 35.06221556029825},
{950, 35.317831610972426},
{951, 35.47650956265658},
{952, 35.92884652798027},
{953, 36.40171114341679},
{954, 35.651733034914436},
{955, 36.021038886103796},
{956, 36.194620959224366},
{957, 36.14405991221338},
{958, 36.02271118328351},
{959, 36.01617792703485},
{960, 36.718644368420634},
{961, 36.658954226071046},
{962, 36.58675202460727},
{963, 35.89078679017791},
{964, 35.94749384377098},
{965, 35.92838317262612},
{966, 34.32490064912566},
{967, 33.68329350874763},
{968, 33.72780060374807},
{969, 33.521931400031036},
{970, 33.48546378484414},
{971, 33.729671379093105},
{972, 32.483138764110244},
{973, 32.70061143845538},
{974, 31.882058598338592},
{975, 31.85978103716163},
{976, 31.360355110091252},
{977, 31.338506662447838},
{978, 31.4975196000238},
{979, 31.636516566035457},
{980, 31.76928554942759},
{981, 31.596565826225252},
{982, 31.24504603522091},
{983, 30.97685607999551},
{984, 30.776149614114455},
{985, 30.369022511891757},
{986, 30.65823464304316},
{987, 30.536961423755283},
{988, 30.543171096017304},
{989, 29.709033348016842},
{990, 29.787775153315135},
{991, 30.020098011459627},
{992, 30.161570003700696},
{993, 30.110594653721783},
{994, 30.269521765071406},
{995, 30.887702552351005},
{996, 31.092501292990715},
{997, 30.96235060201595},
{998, 31.371052725132344},
{999, 31.652461552974707},
{1000, 31.925382030352395},
{1001, 32.09718279718192},
{1002, 32.404705995647134},
{1003, 32.40399854679425},
{1004, 32.105619052543034},
{1005, 32.123902211392696},
{1006, 32.26894930916644},
{1007, 32.06571185804976},
{1008, 32.27138533885846},
{1009, 32.459468377391936},
{1010, 32.45637104295923},
{1011, 33.97355416481095},
{1012, 34.073372443262755},
{1013, 34.555509854599876},
{1014, 34.001992433795586},
{1015, 34.028775375039515},
{1016, 33.15444517016032},
{1017, 33.25999551152861},
{1018, 33.56889630842039},
{1019, 33.836008073592865},
{1020, 34.6557254577998},
{1021, 34.16915786529838},
{1022, 33.91355886515464},
{1023, 33.53814944935212},
{1024, 33.73093246034526},
{1025, 33.779257221704746},
{1026, 33.55901656076105},
{1027, 33.522411543634334},
{1028, 33.308377022855574},
{1029, 33.46872487000558},
{1030, 33.398428332372106},
{1031, 32.78130678002823},
{1032, 32.569113758452666},
{1033, 31.58571790720343},
{1034, 32.16172427938117},
{1035, 31.661979621179476},
{1036, 31.632175065844496},
{1037, 31.789546741031184},
{1038, 32.26513372369237},
{1039, 32.18304328830448},
{1040, 32.71362639662142},
{1041, 31.299268596529224},
{1042, 27.792537058477052},
{1043, 31.385259077931753},
{1044, 30.591065858607116},
{1045, 31.272338876555935},
{1046, 31.673417165689862},
{1047, 31.185485416617453},
{1048, 31.29116101415942},
{1049, 31.420045699024275},
{1050, 32.72027856671056},
{1051, 32.70728464282712},
{1052, 32.84488754596194},
{1053, 33.19971370723099},
{1054, 33.00087402494973},
{1055, 32.99077340899255},
{1056, 32.83232129107784},
{1057, 32.30498262311541},
{1058, 32.30946094376116},
{1059, 32.940891339151776},
{1060, 32.68854192681414},
{1061, 32.73363409330329},
{1062, 32.79589758581314},
{1063, 32.289791532294295},
{1064, 32.68604720099993},
{1065, 33.33078243465505},
{1066, 33.54826648297002},
{1067, 33.40865566016796},
{1068, 33.41296471315078},
{1069, 32.77182145012062},
{1070, 32.33224727266914},
{1071, 32.27203157051601},
{1072, 32.26109905266389},
{1073, 32.921499096795074},
{1074, 33.38395175893338},
{1075, 33.78768855190261},
{1076, 33.442367223026764},
{1077, 33.237913854994794},
{1078, 31.147232293821737},
{1079, 31.146482126046756},
{1080, 31.71150780117646},
{1081, 31.814296312484636},
{1082, 30.955859429080363},
{1083, 30.987259649137073},
{1084, 30.89065910781576},
{1085, 31.16579586461409},
{1086, 31.118733218136573},
{1087, 30.533273297600925},
{1088, 29.252834071159857},
{1089, 29.685743434805644},
{1090, 29.705920523322913},
{1091, 29.85069842502032},
{1092, 30.388305038615574},
{1093, 30.161758590806624},
{1094, 30.779652282941164},
{1095, 30.91190072880913},
{1096, 31.56528710281942},
{1097, 31.768065818580393},
{1098, 31.8230334699979},
{1099, 32.1658225590106},
{1100, 32.18402247418236},
{1101, 33.487267406897004},
{1102, 33.51902089155666},
{1103, 33.39833431034105},
{1104, 32.50713483623776},
{1105, 32.66530621931014},
{1106, 32.78927126650209},
{1107, 32.650964726546256},
{1108, 32.70706148431291},
{1109, 32.916943844199004},
{1110, 32.49004088753038},
{1111, 32.945619481604744},
{1112, 33.03101393088018},
{1113, 33.01159487630862},
{1114, 33.17414749212253},
{1115, 34.21219988578758},
{1116, 34.00043238845383},
{1117, 33.61923485510282},
{1118, 34.275948039809364},
{1119, 33.87944072377637},
{1120, 33.57361872056542},
{1121, 33.60777850243776},
{1122, 33.95519829789812},
{1123, 34.101866130091636},
{1124, 35.08344176854378},
{1125, 35.290045657998256},
{1126, 34.99789523160777},
{1127, 35.11162512342385},
{1128, 35.150203027075506},
{1129, 34.9547553234651},
{1130, 35.68267560816188},
{1131, 35.94530333733083},
{1132, 36.40684420002165},
{1133, 36.90897436886569},
{1134, 36.68925056413046},
{1135, 36.77241779692453},
{1136, 36.7768373962142},
{1137, 36.615559524302526},
{1138, 36.15108571210806},
{1139, 36.31887166999472},
{1140, 36.25359696272827},
{1141, 36.04048635034041},
{1142, 36.00631287066912},
{1143, 36.459483786091326},
{1144, 36.523711914237204},
{1145, 35.76502964124057},
{1146, 36.18603634547918},
{1147, 36.143227345894964},
{1148, 35.42846972819206},
{1149, 34.22551552384586},
{1150, 35.085356710729094},
{1151, 34.978408345953554},
{1152, 35.02162912627897},
{1153, 34.6749544956021},
{1154, 34.5294083257531},
{1155, 33.989361791250865},
{1156, 33.90560409554212},
{1157, 34.22819180059112},
{1158, 35.09979866464593},
{1159, 35.459597394271825},
{1160, 35.028413870661275},
{1161, 35.07365163864229},
{1162, 35.008455674695846},
{1163, 34.87027987153975},
{1164, 32.73740982908738},
{1165, 35.86288920810587},
{1166, 36.38448730256452},
{1167, 35.69198792205293},
{1168, 35.7287802068078},
{1169, 35.67047911791235},
{1170, 35.65931562676154},
{1171, 35.63860255804954},
{1172, 36.28377914027185},
{1173, 36.31724433582806},
{1174, 36.42873315721097},
{1175, 36.55293797881833},
{1176, 36.68858364718202},
{1177, 36.68542383419321},
{1178, 36.59630640724288},
{1179, 36.46919978323624},
{1180, 35.97612499492244},
{1181, 35.97470028204401},
{1182, 36.88186754506275},
{1183, 36.89180762556425},
{1184, 37.398624973193115},
{1185, 37.6485574658129},
{1186, 37.59944793075436},
{1187, 37.52123044476694},
{1188, 37.545545238118},
{1189, 37.63568398432097},
{1190, 37.60117292673316},
{1191, 37.76737303107856},
{1192, 37.796708933792935},
{1193, 38.44788884052119},
{1194, 38.433526334130406},
{1195, 38.53261361058563},
{1196, 38.20625193191746},
{1197, 38.216892858955696},
{1198, 38.10624817074387},
{1199, 38.31498368822061},
{1200, 38.28171670233931},
{1201, 38.03504598890333},
{1202, 37.469381149070585},
{1203, 37.645843865994834},
{1204, 37.996863546861626},
{1205, 37.39010863231819},
{1206, 37.916484620097826},
{1207, 37.9792008265341},
{1208, 37.56402382212345},
{1209, 37.64236828858301},
{1210, 38.20048604682538},
{1211, 38.1135776386156},
{1212, 38.06926736333271},
{1213, 37.85109980183144},
{1214, 37.8667193253515},
{1215, 37.79731989239691},
{1216, 37.79569290509785},
{1217, 37.507680665850124},
{1218, 37.86538998455543},
{1219, 37.97413406343451},
{1220, 38.153189871670634},
{1221, 37.705945919911386},
{1222, 38.003018638410715},
{1223, 37.97804975659373},
{1224, 37.86005819640923},
{1225, 37.74622347438676},
{1226, 37.24273013769576},
{1227, 37.471847978186396},
{1228, 36.574577925568015},
{1229, 36.898112403986175},
{1230, 37.93262825928585},
{1231, 37.98350758652906},
{1232, 37.3327630080204},
{1233, 37.87253628352105},
{1234, 37.03465628886385},
{1235, 37.3628779234913},
{1236, 37.257920892232235},
{1237, 37.355020597223806},
{1238, 36.42612381389829},
{1239, 35.71849960760021},
{1240, 35.569737001180215},
{1241, 35.639328778372764},
{1242, 36.67505414760637},
{1243, 36.529877025906195},
{1244, 36.17805747298349},
{1245, 36.20730061152778},
{1246, 36.7654190000416},
{1247, 36.066963570873966},
{1248, 34.80315462413555},
{1249, 34.79204497446974},
{1250, 34.96282416930701},
{1251, 35.29525328571966},
{1252, 35.39276674967225},
{1253, 35.17789113339765},
{1254, 35.1734239698592},
{1255, 36.27186268761327},
{1256, 36.12969032844646},
{1257, 35.56797468172719},
{1258, 35.413952983479305},
{1259, 35.473554398381665},
{1260, 34.959780400150386},
{1261, 34.705612847987084},
{1262, 35.122081505219015},
{1263, 35.54647234411569},
{1264, 35.56585165590874},
{1265, 34.07163330273662},
{1266, 34.061764136955},
{1267, 34.98718482740182},
{1268, 35.194478377248124},
{1269, 34.814779163097306},
{1270, 35.63339066284622},
{1271, 36.248018614293784},
{1272, 35.89931722172987},
{1273, 36.02707673294714},
{1274, 35.47928825105021},
{1275, 35.339226659095814},
{1276, 36.04365822060287},
{1277, 36.12975065194946},
{1278, 35.657328019340454},
{1279, 35.92456974548614},
{1280, 36.330656914157935},
{1281, 36.192088120039486},
{1282, 35.48734879533039},
{1283, 35.20967589696835},
{1284, 35.16528283177774},
{1285, 35.05450151673027},
{1286, 34.083660774124404},
{1287, 33.772468853879964},
{1288, 33.29191351706884},
{1289, 33.40417208662001},
{1290, 32.607679572698984},
{1291, 32.60800666862871},
{1292, 32.6060770265256},
{1293, 32.50260313437235},
{1294, 32.66708354095242},
{1295, 32.66682027747426},
{1296, 33.0078376785496},
{1297, 32.79448909601},
{1298, 33.33055451566498},
{1299, 33.80720872380922},
{1300, 34.03662018610426},
{1301, 33.471561770605334},
{1302, 33.464542278200675},
{1303, 33.5135192502925},
{1304, 33.522105679079196},
{1305, 33.486732091763336},
{1306, 34.102696294452976},
{1307, 34.28410402104561},
{1308, 34.50821094970082},
{1309, 34.2800121277366},
{1310, 34.07699583444763},
{1311, 33.29032898991077},
{1312, 33.447416548419994},
{1313, 33.07070289905697},
{1314, 32.76441433179846},
{1315, 32.35364095869243},
{1316, 32.19453136744108},
{1317, 32.001227569765504},
{1318, 32.1893553685659},
{1319, 32.17192961357054},
{1320, 32.05156175088731},
{1321, 32.01319521995405},
{1322, 31.361540797597904},
{1323, 31.358737048716332},
{1324, 31.4094163556713},
{1325, 31.385674150907523},
{1326, 30.397821572277874},
{1327, 30.430993318308072},
{1328, 30.559151734617863},
{1329, 30.709052945784077},
{1330, 30.762480629193092},
{1331, 30.428823245671445},
{1332, 30.549369360601233},
{1333, 30.401524153538364},
{1334, 30.862514784963658},
{1335, 30.543179751483258},
{1336, 30.546876105280212},
{1337, 30.8118556608746},
{1338, 30.73308277586123},
{1339, 31.09586849152352},
{1340, 30.443543543528172},
{1341, 30.574438626388865},
{1342, 30.433112514559557},
{1343, 29.60468065056554},
{1344, 29.518027759834045},
{1345, 29.530716732475156},
{1346, 29.258574444161773},
{1347, 28.96213974116116},
{1348, 30.094589411608464},
{1349, 30.027880530088794},
{1350, 30.00173657659883},
{1351, 30.7570211333231},
{1352, 30.905455874138642},
{1353, 30.95776014140416},
{1354, 31.25924961163502},
{1355, 31.28781614068638},
{1356, 31.259224092967646},
{1357, 31.471490532081248},
{1358, 30.66993696457886},
{1359, 29.51601931381402},
{1360, 29.658359527955362},
{1361, 29.498776144228817},
{1362, 29.0952884352307},
{1363, 27.583844233928247},
{1364, 27.104268459905846},
{1365, 25.95144210780829},
{1366, 25.823464503416666},
{1367, 25.72514162857761},
{1368, 25.654123815674225},
{1369, 26.051979147188657},
{1370, 25.841597348616787},
{1371, 25.03088000259754},
{1372, 24.40430526113091},
{1373, 24.39450861715055},
{1374, 24.868258110961357},
{1375, 24.87891945990089},
{1376, 24.826829203959203},
{1377, 24.723127466527284},
{1378, 25.189101108151846},
{1379, 24.999892026613335},
{1380, 25.403952465657824},
{1381, 25.395818912419763},
{1382, 25.361488701260853},
{1383, 25.168837708367573},
{1384, 25.822974064286573},
{1385, 25.184633998151693},
{1386, 25.673392593447062},
{1387, 25.493115319468433},
{1388, 25.616885562952366},
{1389, 26.26185502334556},
{1390, 26.63671721716633},
{1391, 27.41659614028529},
{1392, 27.180001027499763},
{1393, 27.31191586358311},
{1394, 27.41516507193562},
{1395, 27.621542573182392},
{1396, 28.333329103903207},
{1397, 28.490364050694232},
{1398, 28.282737229661876},
{1399, 28.487368411327616},
{1400, 27.619991904229085},
{1401, 27.775228746290942},
{1402, 27.75784788858771},
{1403, 26.388008503362745},
{1404, 26.390446759456594},
{1405, 26.804841863728445},
{1406, 26.645161583486924},
{1407, 25.978560150820044},
{1408, 25.928009125329705},
{1409, 25.928237301882948},
{1410, 25.750677096758384},
{1411, 25.3386270250864},
{1412, 25.140774560316675},
{1413, 24.34074260607449},
{1414, 24.754961425981048},
{1415, 24.730194413283783},
{1416, 24.920450795877183},
{1417, 25.024801997124456},
{1418, 24.6651943693905},
{1419, 25.194960339720506},
{1420, 25.34229435510204},
{1421, 25.305380825552025},
{1422, 25.412841036914795},
{1423, 25.505668659821385},
{1424, 25.930434241278466},
{1425, 25.958267633145237},
{1426, 26.031451433938873},
{1427, 26.586990196306772},
{1428, 27.292096678546},
{1429, 28.12071488383503},
{1430, 28.02832144500101},
{1431, 28.58386148422691},
{1432, 28.79404266374261},
{1433, 29.738295917981866},
{1434, 29.720007389392677},
{1435, 29.63633778940114},
{1436, 29.68841036823205},
{1437, 29.575033122960512},
{1438, 29.539149897548384},
{1439, 29.80686249903287},
{1440, 30.862182502538953},
{1441, 30.42493141449586},
{1442, 30.427703473898646},
{1443, 30.502760352966796},
{1444, 30.537509584235174},
{1445, 30.206448675310977},
{1446, 30.230172108384416},
{1447, 30.164364772985248},
{1448, 29.417259389108583},
{1449, 29.280495127154293},
{1450, 29.331776246449813},
{1451, 28.98662862990845},
{1452, 29.752510306571484},
{1453, 29.70066010243845},
{1454, 29.357272975382973},
{1455, 29.25016590767666},
{1456, 29.355245117563307},
{1457, 29.533981898101043},
{1458, 29.569017902727573},
{1459, 29.611976373457114},
{1460, 29.731805290138638},
{1461, 29.757994213197602},
{1462, 30.0901622202274},
{1463, 31.279393484956277},
{1464, 31.228915684424805},
{1465, 31.39987446802975},
{1466, 30.81523244839042},
{1467, 31.19319811379786},
{1468, 31.21869066135987},
{1469, 31.378064726660238},
{1470, 31.36503448737635},
{1471, 30.890723656435668},
{1472, 31.278271018448063},
{1473, 31.27351266944355},
{1474, 30.906433524045895},
{1475, 31.094199381637562},
{1476, 32.22446329349785},
{1477, 31.755676495678124},
{1478, 31.863329580086756},
{1479, 34.1769485077606},
{1480, 33.67109006952487},
{1481, 34.032560599893785},
{1482, 35.17463593455005},
{1483, 34.68680491085987},
{1484, 35.34323186904023},
{1485, 36.06846197316916},
{1486, 35.75474567916718},
{1487, 34.95879681397107},
{1488, 34.91513272089497},
{1489, 34.27429545432343},
{1490, 34.47677587538645},
{1491, 34.8837191993053},
{1492, 34.888682229883216},
{1493, 34.6615444728743},
{1494, 34.691697874937915},
{1495, 34.673137205747096},
{1496, 34.619588001105676},
{1497, 34.67924802646811},
{1498, 34.711467226688804},
{1499, 34.378496298766606},
{1500, 34.01906505065623},
{1501, 34.1921149855785},
{1502, 34.645939695035565},
{1503, 34.55325778595661},
{1504, 34.837013970080854},
{1505, 34.3201819146262},
{1506, 34.21433963893517},
{1507, 34.209807913410046},
{1508, 34.29503290261106},
{1509, 34.372533557964495},
{1510, 34.29249674028179},
{1511, 34.1793952178106},
{1512, 34.23027204162766},
{1513, 34.30024655177506},
{1514, 34.29076290991813},
{1515, 34.872527881393644},
{1516, 34.88183138613434},
{1517, 34.99793065640857},
{1518, 35.116718830483286},
{1519, 35.11046533465197},
{1520, 36.014438556584366},
{1521, 36.02360905142489},
{1522, 35.91482043384545},
{1523, 35.809274512011065},
{1524, 35.59619773000511},
{1525, 35.44019543122139},
{1526, 35.64144780604629},
{1527, 35.58080982359661},
{1528, 35.452624932745614},
{1529, 35.467238834711935},
{1530, 35.041073967062886},
{1531, 34.27022167037798},
{1532, 33.77867219668047},
{1533, 33.675446148880425},
{1534, 34.29649579312852},
{1535, 33.80063094230085},
{1536, 33.83706673096555},
{1537, 34.19813699318839},
{1538, 34.67898341154741},
{1539, 34.61565170828406},
{1540, 34.88435222539337},
{1541, 34.74803849982474},
{1542, 34.88836986733776},
{1543, 34.316664346866176},
{1544, 35.1429379016867},
{1545, 35.236518386423754},
{1546, 35.480022507695814},
{1547, 35.51129848892317},
{1548, 35.55074228557621},
{1549, 35.47965462275921},
{1550, 35.1614123321548},
{1551, 34.649935916461956},
{1552, 33.831408529297455},
{1553, 33.68659149830347},
{1554, 34.76348070802675},
{1555, 34.75436175642519},
{1556, 34.670519236256325},
{1557, 34.40743801250581},
{1558, 35.03821600965653},
{1559, 35.074059881857586},
{1560, 34.66313179952048},
{1561, 34.05122520128333},
{1562, 34.369261269504776},
{1563, 34.306862410511414},
{1564, 34.35513437341248},
{1565, 34.42016699549108},
{1566, 34.32411882750936},
{1567, 35.10444315366103},
{1568, 35.21667415434252},
{1569, 34.56235394065422},
{1570, 34.99569538327316},
{1571, 35.051462529787685},
{1572, 35.65901988130965},
{1573, 35.30919470315533},
{1574, 35.43427648307305},
{1575, 35.26105873637407},
{1576, 34.984210066219426},
{1577, 34.974915529232504},
{1578, 34.64250053081252},
{1579, 34.855051142104344},
{1580, 34.61935447447808},
{1581, 33.30095950674964},
{1582, 33.52159175029287},
{1583, 33.30245847520868},
{1584, 33.22017010546928},
{1585, 33.302509858225584},
{1586, 33.37904181575153},
{1587, 33.25298440074963},
{1588, 33.25372890621039},
{1589, 33.55633290212615},
{1590, 33.564240306946566},
{1591, 33.77554531353173},
{1592, 33.77747427631988},
{1593, 33.758375775192526},
{1594, 34.36417258641864},
{1595, 34.31644882079321},
{1596, 33.55182639869085},
{1597, 33.56219047110169},
{1598, 33.44110230199712},
{1599, 33.37853776463871},
{1600, 32.03520633056414},
{1601, 32.091659309762015},
{1602, 32.3728274207352},
{1603, 32.32296532348558},
{1604, 32.248876392188286},
{1605, 32.2486900311155},
{1606, 32.30827587355294},
{1607, 31.55600150552031},
{1608, 32.275031428073206},
{1609, 32.27879842764017},
{1610, 32.12685506952653},
{1611, 32.04232751622579},
{1612, 32.3717511617595},
{1613, 32.90499426727991},
{1614, 31.34160341506861},
{1615, 32.79935984939758},
{1616, 33.3133749284244},
{1617, 33.330066949919654},
{1618, 33.6696207424472},
{1619, 33.44727416894953},
{1620, 32.937035354917406},
{1621, 32.42098193976603},
{1622, 32.40919020921572},
{1623, 32.40926615430823},
{1624, 31.91939496950345},
{1625, 31.745665996112034},
{1626, 31.762245569926346},
{1627, 31.76510278674478},
{1628, 32.18895805255132},
{1629, 32.17884231058142},
{1630, 31.634810462741328},
{1631, 31.569967535436145},
{1632, 32.29578832893024},
{1633, 32.930732352651475},
{1634, 33.03934988366926},
{1635, 33.21635409643862},
{1636, 33.261326384884754},
{1637, 33.39655980314689},
{1638, 35.11945078307172},
{1639, 35.076572468626274},
{1640, 34.95856618844892},
{1641, 34.85019550097897},
{1642, 34.70787112679453},
{1643, 34.64396731467868},
{1644, 34.03523254807719},
{1645, 34.599545249438016},
{1646, 34.911136115744284},
{1647, 35.12923123083873},
{1648, 34.19771898956377},
{1649, 34.081163717338875},
{1650, 33.952021163589286},
{1651, 34.03909563960654},
{1652, 33.826004075423334},
{1653, 33.69501183562262},
{1654, 33.54101912632574},
{1655, 33.534398838396605},
{1656, 33.551479085175345},
{1657, 34.17014679283176},
{1658, 33.68298225679782},
{1659, 33.914956135823154},
{1660, 33.85350696847641},
{1661, 33.5930418617416},
{1662, 33.698585740003914},
{1663, 33.53164709646962},
{1664, 32.942749249597995},
{1665, 32.78927293361512},
{1666, 32.805622975726415},
{1667, 32.678834731539396},
{1668, 32.49005865138826},
{1669, 32.45598674269622},
{1670, 32.38305018611963},
{1671, 32.39849660167694},
{1672, 33.26082532734355},
{1673, 33.32014170105489},
{1674, 33.73004210039798},
{1675, 33.724298776862106},
{1676, 33.68423539254731},
{1677, 33.563159682253556},
{1678, 33.46610598060115},
{1679, 33.46576401188156},
{1680, 33.6082093792797},
{1681, 34.06829829976045},
{1682, 33.78773476628623},
{1683, 33.847057378024175},
{1684, 35.250271418221374},
{1685, 35.09767253763393},
{1686, 35.074861457363895},
{1687, 35.40140497372611},
{1688, 36.31807881704327},
{1689, 37.93153971576717},
{1690, 37.96917757654617},
{1691, 38.22198787034048},
{1692, 38.23678368537367},
{1693, 38.20599590804662},
{1694, 38.68369317469104},
{1695, 38.79215511609461},
{1696, 38.802109502845106},
{1697, 38.753495704229714},
{1698, 38.96421874631208},
{1699, 38.90821233250077},
{1700, 39.16182425119748},
{1701, 38.853831057337715},
{1702, 38.63611099293661},
{1703, 38.24474852988975},
{1704, 38.38469009174058},
{1705, 38.32872094403988},
{1706, 38.153737338484326},
{1707, 38.264923332806745},
{1708, 38.280563758044565},
{1709, 38.18750466726223},
{1710, 38.56983066481024},
{1711, 39.505085657555355},
{1712, 39.69872974322229},
{1713, 40.39204144577866},
{1714, 40.48553785053598},
{1715, 41.3269897150344},
{1716, 41.609581527655735},
{1717, 41.65892696057186},
{1718, 41.837313217148804},
{1719, 41.8289668854985},
{1720, 42.15881862704277},
{1721, 42.73710053588433},
{1722, 42.348456207492845},
{1723, 42.096157824123665},
{1724, 41.92253556951924},
{1725, 41.917137854036646},
{1726, 41.97608613143687},
{1727, 41.72574431293022},
{1728, 41.24861627279633},
{1729, 41.87472943187815},
{1730, 41.87568170648001},
{1731, 42.17909911414902},
{1732, 42.049177661936255},
{1733, 41.97474828244962},
{1734, 41.356541003497355},
{1735, 41.22195929777421},
{1736, 41.194030410051106},
{1737, 41.392479793475935},
{1738, 41.34843546848941},
{1739, 41.34402528705148},
{1740, 42.07661962525981},
{1741, 41.52647896910617},
{1742, 41.44561097861686},
{1743, 40.958470191837534},
{1744, 41.037829903821645},
{1745, 41.07205195864329},
{1746, 41.46906637533652},
{1747, 41.49343224957735},
{1748, 40.610182804195105},
{1749, 41.017801824810185},
{1750, 40.79059366319508},
{1751, 40.70441966049171},
{1752, 40.70503981535662},
{1753, 39.93683574905368},
{1754, 39.752239396736506},
{1755, 39.7645522699645},
{1756, 39.55588740392734},
{1757, 39.74727156272358},
{1758, 39.9737782466988},
{1759, 40.39693575020464},
{1760, 40.216674372755165},
{1761, 40.25136763580497},
{1762, 39.38556407545778},
{1763, 39.26119592497736},
{1764, 39.16195286452751},
{1765, 38.86948923485995},
{1766, 39.61600974311452},
{1767, 39.39522112770508},
{1768, 39.514089866690526},
{1769, 39.45799756858736},
{1770, 38.90570527579156},
{1771, 38.80478381534035},
{1772, 38.83322167157176},
{1773, 39.64297039815119},
{1774, 40.33223301684341},
{1775, 40.41694728495109},
{1776, 40.06020383799442},
{1777, 40.07361881200165},
{1778, 39.77916448556436},
{1779, 39.96893684870016},
{1780, 40.57894081959218},
{1781, 39.89797496618613},
{1782, 39.35261558123242},
{1783, 39.36008989987693},
{1784, 38.0512977550727},
{1785, 38.264000581925124},
{1786, 38.26169623365976},
{1787, 39.10252385765842},
{1788, 39.27088966509622},
{1789, 39.15216453676914},
{1790, 39.41049327702329},
{1791, 39.39907719439436},
{1792, 38.82328669157878},
{1793, 39.35804609496024},
{1794, 39.52870877903105},
{1795, 40.562208982859616},
{1796, 41.015089776340226},
{1797, 40.87083252184389},
{1798, 40.53994999420047},
{1799, 41.02575733506004},
{1800, 41.17619373256442},
{1801, 41.19685023564847},
{1802, 40.98189573065346},
{1803, 41.159763360041595},
{1804, 41.59421552557119},
{1805, 41.20806640088285},
{1806, 40.414699831836835},
{1807, 39.92415966374783},
{1808, 39.62606546104271},
{1809, 39.857180263975444},
{1810, 39.59080107893363},
{1811, 39.6974862276464},
{1812, 39.442229052918876},
{1813, 39.30199180817845},
{1814, 39.48025720867229},
{1815, 39.5287187420122},
{1816, 38.853563440015805},
{1817, 38.77749075357144},
{1818, 39.12960232658636},
{1819, 39.07838534119988},
{1820, 39.149292277699324},
{1821, 39.00774814154133},
{1822, 38.63589451271645},
{1823, 38.95501568270912},
{1824, 38.4982817921926},
{1825, 39.10088500152322},
{1826, 39.59621160868451},
{1827, 39.45833354062081},
{1828, 39.59936421761144},
{1829, 39.57053924615646},
{1830, 39.55660528183884},
{1831, 39.71569674116191},
{1832, 39.71328630121623},
{1833, 39.5500745849456},
{1834, 39.52081122302288},
{1835, 38.74032920127892},
{1836, 39.19864530117539},
{1837, 39.06947571924525},
{1838, 39.34422885689023},
{1839, 39.6353362225693},
{1840, 40.71459696346432},
{1841, 40.66496952468566},
{1842, 41.006778236777826},
{1843, 40.98904213311685},
{1844, 40.108733704463795},
{1845, 39.422159369592656},
{1846, 39.23842350070322},
{1847, 37.531170184849564},
{1848, 38.05353522108415},
{1849, 38.31111320664359},
{1850, 38.64900610422957},
{1851, 38.686484249347565},
{1852, 38.68434197825875},
{1853, 38.72332458907287},
{1854, 38.81119764084882},
{1855, 38.46493433585336},
{1856, 38.51556317974943},
{1857, 38.764010535750494},
{1858, 38.55294929767745},
{1859, 38.078366113942565},
{1860, 38.42988961361437},
{1861, 39.047014068329304},
{1862, 39.532635843081685},
{1863, 39.10571993423328},
{1864, 39.60131420080908},
{1865, 38.81680138357229},
{1866, 39.25082176080607},
{1867, 38.15677220409511},
{1868, 38.62555210254061},
{1869, 38.47132788365266},
{1870, 38.487714681626166},
{1871, 38.39888830728324},
{1872, 37.54975105530615},
{1873, 36.620496899833185},
{1874, 36.76592939311881},
{1875, 36.77417927197788},
{1876, 36.78457313190018},
{1877, 36.726556157401774},
{1878, 37.19484339680942},
{1879, 37.12075085414386},
{1880, 36.977258419880144},
{1881, 36.91078025788851},
{1882, 37.02130072401423},
{1883, 36.55535121811925},
{1884, 35.989467257305485},
{1885, 35.963032268137205},
{1886, 36.010752822606925},
{1887, 36.02787704722448},
{1888, 36.49326570398064},
{1889, 36.23934542841747},
{1890, 35.87776015328743},
{1891, 35.09797528518658},
{1892, 35.077065290325194},
{1893, 35.311414001953885},
{1894, 34.57305509209034},
{1895, 34.6842074805997},
{1896, 34.80183031401632},
{1897, 35.47101264307737},
{1898, 35.17048394585489},
{1899, 35.089175368832215},
{1900, 35.22509160122155},
{1901, 35.01261679236839},
{1902, 35.20621635881338},
{1903, 35.37294264170206},
{1904, 34.459027714667855},
{1905, 35.092154761591416},
{1906, 34.8517351148361},
{1907, 34.86550272062247},
{1908, 35.125600870401854},
{1909, 34.83446288221645},
{1910, 34.78242112077965},
{1911, 34.31203602602105},
{1912, 34.279540168694986},
{1913, 35.1096803630126},
{1914, 34.514233849300744},
{1915, 34.53539867779291},
{1916, 34.279414052434674},
{1917, 34.36455267465703},
{1918, 34.10174900420402},
{1919, 34.41175163800032},
{1920, 34.291999205288036},
{1921, 34.19594013042914},
{1922, 34.18838364564101},
{1923, 36.07392483120914},
{1924, 36.05098431555323},
{1925, 36.76095964181586},
{1926, 37.82863560027054},
{1927, 37.52980493319014},
{1928, 37.53527503569141},
{1929, 37.512668716830554},
{1930, 37.11106227609642},
{1931, 37.349978643969806},
{1932, 37.20979207398837},
{1933, 37.29059943130983},
{1934, 37.12675552575493},
{1935, 37.34021255585861},
{1936, 37.33863802694112},
{1937, 36.93793225844994},
{1938, 36.96147855185145},
{1939, 37.079544775050316},
{1940, 36.96800493367608},
{1941, 36.62130515314802},
{1942, 36.64654224799271},
{1943, 36.564393168165026},
{1944, 36.76798510268848},
{1945, 36.34484500467934},
{1946, 35.77622429269998},
{1947, 36.28861461359311},
{1948, 35.62308019530623},
{1949, 35.44131748549641},
{1950, 34.85392909768805},
{1951, 34.55133668610086},
{1952, 35.092025906373635},
{1953, 34.768122961192404},
{1954, 35.02372314142307},
{1955, 35.11384033534372},
{1956, 35.09859916781124},
{1957, 35.63073140705392},
{1958, 35.47556248383545},
{1959, 34.87826460124942},
{1960, 34.87959003890974},
{1961, 34.025365090862095},
{1962, 33.804380004654575},
{1963, 32.78020946544929},
{1964, 32.75918431846117},
{1965, 32.75851314921747},
{1966, 32.93198375479733},
{1967, 35.25828074460553},
{1968, 34.28240693566641},
{1969, 34.339864432373425},
{1970, 34.12397376413126},
{1971, 34.79011341612875},
{1972, 34.64686103755323},
{1973, 34.599313028973235},
{1974, 34.53242934134927},
{1975, 34.606548457455425},
{1976, 34.83272344102554},
{1977, 34.49913157098365},
{1978, 34.70437557256804},
{1979, 35.19565588926615},
{1980, 35.185492244262484},
{1981, 33.550787666460536},
{1982, 34.02484580154227},
{1983, 34.15044793215261},
{1984, 34.543313702949256},
{1985, 34.49383893491851},
{1986, 34.69468439935647},
{1987, 34.36140916945972},
{1988, 33.199535764838636},
{1989, 33.54089185371181},
{1990, 33.25543526295492},
{1991, 33.126250139798344},
{1992, 32.1006804695537},
{1993, 32.11793625750763},
{1994, 31.547141310894098},
{1995, 31.358848538734513},
{1996, 30.96324192071878},
{1997, 31.081433577858583},
{1998, 31.004611604459708},
{1999, 30.86284767543519},
{2000, 30.105433307161203},
{2001, 29.32024170286219},
{2002, 29.319658236582814},
{2003, 29.758958255855212},
{2004, 30.117663632341145},
{2005, 29.548890824528907},
{2006, 29.35044585015188},
{2007, 29.38762759040267},
{2008, 29.391950686507506},
{2009, 30.353380249710128},
{2010, 29.46899984490753},
{2011, 29.33586115695675},
{2012, 29.36114381531963},
{2013, 29.256022938524392},
{2014, 29.207874497314503},
{2015, 29.266115045795534},
{2016, 29.11037246533442},
{2017, 29.98833975350206},
{2018, 29.827356428113166},
{2019, 29.196369679790173},
{2020, 29.09133736287457},
{2021, 29.35398305475309},
{2022, 29.54604416792868},
{2023, 28.36216318117799},
{2024, 28.172545798424313},
{2025, 29.167896346989487},
{2026, 29.134747648857985},
{2027, 28.0133642954549},
{2028, 27.97694686027503},
{2029, 28.374194072568063},
{2030, 28.37095153088029},
{2031, 28.388216858848796},
{2032, 27.992329533869835},
{2033, 27.367870148995426},
{2034, 27.251995482961384},
{2035, 27.746729670664767},
{2036, 27.679330290354688},
{2037, 28.028729073068334},
{2038, 28.028705243794963},
{2039, 28.350786367009555},
{2040, 28.352026432670012},
{2041, 28.225867429505602},
{2042, 28.211139910084785},
{2043, 28.126532263542863},
{2044, 28.0111605756859},
{2045, 28.037986830307158},
{2046, 27.771048205304385},
{2047, 28.101282898505325},
{2048, 27.956754355558264},
{2049, 28.898451768426284},
{2050, 28.33803560429799},
{2051, 28.51219696371789},
{2052, 28.687032331939648},
{2053, 28.519256261795114},
{2054, 28.534411822889545},
{2055, 28.475878463300383},
{2056, 28.425823720753847},
{2057, 28.531458363352172},
{2058, 28.371986321151727},
{2059, 27.281010674258564},
{2060, 27.285423846392753},
{2061, 26.982970399775176},
{2062, 26.779917891194174},
{2063, 27.334018434853117},
{2064, 27.261844645356348},
{2065, 27.08643958372844},
{2066, 27.399456700893154},
{2067, 27.35064731231653},
{2068, 27.27964623953228},
{2069, 27.594268915970815},
{2070, 27.529530803142432},
{2071, 27.57032488737316},
{2072, 27.4481123592095},
{2073, 26.840190037017013},
{2074, 25.848284039508844},
{2075, 27.842568040851305},
{2076, 28.053732543316965},
{2077, 28.626766043264496},
{2078, 28.147220846160913},
{2079, 28.116929624170805},
{2080, 28.006707726319842},
{2081, 28.30104291864807},
{2082, 28.33894272086177},
{2083, 28.44881078446907},
{2084, 28.407665090419357},
{2085, 29.120266868128827},
{2086, 29.3317588176526},
{2087, 29.665040474972592},
{2088, 29.513552545038365},
{2089, 29.692597123031344},
{2090, 28.981378296214995},
{2091, 29.09551924330645},
{2092, 29.103036068928503},
{2093, 29.546315663547908},
{2094, 29.957633494290146},
{2095, 29.904489752089027},
{2096, 29.905243264397946},
{2097, 29.046115152161175},
{2098, 29.044952829872138},
{2099, 29.266240445467794},
{2100, 29.59245009354912},
{2101, 29.711595098413557},
{2102, 30.01909364831503},
{2103, 30.02844709237877},
{2104, 29.95602684268363},
{2105, 29.966649831798698},
{2106, 29.870132199150568},
{2107, 30.054469107153587},
{2108, 30.00047327238945},
{2109, 29.56478041606059},
{2110, 29.90146475387948},
{2111, 29.622907648647697},
{2112, 30.309630232664347},
{2113, 29.964474309001496},
{2114, 29.49100419801057},
{2115, 29.961421989896223},
{2116, 30.346134915473066},
{2117, 30.604426349400814},
{2118, 31.112159919665434},
{2119, 31.422724201504558},
{2120, 31.585365400843976},
{2121, 31.996984724582912},
{2122, 32.01819220164321},
{2123, 29.835521461379106},
{2124, 30.096046165850005},
{2125, 30.103782504040677},
{2126, 30.053631787942646},
{2127, 29.906750519100672},
{2128, 29.906138940448397},
{2129, 29.852649788445774},
{2130, 30.207615016068207},
{2131, 30.820165055355577},
{2132, 30.66806843164003},
{2133, 29.933937853455785},
{2134, 29.836930249713454},
{2135, 29.643987595361455},
{2136, 30.19074784312923},
{2137, 30.22542736675569},
{2138, 29.853735660522815},
{2139, 29.84557151500528},
{2140, 29.839193019325773},
{2141, 31.156302724258683
gitextract_wz3_b8r2/
├── .github/
│ ├── CODEOWNERS
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── CHANGELOG
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── attack.go
├── attack_nonwindows.go
├── attack_test.go
├── attack_windows.go
├── dump.go
├── encode.go
├── file.go
├── flags.go
├── go.mod
├── go.sum
├── internal/
│ ├── cmd/
│ │ ├── echosrv/
│ │ │ └── main.go
│ │ └── jsonschema/
│ │ └── main.go
│ └── resolver/
│ ├── resolver.go
│ └── resolver_test.go
├── lib/
│ ├── attack.go
│ ├── attack_fuzz.go
│ ├── attack_test.go
│ ├── histogram.go
│ ├── histogram_test.go
│ ├── lttb/
│ │ ├── lttb.go
│ │ └── lttb_test.go
│ ├── metrics.go
│ ├── metrics_test.go
│ ├── pacer.go
│ ├── pacer_test.go
│ ├── plot/
│ │ ├── assets/
│ │ │ ├── VERSIONS
│ │ │ ├── plot.html.tpl
│ │ │ └── uplot-plugins.js
│ │ ├── assets.go
│ │ ├── embed.go
│ │ ├── plot.go
│ │ ├── plot_test.go
│ │ ├── testdata/
│ │ │ └── TestPlot.golden.html
│ │ └── timeseries.go
│ ├── prom/
│ │ ├── grafana.json
│ │ ├── prom.go
│ │ └── prom_test.go
│ ├── reporters.go
│ ├── results.go
│ ├── results_easyjson.go
│ ├── results_fuzz.go
│ ├── results_test.go
│ ├── target.schema.json
│ ├── targets.go
│ ├── targets_easyjson.go
│ ├── targets_fuzz.go
│ ├── targets_test.go
│ └── util_fuzz.go
├── main.go
├── plot.go
├── report.go
├── report_nonwindows.go
├── report_windows.go
└── scripts/
└── load-ramping/
├── README.md
├── ramp-requests.plt
└── ramp-requests.py
SYMBOL INDEX (354 symbols across 45 files)
FILE: attack.go
function attackCmd (line 24) | func attackCmd() command {
type attackOpts (line 80) | type attackOpts struct
function attack (line 117) | func attack(opts *attackOpts) (err error) {
function processAttack (line 235) | func processAttack(
function tlsConfig (line 266) | func tlsConfig(insecure bool, certf, keyf string, rootCerts []string) (*...
FILE: attack_nonwindows.go
function systemSpecificFlags (line 8) | func systemSpecificFlags(fs *flag.FlagSet, opts *attackOpts) {
FILE: attack_test.go
function TestHeadersSet (line 18) | func TestHeadersSet(t *testing.T) {
function decodeMetrics (line 39) | func decodeMetrics(buf bytes.Buffer) (vegeta.Metrics, error) {
function TestAttackSignalOnce (line 58) | func TestAttackSignalOnce(t *testing.T) {
function TestAttackSignalTwice (line 116) | func TestAttackSignalTwice(t *testing.T) {
FILE: attack_windows.go
function systemSpecificFlags (line 5) | func systemSpecificFlags(fs *flag.FlagSet, opts *attackOpts) {}
FILE: dump.go
function dumpCmd (line 7) | func dumpCmd() command {
FILE: encode.go
constant encodingCSV (line 15) | encodingCSV = "csv"
constant encodingGob (line 16) | encodingGob = "gob"
constant encodingJSON (line 17) | encodingJSON = "json"
constant encodeUsage (line 20) | encodeUsage = `Usage: vegeta encode [options] [<file>...]
function encodeCmd (line 55) | func encodeCmd() command {
function encode (line 75) | func encode(files []string, to, output string) error {
FILE: file.go
function file (line 13) | func file(name string, create bool) (*os.File, error) {
function decoder (line 27) | func decoder(files []string) (vegeta.Decoder, io.Closer, error) {
type multiCloser (line 47) | type multiCloser
method Close (line 49) | func (mc multiCloser) Close() error {
FILE: flags.go
type headers (line 22) | type headers struct
method String (line 24) | func (h headers) String() string {
method Set (line 33) | func (h headers) Set(value string) error {
type localAddr (line 50) | type localAddr struct
method Set (line 52) | func (ip *localAddr) Set(value string) (err error) {
type csl (line 58) | type csl
method Set (line 60) | func (l *csl) Set(v string) error {
method String (line 65) | func (l csl) String() string { return strings.Join(l, ",") }
type rateFlag (line 67) | type rateFlag struct
method Set (line 69) | func (f *rateFlag) Set(v string) (err error) {
method String (line 100) | func (f *rateFlag) String() string {
type maxBodyFlag (line 107) | type maxBodyFlag struct
method Set (line 109) | func (f *maxBodyFlag) Set(v string) (err error) {
method String (line 128) | func (f *maxBodyFlag) String() string {
type dnsTTLFlag (line 137) | type dnsTTLFlag struct
method Set (line 139) | func (f *dnsTTLFlag) Set(v string) (err error) {
method String (line 149) | func (f *dnsTTLFlag) String() string {
constant connectToFormat (line 158) | connectToFormat = "src:port:dst:port"
type connectToFlag (line 160) | type connectToFlag struct
method String (line 164) | func (c *connectToFlag) String() string {
method Set (line 178) | func (c *connectToFlag) Set(s string) error {
FILE: internal/cmd/echosrv/main.go
function main (line 18) | func main() {
function hash (line 57) | func hash(n int) (string, error) {
FILE: internal/cmd/jsonschema/main.go
function main (line 15) | func main() {
function die (line 52) | func die(s string, args ...interface{}) {
function keys (line 57) | func keys(types map[string]interface{}) (ks []string) {
FILE: internal/resolver/resolver.go
type resolver (line 13) | type resolver struct
method dial (line 72) | func (r *resolver) dial(ctx context.Context, network, _ string) (net.C...
method address (line 76) | func (r *resolver) address() string {
function NewResolver (line 24) | func NewResolver(addrs []string) (*net.Resolver, error) {
function normalizeAddrs (line 38) | func normalizeAddrs(addrs []string) ([]string, error) {
FILE: internal/resolver/resolver_test.go
constant fakeDomain (line 20) | fakeDomain = "acme.notadomain"
function TestResolver (line 23) | func TestResolver(t *testing.T) {
function TestNormalizeAddrs (line 114) | func TestNormalizeAddrs(t *testing.T) {
FILE: lib/attack.go
type Attacker (line 22) | type Attacker struct
method Attack (line 446) | func (a *Attacker) Attack(tr Targeter, p Pacer, du time.Duration, name...
method Stop (line 519) | func (a *Attacker) Stop() bool {
method attack (line 529) | func (a *Attacker) attack(tr Targeter, atk *attack, workers *sync.Wait...
method hit (line 536) | func (a *Attacker) hit(tr Targeter, atk *attack) *Result {
constant DefaultRedirects (line 40) | DefaultRedirects = 10
constant DefaultTimeout (line 43) | DefaultTimeout = 30 * time.Second
constant DefaultConnections (line 46) | DefaultConnections = 10000
constant DefaultMaxConnections (line 49) | DefaultMaxConnections = 0
constant DefaultWorkers (line 51) | DefaultWorkers = 10
constant DefaultMaxWorkers (line 53) | DefaultMaxWorkers = math.MaxUint64
constant DefaultMaxBody (line 56) | DefaultMaxBody = int64(-1)
constant NoFollow (line 58) | NoFollow = -1
function NewAttacker (line 70) | func NewAttacker(opts ...func(*Attacker)) *Attacker {
function Workers (line 105) | func Workers(n uint64) func(*Attacker) {
function MaxWorkers (line 111) | func MaxWorkers(n uint64) func(*Attacker) {
function Connections (line 117) | func Connections(n int) func(*Attacker) {
function MaxConnections (line 126) | func MaxConnections(n int) func(*Attacker) {
function ChunkedBody (line 135) | func ChunkedBody(b bool) func(*Attacker) {
function Redirects (line 141) | func Redirects(n int) func(*Attacker) {
function Proxy (line 159) | func Proxy(proxy func(*http.Request) (*url.URL, error)) func(*Attacker) {
function Timeout (line 168) | func Timeout(d time.Duration) func(*Attacker) {
function LocalAddr (line 176) | func LocalAddr(addr net.IPAddr) func(*Attacker) {
function KeepAlive (line 186) | func KeepAlive(keepalive bool) func(*Attacker) {
function TLSConfig (line 199) | func TLSConfig(c *tls.Config) func(*Attacker) {
function HTTP2 (line 208) | func HTTP2(enabled bool) func(*Attacker) {
function H2C (line 221) | func H2C(enabled bool) func(*Attacker) {
function MaxBody (line 236) | func MaxBody(n int64) func(*Attacker) {
function UnixSocket (line 241) | func UnixSocket(socket string) func(*Attacker) {
function SessionTickets (line 253) | func SessionTickets(enabled bool) func(*Attacker) {
function Client (line 264) | func Client(c *http.Client) func(*Attacker) {
function ProxyHeader (line 270) | func ProxyHeader(h http.Header) func(*Attacker) {
function ConnectTo (line 281) | func ConnectTo(addrMap map[string][]string) func(*Attacker) {
function DNSCaching (line 323) | func DNSCaching(ttl time.Duration) func(*Attacker) {
function firstOfEachIPFamily (line 408) | func firstOfEachIPFamily(ips []string) []string {
type attack (line 434) | type attack struct
FILE: lib/attack_fuzz.go
function FuzzAttackerTCP (line 16) | func FuzzAttackerTCP(fuzz []byte) int {
function FuzzAttackerHTTP (line 72) | func FuzzAttackerHTTP(fuzz []byte) int {
function decodeFuzzResponse (line 131) | func decodeFuzzResponse(fuzz []byte) (
FILE: lib/attack_test.go
function TestAttackRate (line 25) | func TestAttackRate(t *testing.T) {
function TestAttackDuration (line 43) | func TestAttackDuration(t *testing.T) {
function TestTLSConfig (line 67) | func TestTLSConfig(t *testing.T) {
function TestRedirects (line 75) | func TestRedirects(t *testing.T) {
function TestNoFollow (line 93) | func TestNoFollow(t *testing.T) {
function TestTimeout (line 112) | func TestTimeout(t *testing.T) {
function TestLocalAddr (line 134) | func TestLocalAddr(t *testing.T) {
function TestKeepAlive (line 156) | func TestKeepAlive(t *testing.T) {
function TestSessionTickets (line 170) | func TestSessionTickets(t *testing.T) {
function TestConnections (line 182) | func TestConnections(t *testing.T) {
function TestStatusCodeErrors (line 191) | func TestStatusCodeErrors(t *testing.T) {
function TestBadTargeterError (line 208) | func TestBadTargeterError(t *testing.T) {
function TestResponseBodyCapture (line 218) | func TestResponseBodyCapture(t *testing.T) {
function TestProxyOption (line 237) | func TestProxyOption(t *testing.T) {
function TestMaxBody (line 268) | func TestMaxBody(t *testing.T) {
function TestUnixSocket (line 298) | func TestUnixSocket(t *testing.T) {
function TestClient (line 348) | func TestClient(t *testing.T) {
function TestVegetaHeaders (line 382) | func TestVegetaHeaders(t *testing.T) {
function TestDNSCaching_Issue649 (line 415) | func TestDNSCaching_Issue649(t *testing.T) {
function TestFirstOfEachIPFamily (line 427) | func TestFirstOfEachIPFamily(t *testing.T) {
function TestAttackConnectTo (line 503) | func TestAttackConnectTo(t *testing.T) {
FILE: lib/histogram.go
type Buckets (line 11) | type Buckets
method Nth (line 59) | func (bs Buckets) Nth(i int) (left, right string) {
method UnmarshalText (line 67) | func (bs *Buckets) UnmarshalText(value []byte) error {
type Histogram (line 14) | type Histogram struct
method Add (line 23) | func (h *Histogram) Add(r *Result) {
method MarshalJSON (line 40) | func (h *Histogram) MarshalJSON() ([]byte, error) {
FILE: lib/histogram_test.go
function TestHistogram_Add (line 9) | func TestHistogram_Add(t *testing.T) {
function TestBuckets_UnmarshalText (line 42) | func TestBuckets_UnmarshalText(t *testing.T) {
FILE: lib/lttb/lttb.go
type Point (line 6) | type Point struct
type Iter (line 10) | type Iter
function Downsample (line 20) | func Downsample(count, threshold int, it Iter) ([]Point, error) {
function sample (line 71) | func sample(a Point, current, next []Point) (b Point) {
FILE: lib/lttb/lttb_test.go
function TestDownsample (line 14) | func TestDownsample(t *testing.T) {
function BenchmarkLTTB (line 84) | func BenchmarkLTTB(b *testing.B) {
function newIterator (line 99) | func newIterator(data []Point) Iter {
FILE: lib/metrics.go
type Metrics (line 12) | type Metrics struct
method Add (line 50) | func (m *Metrics) Add(r *Result) {
method Close (line 90) | func (m *Metrics) Close() {
method init (line 117) | func (m *Metrics) init() {
type LatencyMetrics (line 132) | type LatencyMetrics struct
method Add (line 154) | func (l *LatencyMetrics) Add(latency time.Duration) {
method Quantile (line 166) | func (l LatencyMetrics) Quantile(nth float64) time.Duration {
method init (line 171) | func (l *LatencyMetrics) init() {
type ByteMetrics (line 180) | type ByteMetrics struct
type estimator (line 187) | type estimator interface
type tdigestEstimator (line 192) | type tdigestEstimator struct
method Add (line 198) | func (e *tdigestEstimator) Add(s float64) { e.TDigest.Add(s, 1) }
method Get (line 199) | func (e *tdigestEstimator) Get(q float64) float64 {
function newTdigestEstimator (line 194) | func newTdigestEstimator(compression float64) *tdigestEstimator {
FILE: lib/metrics_test.go
function TestMetrics_Add (line 17) | func TestMetrics_Add(t *testing.T) {
function equateApproxDuration (line 88) | func equateApproxDuration(margin time.Duration) cmp.Option {
function areNonZeroDurations (line 96) | func areNonZeroDurations(x, y time.Duration) bool {
type durationApproximator (line 100) | type durationApproximator struct
method compare (line 104) | func (a durationApproximator) compare(x, y time.Duration) bool {
function TestMetrics_NoInfiniteRate (line 116) | func TestMetrics_NoInfiniteRate(t *testing.T) {
function TestMetrics_NonNilErrorsOnClose (line 128) | func TestMetrics_NonNilErrorsOnClose(t *testing.T) {
function TestMetrics_EmptyMetricsCanBeReported (line 142) | func TestMetrics_EmptyMetricsCanBeReported(t *testing.T) {
function BenchmarkMetrics (line 153) | func BenchmarkMetrics(b *testing.B) {
type bmizeranyEstimator (line 206) | type bmizeranyEstimator struct
method Add (line 214) | func (e *bmizeranyEstimator) Add(s float64) { e.Insert(s) }
method Get (line 215) | func (e *bmizeranyEstimator) Get(q float64) float64 {
function newBmizeranyEstimator (line 210) | func newBmizeranyEstimator(qs ...float64) *bmizeranyEstimator {
type dgryskiEstimator (line 219) | type dgryskiEstimator struct
method Add (line 227) | func (e *dgryskiEstimator) Add(s float64) { e.Insert(s) }
method Get (line 228) | func (e *dgryskiEstimator) Get(q float64) float64 {
function newDgriskyEstimator (line 223) | func newDgriskyEstimator(epsilon float64) *dgryskiEstimator {
FILE: lib/pacer.go
type Pacer (line 10) | type Pacer interface
type PacerFunc (line 24) | type PacerFunc
method Pace (line 27) | func (pf PacerFunc) Pace(elapsed time.Duration, hits uint64) (time.Dur...
type ConstantPacer (line 32) | type ConstantPacer struct
method String (line 46) | func (cp ConstantPacer) String() string {
method Pace (line 51) | func (cp ConstantPacer) Pace(elapsed time.Duration, hits uint64) (time...
method Rate (line 77) | func (cp ConstantPacer) Rate(elapsed time.Duration) float64 {
method hitsPerNs (line 83) | func (cp ConstantPacer) hitsPerNs() float64 {
constant MeanUp (line 90) | MeanUp float64 = 0
constant Peak (line 93) | Peak = math.Pi / 2
constant MeanDown (line 96) | MeanDown = math.Pi
constant Trough (line 99) | Trough = 3 * math.Pi / 2
type SinePacer (line 140) | type SinePacer struct
method String (line 166) | func (sp SinePacer) String() string {
method invalid (line 171) | func (sp SinePacer) invalid() bool {
method Pace (line 176) | func (sp SinePacer) Pace(elapsedTime time.Duration, elapsedHits uint64...
method Rate (line 209) | func (sp SinePacer) Rate(elapsed time.Duration) float64 {
method ampHits (line 216) | func (sp SinePacer) ampHits() float64 {
method radians (line 223) | func (sp SinePacer) radians(t time.Duration) float64 {
method hitsPerNs (line 231) | func (sp SinePacer) hitsPerNs(t time.Duration) float64 {
method hits (line 244) | func (sp SinePacer) hits(t time.Duration) float64 {
type LinearPacer (line 253) | type LinearPacer struct
method Pace (line 259) | func (p LinearPacer) Pace(elapsed time.Duration, hits uint64) (time.Du...
method Rate (line 289) | func (p LinearPacer) Rate(elapsed time.Duration) float64 {
method hits (line 299) | func (p LinearPacer) hits(t time.Duration) float64 {
FILE: lib/pacer_test.go
function TestConstantPacer (line 11) | func TestConstantPacer(t *testing.T) {
function TestConstantPacer_Rate (line 68) | func TestConstantPacer_Rate(t *testing.T) {
function floatEqual (line 102) | func floatEqual(x, y float64) bool {
function durationEqual (line 110) | func durationEqual(x, y time.Duration) bool {
type sineTest (line 159) | type sineTest struct
method Pacer (line 165) | func (st sineTest) Pacer(startAt float64) SinePacer {
method ampHits (line 175) | func (st sineTest) ampHits() float64 {
function TestSinePacerHits (line 179) | func TestSinePacerHits(t *testing.T) {
function TestSinePacerInvalid (line 212) | func TestSinePacerInvalid(t *testing.T) {
function TestSinePacerPace_Flat (line 233) | func TestSinePacerPace_Flat(t *testing.T) {
function TestSincePacer_Rate (line 262) | func TestSincePacer_Rate(t *testing.T) {
function TestLinearPacer (line 291) | func TestLinearPacer(t *testing.T) {
function TestLinearPacer_hits (line 364) | func TestLinearPacer_hits(t *testing.T) {
function TestLinearPacer_Rate (line 392) | func TestLinearPacer_Rate(t *testing.T) {
FILE: lib/plot/assets/uplot-plugins.js
function pivotData (line 2) | function pivotData(rowData) {
function formatDuration (line 18) | function formatDuration(ms) {
function exportToPNG (line 39) | function exportToPNG(uplotInstance, filename) {
FILE: lib/plot/embed.go
type embedFS (line 19) | type embedFS struct
method Open (line 23) | func (e *embedFS) Open(name string) (http.File, error) {
type embedFile (line 52) | type embedFile struct
method Readdir (line 56) | func (f *embedFile) Readdir(count int) ([]os.FileInfo, error) {
method Seek (line 60) | func (f *embedFile) Seek(offset int64, whence int) (int64, error) {
type embedFileSeeker (line 64) | type embedFileSeeker struct
method Read (line 70) | func (f *embedFileSeeker) Read(p []byte) (int, error) {
method Seek (line 74) | func (f *embedFileSeeker) Seek(offset int64, whence int) (int64, error) {
method Readdir (line 78) | func (f *embedFileSeeker) Readdir(count int) ([]os.FileInfo, error) {
type bytesReaderAt (line 82) | type bytesReaderAt struct
method ReadAt (line 86) | func (r *bytesReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
FILE: lib/plot/plot.go
type Plot (line 20) | type Plot struct
method Add (line 148) | func (p *Plot) Add(r *vegeta.Result) error {
method Close (line 158) | func (p *Plot) Close() {
method WriteTo (line 167) | func (p *Plot) WriteTo(w io.Writer) (n int64, err error) {
method data (line 269) | func (p *Plot) data() (dataPoints, []string, error) {
type Labeler (line 30) | type Labeler
function ErrorLabeler (line 35) | func ErrorLabeler(r *vegeta.Result) (label string) {
type labeledSeries (line 47) | type labeledSeries struct
method add (line 71) | func (ls *labeledSeries) add(r *vegeta.Result) (err error) {
type point (line 56) | type point struct
function newLabeledSeries (line 63) | func newLabeledSeries(label Labeler) *labeledSeries {
type Opt (line 113) | type Opt
function Title (line 116) | func Title(title string) Opt {
function Downsample (line 122) | func Downsample(threshold int) Opt {
function Label (line 128) | func Label(l Labeler) Opt {
function New (line 134) | func New(opts ...Opt) *Plot {
function labelColors (line 249) | func labelColors(labels []string) []string {
function asset (line 321) | func asset(path string) ([]byte, error) {
type countingWriter (line 329) | type countingWriter struct
method Write (line 334) | func (cw *countingWriter) Write(p []byte) (int, error) {
type dataPoints (line 340) | type dataPoints
method Len (line 342) | func (ps dataPoints) Len() int { return len(ps) }
method Less (line 344) | func (ps dataPoints) Less(i, j int) bool {
method Swap (line 349) | func (ps dataPoints) Swap(i, j int) {
method Append (line 353) | func (ps dataPoints) Append(buf []byte) []byte {
FILE: lib/plot/plot_test.go
function TestPlot (line 21) | func TestPlot(t *testing.T) {
function TestLabeledSeries (line 69) | func TestLabeledSeries(t *testing.T) {
function BenchmarkPlot (line 132) | func BenchmarkPlot(b *testing.B) {
FILE: lib/plot/timeseries.go
type timeSeries (line 13) | type timeSeries struct
method add (line 31) | func (ts *timeSeries) add(t uint64, v float64) error {
method iter (line 43) | func (ts *timeSeries) iter() lttb.Iter {
function newTimeSeries (line 21) | func newTimeSeries(attack, label string) *timeSeries {
FILE: lib/prom/prom.go
type Metrics (line 16) | type Metrics struct
method Register (line 49) | func (pm *Metrics) Register(r prometheus.Registerer) error {
method Observe (line 64) | func (pm *Metrics) Observe(res *vegeta.Result) {
function NewMetrics (line 25) | func NewMetrics() *Metrics {
function NewHandler (line 76) | func NewHandler(r *prometheus.Registry, startTime time.Time) http.Handler {
FILE: lib/prom/prom_test.go
function TestMetrics_Observe (line 15) | func TestMetrics_Observe(t *testing.T) {
FILE: lib/reporters.go
type Report (line 14) | type Report interface
type Closer (line 20) | type Closer interface
type Reporter (line 27) | type Reporter
method Report (line 30) | func (rep Reporter) Report(w io.Writer) error { return rep(w) }
function NewHistogramReporter (line 34) | func NewHistogramReporter(h *Histogram) Reporter {
function NewTextReporter (line 57) | func NewTextReporter(m *Metrics) Reporter {
function round (line 125) | func round(d time.Duration) time.Duration {
function NewJSONReporter (line 135) | func NewJSONReporter(m *Metrics) Reporter {
function NewHDRHistogramPlotReporter (line 240) | func NewHDRHistogramPlotReporter(m *Metrics) Reporter {
function milliseconds (line 269) | func milliseconds(d time.Duration) float64 {
function oneByQuantile (line 274) | func oneByQuantile(q float64) float64 {
FILE: lib/results.go
function init (line 21) | func init() {
type Result (line 26) | type Result struct
method End (line 42) | func (r *Result) End() time.Time { return r.Timestamp.Add(r.Latency) }
method Equal (line 45) | func (r Result) Equal(other Result) bool {
function headerEqual (line 60) | func headerEqual(h1, h2 http.Header) bool {
type Results (line 83) | type Results
method Add (line 87) | func (rs *Results) Add(r *Result) { *rs = append(*rs, *r) }
method Close (line 91) | func (rs *Results) Close() { sort.Sort(rs) }
method Len (line 94) | func (rs Results) Len() int { return len(rs) }
method Less (line 95) | func (rs Results) Less(i, j int) bool { return rs[i].Timestamp.Before(...
method Swap (line 96) | func (rs Results) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
type Decoder (line 99) | type Decoder
method Decode (line 152) | func (dec Decoder) Decode(r *Result) error { return dec(r) }
type DecoderFactory (line 102) | type DecoderFactory
function DecoderFor (line 107) | func DecoderFor(r io.Reader) Decoder {
function NewRoundRobinDecoder (line 124) | func NewRoundRobinDecoder(dec ...Decoder) Decoder {
function NewDecoder (line 145) | func NewDecoder(rd io.Reader) Decoder {
type Encoder (line 155) | type Encoder
method Encode (line 165) | func (enc Encoder) Encode(r *Result) error { return enc(r) }
function NewEncoder (line 158) | func NewEncoder(r io.Writer) Encoder {
function NewCSVEncoder (line 171) | func NewCSVEncoder(w io.Writer) Encoder {
function headerBytes (line 198) | func headerBytes(h http.Header) []byte {
function NewCSVDecoder (line 208) | func NewCSVDecoder(r io.Reader) Decoder {
type jsonResult (line 274) | type jsonResult
function NewJSONEncoder (line 278) | func NewJSONEncoder(w io.Writer) Encoder {
function NewJSONDecoder (line 292) | func NewJSONDecoder(r io.Reader) Decoder {
FILE: lib/results_easyjson.go
function easyjsonBd1621b8DecodeGithubComTsenartVegetaV12Lib (line 23) | func easyjsonBd1621b8DecodeGithubComTsenartVegetaV12Lib(in *jlexer.Lexer...
function easyjsonBd1621b8EncodeGithubComTsenartVegetaV12Lib (line 118) | func easyjsonBd1621b8EncodeGithubComTsenartVegetaV12Lib(out *jwriter.Wri...
method MarshalEasyJSON (line 213) | func (v jsonResult) MarshalEasyJSON(w *jwriter.Writer) {
method UnmarshalEasyJSON (line 218) | func (v *jsonResult) UnmarshalEasyJSON(l *jlexer.Lexer) {
FILE: lib/results_fuzz.go
function FuzzResultsFormatDetection (line 12) | func FuzzResultsFormatDetection(fuzz []byte) int {
function FuzzGobDecoder (line 25) | func FuzzGobDecoder(fuzz []byte) int {
function FuzzCSVDecoder (line 35) | func FuzzCSVDecoder(fuzz []byte) int {
function FuzzJSONDecoder (line 45) | func FuzzJSONDecoder(fuzz []byte) int {
function readAllResults (line 54) | func readAllResults(decoder Decoder) (ok bool) {
FILE: lib/results_test.go
function TestResultDecoding (line 17) | func TestResultDecoding(t *testing.T) {
function TestResultEncoding (line 55) | func TestResultEncoding(t *testing.T) {
function BenchmarkResultEncodings (line 145) | func BenchmarkResultEncodings(b *testing.B) {
FILE: lib/targets.go
type Target (line 24) | type Target struct
method Request (line 33) | func (t *Target) Request() (*http.Request, error) {
method Equal (line 57) | func (t *Target) Equal(other *Target) bool {
constant HTTPTargetFormat (line 107) | HTTPTargetFormat = "http"
constant JSONTargetFormat (line 109) | JSONTargetFormat = "json"
type Targeter (line 114) | type Targeter
method Decode (line 117) | func (tr Targeter) Decode(t *Target) error {
function NewJSONTargeter (line 132) | func NewJSONTargeter(src io.Reader, body []byte, header http.Header) Tar...
type TargetEncoder (line 196) | type TargetEncoder
method Encode (line 199) | func (enc TargetEncoder) Encode(t *Target) error {
function NewJSONTargetEncoder (line 204) | func NewJSONTargetEncoder(w io.Writer) TargetEncoder {
function NewStaticTargeter (line 219) | func NewStaticTargeter(tgts ...Target) Targeter {
function ReadAllTargets (line 231) | func ReadAllTargets(t Targeter) (tgts []Target, err error) {
function NewHTTPTargeter (line 262) | func NewHTTPTargeter(src io.Reader, body []byte, hdr http.Header) Target...
function startsWithHTTPMethod (line 343) | func startsWithHTTPMethod(t string) bool {
type peekingScanner (line 349) | type peekingScanner struct
method Err (line 354) | func (s *peekingScanner) Err() error {
method Peek (line 358) | func (s *peekingScanner) Peek() string {
method Scan (line 366) | func (s *peekingScanner) Scan() bool {
method Text (line 373) | func (s *peekingScanner) Text() string {
FILE: lib/targets_easyjson.go
type jsonTarget (line 13) | type jsonTarget
method decode (line 15) | func (t *jsonTarget) decode(in *jlexer.Lexer) {
method encode (line 97) | func (t jsonTarget) encode(out *jwriter.Writer) {
FILE: lib/targets_fuzz.go
function FuzzHTTPTargeter (line 12) | func FuzzHTTPTargeter(fuzz []byte) int {
function FuzzJSONTargeter (line 30) | func FuzzJSONTargeter(fuzz []byte) int {
function decodeFuzzTargetDefaults (line 47) | func decodeFuzzTargetDefaults(fuzz []byte) (
FILE: lib/targets_test.go
function TestTargetRequest (line 16) | func TestTargetRequest(t *testing.T) {
function TestTargetRequest_EmptyBody (line 60) | func TestTargetRequest_EmptyBody(t *testing.T) {
function TestJSONTargeter (line 93) | func TestJSONTargeter(t *testing.T) {
function TestReadAllTargets (line 204) | func TestReadAllTargets(t *testing.T) {
type errReader (line 282) | type errReader struct
method Read (line 284) | func (e errReader) Read(p []byte) (n int, err error) {
function TestNewHTTPTargeter (line 288) | func TestNewHTTPTargeter(t *testing.T) {
function TestErrNilTarget (line 441) | func TestErrNilTarget(t *testing.T) {
function BenchmarkJSONTargetEncoding (line 455) | func BenchmarkJSONTargetEncoding(b *testing.B) {
FILE: lib/util_fuzz.go
function decodeFuzzHeaders (line 6) | func decodeFuzzHeaders(fuzz []byte, headers map[string][]string) (
function decodeFuzzHeader (line 38) | func decodeFuzzHeader(fuzz []byte, headers map[string][]string) (
function extractFuzzString (line 63) | func extractFuzzString(fuzz []byte) (
function extractFuzzByteString (line 93) | func extractFuzzByteString(fuzz []byte) (
FILE: main.go
function main (line 14) | func main() {
constant examples (line 106) | examples = `
type command (line 114) | type command struct
FILE: plot.go
constant plotUsage (line 14) | plotUsage = `Usage: vegeta plot [options] [<file>...]
function plotCmd (line 43) | func plotCmd() command {
function plotRun (line 63) | func plotRun(files []string, threshold int, title, output string) error {
FILE: report.go
constant reportUsage (line 15) | reportUsage = `Usage: vegeta report [options] [<file>...]
function reportCmd (line 40) | func reportCmd() command {
function report (line 61) | func report(files []string, typ, output string, every time.Duration, buc...
function writeReport (line 158) | func writeReport(r vegeta.Reporter, rc vegeta.Closer, out io.Writer) err...
function clear (line 165) | func clear(out io.Writer) error {
FILE: report_nonwindows.go
function clearScreen (line 12) | func clearScreen() error {
FILE: report_windows.go
function clearScreen (line 11) | func clearScreen() error {
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (834K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 15,
"preview": "* @tsenart @xla"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 744,
"preview": "# Contributing\n\nNon trivial changes should be discussed with the project maintainers by\nopening a [Feature Request](http"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 815,
"preview": "---\nname: 🐛 Bug Report\nabout: Report a bug to help Vegeta improve.\n---\n\n<!-- ⚠️ If you do not respect this template you"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 577,
"preview": "---\nname: 🚀 Feature Request\nabout: Propose a change to Vegeta💡!\n---\n\n<!-- ⚠️ If you do not respect this template your i"
},
{
"path": ".github/ISSUE_TEMPLATE/question.md",
"chars": 108,
"preview": "---\nname: ❓ Question\nabout: Ask a question that isn't a feature request nor a bug report.\n---\n\n#### Question"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 398,
"preview": "#### Background\n\n<!-- Required background information to understand the PR. Link here any related issues. -->\n\n#### Chec"
},
{
"path": ".github/dependabot.yml",
"chars": 433,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/ci.yml",
"chars": 4209,
"preview": "name: CI\non:\n push:\n tags:\n - \"v*.*.*\"\n branches:\n - master\n pull_request:\n types: [opened, synchro"
},
{
"path": ".gitignore",
"chars": 336,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": "CHANGELOG",
"chars": 2548,
"preview": "2018-05-18: v7.0.0\n Include response body in hit results (#279)\n Added support for h2c requests (HTTP/2 without TLS) ("
},
{
"path": "Dockerfile",
"chars": 306,
"preview": "FROM golang:1.20-alpine3.18 AS BUILD\n\nRUN apk add make build-base git\n\nWORKDIR /vegeta\n\n# cache dependencies\nADD go.mod "
},
{
"path": "LICENSE",
"chars": 1084,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013-2023 Tomás Senart\n\nPermission is hereby granted, free of charge, to any person"
},
{
"path": "Makefile",
"chars": 558,
"preview": "COMMIT=$(shell git rev-parse HEAD)\nVERSION=$(shell git describe --tags --exact-match --always)\nDATE=$(shell date +'%FT%T"
},
{
"path": "README.md",
"chars": 31707,
"preview": "# Vegeta [](https://github.com/tsenart/vegeta/a"
},
{
"path": "attack.go",
"chars": 9286,
"preview": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\""
},
{
"path": "attack_nonwindows.go",
"chars": 287,
"preview": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport \"flag\"\n\nfunc systemSpecificFlags(fs *flag.FlagSet, opts *at"
},
{
"path": "attack_test.go",
"chars": 3957,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"reflect\"\n\t\"sync\"\n\t\"testing\"\n\t\"ti"
},
{
"path": "attack_windows.go",
"chars": 93,
"preview": "package main\n\nimport \"flag\"\n\nfunc systemSpecificFlags(fs *flag.FlagSet, opts *attackOpts) {}\n"
},
{
"path": "dump.go",
"chars": 205,
"preview": "package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc dumpCmd() command {\n\treturn command{fn: func([]string) error {\n\t\treturn fmt.Errorf"
},
{
"path": "encode.go",
"chars": 2624,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\n\tvegeta \"github.com/tsenart/vegeta/v12/lib\"\n)"
},
{
"path": "file.go",
"chars": 1131,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\tvegeta \"github.com/tsenart/vegeta/v12/lib\"\n)\n\nfunc file"
},
{
"path": "flags.go",
"chars": 4418,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/c2"
},
{
"path": "go.mod",
"chars": 1863,
"preview": "module github.com/tsenart/vegeta/v12\n\ngo 1.22\n\nrequire (\n\tgithub.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9"
},
{
"path": "go.sum",
"chars": 15813,
"preview": "github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog=\ngith"
},
{
"path": "internal/cmd/echosrv/main.go",
"chars": 1439,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"flag\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"ne"
},
{
"path": "internal/cmd/jsonschema/main.go",
"chars": 1126,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/jsonschema\"\n\n\tvegeta \"g"
},
{
"path": "internal/resolver/resolver.go",
"chars": 1840,
"preview": "package resolver\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\ntype resolver stru"
},
{
"path": "internal/resolver/resolver_test.go",
"chars": 3562,
"preview": "package resolver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"string"
},
{
"path": "lib/attack.go",
"chars": 15643,
"preview": "package vegeta\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strc"
},
{
"path": "lib/attack_fuzz.go",
"chars": 2934,
"preview": "//go:build gofuzz\n// +build gofuzz\n\npackage vegeta\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n"
},
{
"path": "lib/attack_test.go",
"chars": 14490,
"preview": "package vegeta\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\""
},
{
"path": "lib/histogram.go",
"chars": 1998,
"preview": "package vegeta\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Buckets represents an Histogram's latency buckets.\ntyp"
},
{
"path": "lib/histogram_test.go",
"chars": 1705,
"preview": "package vegeta\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestHistogram_Add(t *testing.T) {\n\tt.Parallel()\n\thist := "
},
{
"path": "lib/lttb/lttb.go",
"chars": 2653,
"preview": "package lttb\n\nimport \"errors\"\n\n// A Point in a line chart.\ntype Point struct{ X, Y float64 }\n\n// An Iter is an iterator "
},
{
"path": "lib/lttb/lttb_test.go",
"chars": 389634,
"preview": "package lttb\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n\t\"testing\"\n\t\"unsafe\"\n\n\tgolttb \"github.com/dgryski/go-lttb\"\n\t\"github.com"
},
{
"path": "lib/metrics.go",
"chars": 5798,
"preview": "package vegeta\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/influxdata/tdigest\"\n)\n\n// Metrics holds metrics computed out o"
},
{
"path": "lib/metrics_test.go",
"chars": 5339,
"preview": "package vegeta\n\nimport (\n\t\"io\"\n\t\"math/rand\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\tbmizerany \"github.com/bmizerany/perks/quanti"
},
{
"path": "lib/pacer.go",
"chars": 10675,
"preview": "package vegeta\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n)\n\n// A Pacer defines the rate of hits during an Attack.\ntype Pacer inte"
},
{
"path": "lib/pacer_test.go",
"chars": 12741,
"preview": "package vegeta\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"testing\"\n\t\"testing/quick\"\n\t\"time\"\n)\n\nfunc TestConstantPacer(t *testing.T) "
},
{
"path": "lib/plot/assets/VERSIONS",
"chars": 14,
"preview": "uPlot v1.6.32\n"
},
{
"path": "lib/plot/assets/plot.html.tpl",
"chars": 10024,
"preview": "<!doctype html>\n<html>\n<head>\n <title>{{.Title}}</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"widt"
},
{
"path": "lib/plot/assets/uplot-plugins.js",
"chars": 1400,
"preview": "// Data pivoting: Convert row-oriented data to column-oriented for uPlot\nfunction pivotData(rowData) {\n if (!rowData ||"
},
{
"path": "lib/plot/assets.go",
"chars": 171,
"preview": "//go:build dev\n// +build dev\n\npackage plot\n\nimport (\n\t\"net/http\"\n)\n\n// Assets contains assets required to render the Plo"
},
{
"path": "lib/plot/embed.go",
"chars": 1630,
"preview": "//go:build !dev\n\npackage plot\n\nimport (\n\t\"embed\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"os\"\n)\n\n//go:embed assets/*\nvar assetsFS em"
},
{
"path": "lib/plot/plot.go",
"chars": 7684,
"preview": "package plot\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"math\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tve"
},
{
"path": "lib/plot/plot_test.go",
"chars": 3404,
"preview": "package plot\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.c"
},
{
"path": "lib/plot/testdata/TestPlot.golden.html",
"chars": 96023,
"preview": "<!doctype html>\n<html>\n<head>\n <title>TestPlot</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width="
},
{
"path": "lib/plot/timeseries.go",
"chars": 1087,
"preview": "package plot\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\ttsz \"github.com/tsenart/go-tsz\"\n\t\"github.com/tsenart/vegeta/v12/lib/lttb\"\n)\n\n"
},
{
"path": "lib/prom/grafana.json",
"chars": 14257,
"preview": "{\n \"__inputs\": [\n {\n \"name\": \"DS_PROMETHEUS\",\n \"label\": \"prometheus\",\n \"description\": \"\",\n \"type"
},
{
"path": "lib/prom/prom.go",
"chars": 2749,
"preview": "package prom\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github"
},
{
"path": "lib/prom/prom_test.go",
"chars": 1985,
"preview": "package prom\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/"
},
{
"path": "lib/reporters.go",
"chars": 5876,
"preview": "package vegeta\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n)\n\n// A Report repre"
},
{
"path": "lib/results.go",
"chars": 7707,
"preview": "package vegeta\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/csv\"\n\t\"encoding/gob\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/te"
},
{
"path": "lib/results_easyjson.go",
"chars": 4631,
"preview": "// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.\n\npackage vegeta\n\nimport (\n\tjson \"encoding/json\"\n"
},
{
"path": "lib/results_fuzz.go",
"chars": 1187,
"preview": "//go:build gofuzz\n// +build gofuzz\n\npackage vegeta\n\nimport (\n\t\"bytes\"\n\t\"io\"\n)\n\n// FuzzResultsFormatDetection tests resul"
},
{
"path": "lib/results_test.go",
"chars": 4681,
"preview": "package vegeta\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"githu"
},
{
"path": "lib/target.schema.json",
"chars": 774,
"preview": "{\n \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n \"$ref\": \"#/definitions/Target\",\n \"definitions\": {\n \"Targ"
},
{
"path": "lib/targets.go",
"chars": 9145,
"preview": "package vegeta\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sy"
},
{
"path": "lib/targets_easyjson.go",
"chars": 3074,
"preview": "// This file has been modified from the original generated code to make it work with\n// type alias jsonTarget so that th"
},
{
"path": "lib/targets_fuzz.go",
"chars": 1156,
"preview": "//go:build gofuzz\n// +build gofuzz\n\npackage vegeta\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n)\n\n// FuzzHTTPTargeter tests decoding "
},
{
"path": "lib/targets_test.go",
"chars": 11292,
"preview": "package vegeta\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testin"
},
{
"path": "lib/util_fuzz.go",
"chars": 1888,
"preview": "//go:build gofuzz\n// +build gofuzz\n\npackage vegeta\n\nfunc decodeFuzzHeaders(fuzz []byte, headers map[string][]string) (\n\t"
},
{
"path": "main.go",
"chars": 2424,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"sort\"\n\t\"strings\"\n)\n\nfunc main() {\n\tcomm"
},
{
"path": "plot.go",
"chars": 2749,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\n\tvegeta \"github.com/tsenart/vegeta/v12/lib\"\n\t\"github.com"
},
{
"path": "report.go",
"chars": 3989,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"time\"\n\n\tvegeta \"github.com/tsenart/vegeta/v1"
},
{
"path": "report_nonwindows.go",
"chars": 193,
"preview": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport (\n\t\"os\"\n)\n\nvar escCodes = []byte(\"\\033[2J\\033[0;0H\")\n\nfunc "
},
{
"path": "report_windows.go",
"chars": 193,
"preview": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n)\n\nfunc clearScreen() error {\n\tcmd := exec"
},
{
"path": "scripts/load-ramping/README.md",
"chars": 372,
"preview": "# Load ramping\n\nThis script will automatically run vegeta against a target with different request\nrates and graph the la"
},
{
"path": "scripts/load-ramping/ramp-requests.plt",
"chars": 1642,
"preview": "# Two plots: success rate plot on top, rate/latency distribution below\nset multiplot layout 2,1\n\n\n#\n# Shared config\n#\n\n#"
},
{
"path": "scripts/load-ramping/ramp-requests.py",
"chars": 2236,
"preview": "#!/usr/bin/env python3\n\nimport json\nimport os\nimport subprocess\nimport sys\nimport time\n\n\nif '-h' in sys.argv or '--help'"
}
]
About this extraction
This page contains the full source code of the tsenart/vegeta GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (742.9 KB), approximately 330.7k tokens, and a symbol index with 354 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.