├── .gitignore ├── locust_exporter.png ├── locust_dashboard.png ├── Dockerfile ├── CONTRIBUTING.md ├── go.mod ├── .github ├── workflows │ ├── release-drafter.yml │ ├── ci.yml │ ├── deploy-main.yml │ ├── trivy-aqua.yml │ └── deploy-release.yml ├── dependabot.yml └── release-drafter.yml ├── .promu.yml ├── Makefile ├── CHANGELOG.md ├── README.md ├── Makefile.common ├── LICENSE ├── locust_exporter.md ├── go.sum ├── main.go └── locust_dashboard.json /.gitignore: -------------------------------------------------------------------------------- 1 | locust_exporter 2 | main 3 | .idea 4 | -------------------------------------------------------------------------------- /locust_exporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContainerSolutions/locust_exporter/HEAD/locust_exporter.png -------------------------------------------------------------------------------- /locust_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContainerSolutions/locust_exporter/HEAD/locust_dashboard.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | EXPOSE 9646 3 | USER 1000 4 | 5 | COPY locust_exporter /bin/locust_exporter 6 | 7 | ENTRYPOINT ["locust_exporter"] 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests! 6 | 7 | **Happy Coding!** 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ContainerSolutions/locust_exporter 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/prometheus/client_golang v1.11.1 7 | github.com/prometheus/common v0.26.0 8 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 9 | ) 10 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: gomod 5 | directory: "/" 6 | target-branch: "main" 7 | schedule: 8 | interval: weekly 9 | time: "04:00" 10 | open-pull-requests-limit: 10 11 | 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | target-branch: "main" 15 | schedule: 16 | interval: "weekly" 17 | time: "04:00" 18 | open-pull-requests-limit: 10 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches-ignore: 7 | - main 8 | pull_request: 9 | types: [opened] 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '^1.12' 23 | 24 | - name: Build 25 | run: make build 26 | 27 | - name: Container - Build 28 | run: docker build . 29 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | go: 3 | version: 1.12 4 | cgo: false 5 | repository: 6 | path: github.com/ContainerSolutions/locust_exporter 7 | build: 8 | flags: -a -tags netgo 9 | ldflags: | 10 | -s 11 | -X {{repoPath}}/version.Version={{.Version}} 12 | -X {{repoPath}}/version.Revision={{.Revision}} 13 | -X {{repoPath}}/version.Branch={{.Branch}} 14 | -X {{repoPath}}/version.BuildUser={{user}}@{{host}} 15 | -X {{repoPath}}/version.BuildDate={{date "20060102-15:04:05"}} 16 | tarball: 17 | prefix: . 18 | files: 19 | - LICENSE 20 | crossbuild: 21 | platforms: 22 | - linux/amd64 23 | - linux/386 24 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | 4 | categories: 5 | - title: 'Features' 6 | labels: 7 | - 'feature' 8 | - title: 'Bugs' 9 | labels: 10 | - 'bug' 11 | - 'fix' 12 | - title: 'Maintenance' 13 | labels: 14 | - 'chore' 15 | 16 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 17 | change-title-escapes: '\<*_&' 18 | 19 | version-resolver: 20 | default: patch 21 | major: 22 | labels: 23 | - 'major' 24 | minor: 25 | labels: 26 | - 'minor' 27 | patch: 28 | labels: 29 | - 'patch' 30 | 31 | template: | 32 | ## Changes 33 | 34 | $CHANGES 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | include Makefile.common 15 | 16 | ## This is a copy! 17 | ## https://github.com/prometheus/prometheus/blob/main/Makefile.common 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | Please see [GitHub Releases](https://github.com/ContainerSolutions/locust_exporter/releases) for the latest updates. 4 | 5 | ## 0.4.1 / 2021-02-27 6 | 7 | * PR [#20](https://github.com/ContainerSolutions/locust_exporter/pull/20) migrate to GitHub Actions 8 | 9 | ## 0.4.0 / 2020-09-08 10 | 11 | * PR [#7](https://github.com/ContainerSolutions/locust_exporter/pull/7) replace slave by worker 12 | 13 | ## 0.3.0 / 2020-02-18 14 | 15 | * PR [#1](https://github.com/ContainerSolutions/locust_exporter/pull/1) fixed compat issues with occurences & slave count, add error message 16 | * PR [#2](https://github.com/ContainerSolutions/locust_exporter/pull/2) New labes added 17 | 18 | ## 0.2.0 / 2019-09-06 19 | 20 | Initial release of Go version 21 | 22 | ## 0.1.0 / 2019-09-04 23 | 24 | Initial release of Python version 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy-main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | REGISTRY: containersol/locust_exporter 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '^1.12' 23 | 24 | - name: Build 25 | run: make build 26 | 27 | - name: Container - Login 28 | uses: docker/login-action@v2 29 | with: 30 | username: ${{ secrets.DOCKERHUB_USER }} 31 | password: ${{ secrets.DOCKERHUB_PASS }} 32 | 33 | - name: Container - Build 34 | run: docker build -t ${{ env.REGISTRY }}:latest . 35 | 36 | - name: Container - Push 37 | run: docker push ${{ env.REGISTRY }}:latest 38 | -------------------------------------------------------------------------------- /.github/workflows/trivy-aqua.yml: -------------------------------------------------------------------------------- 1 | name: Trivy Aqua 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 4 * * MON' 7 | 8 | env: 9 | REGISTRY: containersol/locust_exporter 10 | 11 | jobs: 12 | trivy-scan: 13 | name: Scan 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 10 16 | steps: 17 | 18 | - name: Container - Login 19 | uses: docker/login-action@v2 20 | with: 21 | username: ${{ secrets.DOCKERHUB_USER }} 22 | password: ${{ secrets.DOCKERHUB_PASS }} 23 | 24 | - name: Container - Pull 25 | run: docker pull ${{ env.REGISTRY }}:latest 26 | 27 | # Move to homoluctus/gitrivy when PR62 is merged 28 | # https://github.com/lazy-actions/gitrivy/pull/62 29 | - name: Container - Scan 30 | uses: wochinge/gitrivy@master 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | image: ${{ env.REGISTRY }}:latest 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy-release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Release 2 | 3 | on: 4 | release: 5 | types: 6 | - released 7 | 8 | env: 9 | REGISTRY: containersol/locust_exporter 10 | 11 | jobs: 12 | build: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Setup 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: '^1.12' 24 | 25 | - name: Build 26 | run: make build 27 | 28 | - name: Container - Login 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USER }} 32 | password: ${{ secrets.DOCKERHUB_PASS }} 33 | 34 | - name: Container - Build 35 | run: docker build -t ${{ env.REGISTRY }}:$(echo $GITHUB_REF | sed -e "s/refs\/tags\///g") . 36 | 37 | - name: Container - Push 38 | run: docker push ${{ env.REGISTRY }}:$(echo $GITHUB_REF | sed -e "s/refs\/tags\///g") 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Locust Exporter 2 | 3 | Prometheus exporter for [Locust](https://github.com/locustio/locust). This exporter was inspired by [mbolek/locust_exporter](https://github.com/mbolek/locust_exporter). 4 | 5 | [![Docker Pulls](https://img.shields.io/docker/pulls/containersol/locust_exporter.svg)](https://hub.docker.com/r/containersol/locust_exporter/tags) [![license](https://img.shields.io/github/license/ContainerSolutions/locust_exporter.svg)](https://github.com/ContainerSolutions/locust_exporter/blob/master/LICENSE) 6 | 7 | ![locust_dashboard](locust_dashboard.png) 8 | 9 | ## Quick Start 10 | 11 | This package is available for Docker: 12 | 13 | 1. Run Locust ([example docker-compose](https://github.com/locustio/locust/blob/master/examples/docker-compose/docker-compose.yml)) 14 | 15 | 2. Run Locust Exporter 16 | 17 | with docker: 18 | 19 | ```bash 20 | docker run --net=host containersol/locust_exporter 21 | ``` 22 | 23 | or with docker-compose: 24 | 25 | ```yaml 26 | version: "3.0" 27 | 28 | services: 29 | locust-exporter: 30 | image: containersol/locust_exporter 31 | network_mode: "host" 32 | ``` 33 | 34 | 3. Modify `prometheus.yml` to add target to Prometheus 35 | 36 | ```yaml 37 | scrape_configs: 38 | - job_name: 'locust' 39 | static_configs: 40 | - targets: [':9646'] 41 | ``` 42 | 43 | 4. Add dashboard to Grafana with ID [11985](https://grafana.com/grafana/dashboards/11985) 44 | 45 | ## Building and Running 46 | 47 | The default way to build is: 48 | 49 | ```bash 50 | go get github.com/ContainerSolutions/locust_exporter 51 | cd ${GOPATH-$HOME/go}/src/github.com/ContainerSolutions/locust_exporter/ 52 | go run main.go 53 | ``` 54 | 55 | ### Flags 56 | 57 | - `--locust.uri` 58 | Address of Locust. Default is `http://localhost:8089`. 59 | 60 | - `--locust.timeout` 61 | Timeout request to Locust. Default is `5s`. 62 | 63 | - `--web.listen-address` 64 | Address to listen on for web interface and telemetry. Default is `:9646`. 65 | 66 | - `--web.telemetry-path` 67 | Path under which to expose metrics. Default is `/metrics`. 68 | 69 | - `--locust.namespace` 70 | Namespace for prometheus metrics. Default `locust`. 71 | 72 | - `--log.level` 73 | Set logging level: one of `debug`, `info`, `warn`, `error`, `fatal` 74 | 75 | - `--log.format` 76 | Set the log output target and format. e.g. `logger:syslog?appname=bob&local=7` or `logger:stdout?json=true` 77 | Defaults to `logger:stderr`. 78 | 79 | ### Environment Variables 80 | 81 | The following environment variables configure the exporter: 82 | 83 | - `LOCUST_EXPORTER_URI` 84 | Address of Locust. Default is `http://localhost:8089`. 85 | 86 | - `LOCUST_EXPORTER_TIMEOUT` 87 | Timeout reqeust to Locust. Default is `5s`. 88 | 89 | - `LOCUST_EXPORTER_WEB_LISTEN_ADDRESS` 90 | Address to listen on for web interface and telemetry. Default is `:9646`. 91 | 92 | - `LOCUST_EXPORTER_WEB_TELEMETRY_PATH` 93 | Path under which to expose metrics. Default is `/metrics`. 94 | 95 | - `LOCUST_METRIC_NAMESPACE` 96 | Namespace for prometheus metrics. Default `locust`. 97 | 98 | ### Grafana 99 | 100 | The grafana dashboard has beed published with ID [11985](https://grafana.com/grafana/dashboards/11985) and was exported to [locust_dashboard.json](locust_dashboard.json). 101 | 102 | ### Screenshot 103 | 104 | [![locust exporter](locust_exporter.png)](locust_exporter.md) 105 | 106 | ### Changelog 107 | 108 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 109 | 110 | ## Contributing 111 | 112 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 113 | 114 | ## License 115 | 116 | Apache License. Please see [License File](LICENSE.md) for more information. 117 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | 15 | # A common Makefile that includes rules to be reused in different prometheus projects. 16 | # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! 17 | 18 | # Example usage : 19 | # Create the main Makefile in the root project directory. 20 | # include Makefile.common 21 | # customTarget: 22 | # @echo ">> Running customTarget" 23 | # 24 | 25 | # Ensure GOBIN is not set during build so that promu is installed to the correct path 26 | unexport GOBIN 27 | 28 | GO ?= go 29 | GOFMT ?= $(GO)fmt 30 | FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 31 | GOOPTS ?= 32 | GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) 33 | GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) 34 | 35 | GO_VERSION ?= $(shell $(GO) version) 36 | GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) 37 | PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') 38 | 39 | GOVENDOR := 40 | GO111MODULE := 41 | ifeq (, $(PRE_GO_111)) 42 | ifneq (,$(wildcard go.mod)) 43 | # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). 44 | GO111MODULE := on 45 | 46 | ifneq (,$(wildcard vendor)) 47 | # Always use the local vendor/ directory to satisfy the dependencies. 48 | GOOPTS := $(GOOPTS) -mod=vendor 49 | endif 50 | endif 51 | else 52 | ifneq (,$(wildcard go.mod)) 53 | ifneq (,$(wildcard vendor)) 54 | $(warning This repository requires Go >= 1.11 because of Go modules) 55 | $(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') 56 | endif 57 | else 58 | # This repository isn't using Go modules (yet). 59 | GOVENDOR := $(FIRST_GOPATH)/bin/govendor 60 | endif 61 | endif 62 | PROMU := $(FIRST_GOPATH)/bin/promu 63 | pkgs = ./... 64 | 65 | ifeq (arm, $(GOHOSTARCH)) 66 | GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) 67 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) 68 | else 69 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) 70 | endif 71 | 72 | GOTEST := $(GO) test 73 | GOTEST_DIR := 74 | ifneq ($(CIRCLE_JOB),) 75 | ifneq ($(shell which gotestsum),) 76 | GOTEST_DIR := test-results 77 | GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- 78 | endif 79 | endif 80 | 81 | PROMU_VERSION ?= 0.7.0 82 | PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz 83 | 84 | GOLANGCI_LINT := 85 | GOLANGCI_LINT_OPTS ?= 86 | GOLANGCI_LINT_VERSION ?= v1.36.0 87 | # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. 88 | # windows isn't included here because of the path separator being different. 89 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) 90 | ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) 91 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 92 | endif 93 | endif 94 | 95 | PREFIX ?= $(shell pwd) 96 | BIN_DIR ?= $(shell pwd) 97 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 98 | DOCKERFILE_PATH ?= ./Dockerfile 99 | DOCKERBUILD_CONTEXT ?= ./ 100 | DOCKER_REPO ?= prom 101 | 102 | DOCKER_ARCHS ?= amd64 103 | 104 | BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) 105 | PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) 106 | TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) 107 | 108 | ifeq ($(GOHOSTARCH),amd64) 109 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) 110 | # Only supported on amd64 111 | test-flags := -race 112 | endif 113 | endif 114 | 115 | # This rule is used to forward a target like "build" to "common-build". This 116 | # allows a new "build" target to be defined in a Makefile which includes this 117 | # one and override "common-build" without override warnings. 118 | %: common-% ; 119 | 120 | .PHONY: common-all 121 | common-all: precheck style check_license lint unused build test 122 | 123 | .PHONY: common-style 124 | common-style: 125 | @echo ">> checking code style" 126 | @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ 127 | if [ -n "$${fmtRes}" ]; then \ 128 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ 129 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \ 130 | exit 1; \ 131 | fi 132 | 133 | .PHONY: common-check_license 134 | common-check_license: 135 | @echo ">> checking license header" 136 | @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ 137 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ 138 | done); \ 139 | if [ -n "$${licRes}" ]; then \ 140 | echo "license header checking failed:"; echo "$${licRes}"; \ 141 | exit 1; \ 142 | fi 143 | 144 | .PHONY: common-deps 145 | common-deps: 146 | @echo ">> getting dependencies" 147 | ifdef GO111MODULE 148 | GO111MODULE=$(GO111MODULE) $(GO) mod download 149 | else 150 | $(GO) get $(GOOPTS) -t ./... 151 | endif 152 | 153 | .PHONY: update-go-deps 154 | update-go-deps: 155 | @echo ">> updating Go dependencies" 156 | @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ 157 | $(GO) get $$m; \ 158 | done 159 | GO111MODULE=$(GO111MODULE) $(GO) mod tidy 160 | ifneq (,$(wildcard vendor)) 161 | GO111MODULE=$(GO111MODULE) $(GO) mod vendor 162 | endif 163 | 164 | .PHONY: common-test-short 165 | common-test-short: $(GOTEST_DIR) 166 | @echo ">> running short tests" 167 | GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs) 168 | 169 | .PHONY: common-test 170 | common-test: $(GOTEST_DIR) 171 | @echo ">> running all tests" 172 | GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) 173 | 174 | $(GOTEST_DIR): 175 | @mkdir -p $@ 176 | 177 | .PHONY: common-format 178 | common-format: 179 | @echo ">> formatting code" 180 | GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) 181 | 182 | .PHONY: common-vet 183 | common-vet: 184 | @echo ">> vetting code" 185 | GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) 186 | 187 | .PHONY: common-lint 188 | common-lint: $(GOLANGCI_LINT) 189 | ifdef GOLANGCI_LINT 190 | @echo ">> running golangci-lint" 191 | ifdef GO111MODULE 192 | # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. 193 | # Otherwise staticcheck might fail randomly for some reason not yet explained. 194 | GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null 195 | GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) 196 | else 197 | $(GOLANGCI_LINT) run $(pkgs) 198 | endif 199 | endif 200 | 201 | # For backward-compatibility. 202 | .PHONY: common-staticcheck 203 | common-staticcheck: lint 204 | 205 | .PHONY: common-unused 206 | common-unused: $(GOVENDOR) 207 | ifdef GOVENDOR 208 | @echo ">> running check for unused packages" 209 | @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' 210 | else 211 | ifdef GO111MODULE 212 | @echo ">> running check for unused/missing packages in go.mod" 213 | GO111MODULE=$(GO111MODULE) $(GO) mod tidy 214 | ifeq (,$(wildcard vendor)) 215 | @git diff --exit-code -- go.sum go.mod 216 | else 217 | @echo ">> running check for unused packages in vendor/" 218 | GO111MODULE=$(GO111MODULE) $(GO) mod vendor 219 | @git diff --exit-code -- go.sum go.mod vendor/ 220 | endif 221 | endif 222 | endif 223 | 224 | .PHONY: common-build 225 | common-build: promu 226 | @echo ">> building binaries" 227 | GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) 228 | 229 | .PHONY: common-tarball 230 | common-tarball: promu 231 | @echo ">> building release tarball" 232 | $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) 233 | 234 | .PHONY: common-docker $(BUILD_DOCKER_ARCHS) 235 | common-docker: $(BUILD_DOCKER_ARCHS) 236 | $(BUILD_DOCKER_ARCHS): common-docker-%: 237 | docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ 238 | -f $(DOCKERFILE_PATH) \ 239 | --build-arg ARCH="$*" \ 240 | --build-arg OS="linux" \ 241 | $(DOCKERBUILD_CONTEXT) 242 | 243 | .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) 244 | common-docker-publish: $(PUBLISH_DOCKER_ARCHS) 245 | $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: 246 | docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" 247 | 248 | DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) 249 | .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) 250 | common-docker-tag-latest: $(TAG_DOCKER_ARCHS) 251 | $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: 252 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" 253 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" 254 | 255 | .PHONY: common-docker-manifest 256 | common-docker-manifest: 257 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) 258 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" 259 | 260 | .PHONY: promu 261 | promu: $(PROMU) 262 | 263 | $(PROMU): 264 | $(eval PROMU_TMP := $(shell mktemp -d)) 265 | curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) 266 | mkdir -p $(FIRST_GOPATH)/bin 267 | cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu 268 | rm -r $(PROMU_TMP) 269 | 270 | .PHONY: proto 271 | proto: 272 | @echo ">> generating code from proto files" 273 | @./scripts/genproto.sh 274 | 275 | ifdef GOLANGCI_LINT 276 | $(GOLANGCI_LINT): 277 | mkdir -p $(FIRST_GOPATH)/bin 278 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ 279 | | sed -e '/install -d/d' \ 280 | | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) 281 | endif 282 | 283 | ifdef GOVENDOR 284 | .PHONY: $(GOVENDOR) 285 | $(GOVENDOR): 286 | GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor 287 | endif 288 | 289 | .PHONY: precheck 290 | precheck:: 291 | 292 | define PRECHECK_COMMAND_template = 293 | precheck:: $(1)_precheck 294 | 295 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 296 | .PHONY: $(1)_precheck 297 | $(1)_precheck: 298 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 299 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 300 | exit 1; \ 301 | fi 302 | endef 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Container Solutions 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /locust_exporter.md: -------------------------------------------------------------------------------- 1 | 2 | # Sample :9646/metrics 3 | 4 | ``` Bash 5 | # HELP go_gc_duration_seconds A summary of the GC invocation durations. 6 | # TYPE go_gc_duration_seconds summary 7 | go_gc_duration_seconds{quantile="0"} 1.5468e-05 8 | go_gc_duration_seconds{quantile="0.25"} 5.5945e-05 9 | go_gc_duration_seconds{quantile="0.5"} 8.238e-05 10 | go_gc_duration_seconds{quantile="0.75"} 0.00016262 11 | go_gc_duration_seconds{quantile="1"} 0.000387374 12 | go_gc_duration_seconds_sum 0.062545795 13 | go_gc_duration_seconds_count 626 14 | # HELP go_goroutines Number of goroutines that currently exist. 15 | # TYPE go_goroutines gauge 16 | go_goroutines 11 17 | # HELP go_info Information about the Go environment. 18 | # TYPE go_info gauge 19 | go_info{version="go1.13.8"} 1 20 | # HELP go_memstats_alloc_bytes Number of bytes allocated and still in use. 21 | # TYPE go_memstats_alloc_bytes gauge 22 | go_memstats_alloc_bytes 8.14464e+06 23 | # HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed. 24 | # TYPE go_memstats_alloc_bytes_total counter 25 | go_memstats_alloc_bytes_total 2.648051232e+09 26 | # HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table. 27 | # TYPE go_memstats_buck_hash_sys_bytes gauge 28 | go_memstats_buck_hash_sys_bytes 1.495011e+06 29 | # HELP go_memstats_frees_total Total number of frees. 30 | # TYPE go_memstats_frees_total counter 31 | go_memstats_frees_total 9.040828e+06 32 | # HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started. 33 | # TYPE go_memstats_gc_cpu_fraction gauge 34 | go_memstats_gc_cpu_fraction 2.3302348602264766e-05 35 | # HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata. 36 | # TYPE go_memstats_gc_sys_bytes gauge 37 | go_memstats_gc_sys_bytes 2.38592e+06 38 | # HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use. 39 | # TYPE go_memstats_heap_alloc_bytes gauge 40 | go_memstats_heap_alloc_bytes 8.14464e+06 41 | # HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used. 42 | # TYPE go_memstats_heap_idle_bytes gauge 43 | go_memstats_heap_idle_bytes 5.6713216e+07 44 | # HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use. 45 | # TYPE go_memstats_heap_inuse_bytes gauge 46 | go_memstats_heap_inuse_bytes 9.314304e+06 47 | # HELP go_memstats_heap_objects Number of allocated objects. 48 | # TYPE go_memstats_heap_objects gauge 49 | go_memstats_heap_objects 11608 50 | # HELP go_memstats_heap_released_bytes Number of heap bytes released to OS. 51 | # TYPE go_memstats_heap_released_bytes gauge 52 | go_memstats_heap_released_bytes 5.5484416e+07 53 | # HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system. 54 | # TYPE go_memstats_heap_sys_bytes gauge 55 | go_memstats_heap_sys_bytes 6.602752e+07 56 | # HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection. 57 | # TYPE go_memstats_last_gc_time_seconds gauge 58 | go_memstats_last_gc_time_seconds 1.58505847702685e+09 59 | # HELP go_memstats_lookups_total Total number of pointer lookups. 60 | # TYPE go_memstats_lookups_total counter 61 | go_memstats_lookups_total 0 62 | # HELP go_memstats_mallocs_total Total number of mallocs. 63 | # TYPE go_memstats_mallocs_total counter 64 | go_memstats_mallocs_total 9.052436e+06 65 | # HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures. 66 | # TYPE go_memstats_mcache_inuse_bytes gauge 67 | go_memstats_mcache_inuse_bytes 13888 68 | # HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system. 69 | # TYPE go_memstats_mcache_sys_bytes gauge 70 | go_memstats_mcache_sys_bytes 16384 71 | # HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures. 72 | # TYPE go_memstats_mspan_inuse_bytes gauge 73 | go_memstats_mspan_inuse_bytes 58888 74 | # HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system. 75 | # TYPE go_memstats_mspan_sys_bytes gauge 76 | go_memstats_mspan_sys_bytes 114688 77 | # HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place. 78 | # TYPE go_memstats_next_gc_bytes gauge 79 | go_memstats_next_gc_bytes 9.938608e+06 80 | # HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations. 81 | # TYPE go_memstats_other_sys_bytes gauge 82 | go_memstats_other_sys_bytes 2.214165e+06 83 | # HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator. 84 | # TYPE go_memstats_stack_inuse_bytes gauge 85 | go_memstats_stack_inuse_bytes 1.081344e+06 86 | # HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator. 87 | # TYPE go_memstats_stack_sys_bytes gauge 88 | go_memstats_stack_sys_bytes 1.081344e+06 89 | # HELP go_memstats_sys_bytes Number of bytes obtained from system. 90 | # TYPE go_memstats_sys_bytes gauge 91 | go_memstats_sys_bytes 7.3335032e+07 92 | # HELP go_threads Number of OS threads created. 93 | # TYPE go_threads gauge 94 | go_threads 14 95 | # HELP locust_errors The current number of errors. 96 | # TYPE locust_errors gauge 97 | locust_errors{error="\"HTTPError('404 Client Error: NOT FOUND for url: http://locust-master:8089/does_not_exist',)\"",method="GET",name="/does_not_exist"} 20099 98 | # HELP locust_requests_avg_content_length 99 | # TYPE locust_requests_avg_content_length gauge 100 | locust_requests_avg_content_length{method="",name="Aggregated"} 5887.493411956757 101 | locust_requests_avg_content_length{method="GET",name="/"} 15045.000150157666 102 | locust_requests_avg_content_length{method="GET",name="/does_not_exist"} 232 103 | locust_requests_avg_content_length{method="GET",name="/stats/requests"} 2415.2776747682287 104 | # HELP locust_requests_avg_response_time 105 | # TYPE locust_requests_avg_response_time gauge 106 | locust_requests_avg_response_time{method="",name="Aggregated"} 8.325235602154045 107 | locust_requests_avg_response_time{method="GET",name="/"} 8.417879713507075 108 | locust_requests_avg_response_time{method="GET",name="/does_not_exist"} 8.222418855366953 109 | locust_requests_avg_response_time{method="GET",name="/stats/requests"} 8.336038763915587 110 | # HELP locust_requests_current_fail_per_sec 111 | # TYPE locust_requests_current_fail_per_sec gauge 112 | locust_requests_current_fail_per_sec{method="",name="Aggregated"} 1.8 113 | locust_requests_current_fail_per_sec{method="GET",name="/"} 0 114 | locust_requests_current_fail_per_sec{method="GET",name="/does_not_exist"} 1.8 115 | locust_requests_current_fail_per_sec{method="GET",name="/stats/requests"} 0 116 | # HELP locust_requests_current_response_time_percentile_50 117 | # TYPE locust_requests_current_response_time_percentile_50 gauge 118 | locust_requests_current_response_time_percentile_50 9 119 | # HELP locust_requests_current_response_time_percentile_95 120 | # TYPE locust_requests_current_response_time_percentile_95 gauge 121 | locust_requests_current_response_time_percentile_95 11 122 | # HELP locust_requests_current_rps 123 | # TYPE locust_requests_current_rps gauge 124 | locust_requests_current_rps{method="",name="Aggregated"} 3.3 125 | locust_requests_current_rps{method="GET",name="/"} 0.8 126 | locust_requests_current_rps{method="GET",name="/does_not_exist"} 1.8 127 | locust_requests_current_rps{method="GET",name="/stats/requests"} 0.7 128 | # HELP locust_requests_fail_ratio 129 | # TYPE locust_requests_fail_ratio gauge 130 | locust_requests_fail_ratio 0.3347991937767561 131 | # HELP locust_requests_max_response_time 132 | # TYPE locust_requests_max_response_time gauge 133 | locust_requests_max_response_time{method="",name="Aggregated"} 50 134 | locust_requests_max_response_time{method="GET",name="/"} 24 135 | locust_requests_max_response_time{method="GET",name="/does_not_exist"} 25 136 | locust_requests_max_response_time{method="GET",name="/stats/requests"} 50 137 | # HELP locust_requests_median_response_time 138 | # TYPE locust_requests_median_response_time gauge 139 | locust_requests_median_response_time{method="",name="Aggregated"} 9 140 | locust_requests_median_response_time{method="GET",name="/"} 9 141 | locust_requests_median_response_time{method="GET",name="/does_not_exist"} 9 142 | locust_requests_median_response_time{method="GET",name="/stats/requests"} 9 143 | # HELP locust_requests_min_response_time 144 | # TYPE locust_requests_min_response_time gauge 145 | locust_requests_min_response_time{method="",name="Aggregated"} 2 146 | locust_requests_min_response_time{method="GET",name="/"} 2 147 | locust_requests_min_response_time{method="GET",name="/does_not_exist"} 3 148 | locust_requests_min_response_time{method="GET",name="/stats/requests"} 3 149 | # HELP locust_requests_num_failures 150 | # TYPE locust_requests_num_failures gauge 151 | locust_requests_num_failures{method="",name="Aggregated"} 20099 152 | locust_requests_num_failures{method="GET",name="/"} 0 153 | locust_requests_num_failures{method="GET",name="/does_not_exist"} 20099 154 | locust_requests_num_failures{method="GET",name="/stats/requests"} 0 155 | # HELP locust_requests_num_requests 156 | # TYPE locust_requests_num_requests gauge 157 | locust_requests_num_requests{method="",name="Aggregated"} 60033 158 | locust_requests_num_requests{method="GET",name="/"} 19979 159 | locust_requests_num_requests{method="GET",name="/does_not_exist"} 20099 160 | locust_requests_num_requests{method="GET",name="/stats/requests"} 19955 161 | # HELP locust_running The current state of the execution (0 = STOPPED 1 = HATCHING 2 = RUNNING,). 162 | # TYPE locust_running gauge 163 | locust_running 2 164 | # HELP locust_worker_detail The current status of a worker with user count 165 | # TYPE locust_worker_detail gauge 166 | locust_worker_detail{id="84d64fa91fe7_f501b54dce7f4bb6a80db4e7b677ce51",state="running"} 10 167 | # HELP locust_workers_count The current number of workers. 168 | # TYPE locust_workers_count gauge 169 | locust_workers_count 1 170 | # HELP locust_workers_hatching_count The current number of hatching workers. 171 | # TYPE locust_workers_hatching_count gauge 172 | locust_workers_hatching_count 0 173 | # HELP locust_workers_missing_count The current number of missing workers. 174 | # TYPE locust_workers_missing_count gauge 175 | locust_workers_missing_count 0 176 | # HELP locust_workers_running_count The current number of running workers. 177 | # TYPE locust_workers_running_count gauge 178 | locust_workers_running_count 1 179 | # HELP locust_up The current health status of the server (1 = UP, 0 = DOWN). 180 | # TYPE locust_up gauge 181 | locust_up 1 182 | # HELP locust_users The current number of users. 183 | # TYPE locust_users gauge 184 | locust_users 10 185 | # HELP locustexporter_build_info A metric with a constant '1' value labeled by version, revision, branch, and goversion from which locustexporter was built. 186 | # TYPE locustexporter_build_info gauge 187 | locustexporter_build_info{branch="",goversion="go1.13.8",revision="",version=""} 1 188 | # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. 189 | # TYPE process_cpu_seconds_total counter 190 | process_cpu_seconds_total 86.35 191 | # HELP process_max_fds Maximum number of open file descriptors. 192 | # TYPE process_max_fds gauge 193 | process_max_fds 1.048576e+06 194 | # HELP process_open_fds Number of open file descriptors. 195 | # TYPE process_open_fds gauge 196 | process_open_fds 10 197 | # HELP process_resident_memory_bytes Resident memory size in bytes. 198 | # TYPE process_resident_memory_bytes gauge 199 | process_resident_memory_bytes 2.9392896e+07 200 | # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. 201 | # TYPE process_start_time_seconds gauge 202 | process_start_time_seconds 1.58503749614e+09 203 | # HELP process_virtual_memory_bytes Virtual memory size in bytes. 204 | # TYPE process_virtual_memory_bytes gauge 205 | process_virtual_memory_bytes 1.16871168e+08 206 | # HELP process_virtual_memory_max_bytes Maximum amount of virtual memory available in bytes. 207 | # TYPE process_virtual_memory_max_bytes gauge 208 | process_virtual_memory_max_bytes -1 209 | # HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served. 210 | # TYPE promhttp_metric_handler_requests_in_flight gauge 211 | promhttp_metric_handler_requests_in_flight 1 212 | # HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code. 213 | # TYPE promhttp_metric_handler_requests_total counter 214 | promhttp_metric_handler_requests_total{code="200"} 6771 215 | promhttp_metric_handler_requests_total{code="500"} 0 216 | promhttp_metric_handler_requests_total{code="503"} 0 217 | ``` 218 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 4 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= 8 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 10 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 11 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 12 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 13 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 14 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 19 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 20 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 21 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 22 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 23 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 24 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 25 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 26 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 30 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 31 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 32 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 33 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 34 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 35 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 36 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 37 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 38 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 39 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 41 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 42 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 44 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 45 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 46 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 47 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 48 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 49 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 50 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 51 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 52 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 53 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 54 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 55 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 58 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 59 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 60 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 61 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 64 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 65 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 66 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 67 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 68 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 70 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 71 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 72 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 73 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 74 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 75 | github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= 76 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 77 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 78 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 79 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 80 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 81 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 82 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 83 | github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= 84 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 85 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 86 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 87 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 88 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 89 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 90 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 91 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 92 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 93 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 94 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 95 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 96 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 97 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 98 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 99 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 100 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 101 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 102 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 103 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 104 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 105 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 106 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 107 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 108 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 109 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 110 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 111 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 112 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 113 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 114 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 115 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 117 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 118 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 119 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 120 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 121 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 122 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 123 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= 125 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 126 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 127 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 128 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 129 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 130 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 132 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 133 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 134 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 135 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 136 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 137 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 138 | google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ= 139 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 140 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 141 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 144 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 145 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 146 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 147 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 149 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 150 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 151 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "sync" 13 | "time" 14 | 15 | "github.com/prometheus/client_golang/prometheus" 16 | "github.com/prometheus/client_golang/prometheus/promhttp" 17 | "github.com/prometheus/common/log" 18 | "github.com/prometheus/common/version" 19 | "gopkg.in/alecthomas/kingpin.v2" 20 | ) 21 | 22 | var ( 23 | namespace string 24 | NameSpace *string 25 | ) 26 | 27 | // Exporter structure 28 | type Exporter struct { 29 | uri string 30 | mutex sync.RWMutex 31 | fetch func(endpoint string) (io.ReadCloser, error) 32 | 33 | locustUp, 34 | locustUsers, 35 | locustFailRatio, 36 | locustCurrentResponseTimePercentileNinetyFifth, 37 | locustCurrentResponseTimePercentileFiftieth prometheus.Gauge 38 | locustRunning, 39 | locustWorkersCount, 40 | locustWorkersRunningCount, 41 | locustWorkersHatchingCount, 42 | locustWorkersMissingCount prometheus.Gauge 43 | locustNumRequests, 44 | locustNumFailures, 45 | locustAvgResponseTime, 46 | locustCurrentFailPerSec, 47 | locustWorkersDetail, 48 | locustMinResponseTime, 49 | locustMaxResponseTime, 50 | locustCurrentRps, 51 | locustMedianResponseTime, 52 | locustAvgContentLength, 53 | locustErrors *prometheus.GaugeVec 54 | totalScrapes prometheus.Counter 55 | } 56 | 57 | // NewExporter function 58 | func NewExporter(uri string, timeout time.Duration) (*Exporter, error) { 59 | u, err := url.Parse(uri) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | var fetch func(endpoint string) (io.ReadCloser, error) 65 | switch u.Scheme { 66 | case "http", "https", "file": 67 | fetch = fetchHTTP(uri, timeout) 68 | default: 69 | return nil, fmt.Errorf("unsupported scheme: %q", u.Scheme) 70 | } 71 | 72 | return &Exporter{ 73 | uri: uri, 74 | fetch: fetch, 75 | locustRunning: prometheus.NewGauge( 76 | prometheus.GaugeOpts{ 77 | Namespace: namespace, 78 | Name: "running", 79 | Help: "The current state of the execution (0 = STOPPED 1 = HATCHING 2 = RUNNING,).", 80 | }, 81 | ), 82 | locustUp: prometheus.NewGauge( 83 | prometheus.GaugeOpts{ 84 | Namespace: namespace, 85 | Name: "up", 86 | Help: "The current health status of the server (1 = UP, 0 = DOWN).", 87 | }, 88 | ), 89 | locustUsers: prometheus.NewGauge( 90 | prometheus.GaugeOpts{ 91 | Namespace: namespace, 92 | Name: "users", 93 | Help: "The current number of users.", 94 | }, 95 | ), 96 | locustWorkersCount: prometheus.NewGauge( 97 | prometheus.GaugeOpts{ 98 | Namespace: namespace, 99 | Name: "workers_count", 100 | Help: "The current number of workers.", 101 | }, 102 | ), 103 | locustCurrentResponseTimePercentileNinetyFifth: prometheus.NewGauge( 104 | prometheus.GaugeOpts{ 105 | Namespace: namespace, 106 | Subsystem: "requests", 107 | Name: "current_response_time_percentile_95", 108 | }, 109 | ), 110 | locustCurrentResponseTimePercentileFiftieth: prometheus.NewGauge( 111 | prometheus.GaugeOpts{ 112 | Namespace: namespace, 113 | Subsystem: "requests", 114 | Name: "current_response_time_percentile_50", 115 | }, 116 | ), 117 | locustFailRatio: prometheus.NewGauge( 118 | prometheus.GaugeOpts{ 119 | Namespace: namespace, 120 | Subsystem: "requests", 121 | Name: "fail_ratio", 122 | }, 123 | ), 124 | locustWorkersRunningCount: prometheus.NewGauge( 125 | prometheus.GaugeOpts{ 126 | Namespace: namespace, 127 | Name: "workers_running_count", 128 | Help: "The current number of running workers.", 129 | }, 130 | ), 131 | locustWorkersHatchingCount: prometheus.NewGauge( 132 | prometheus.GaugeOpts{ 133 | Namespace: namespace, 134 | Name: "workers_hatching_count", 135 | Help: "The current number of hatching workers.", 136 | }, 137 | ), 138 | locustWorkersMissingCount: prometheus.NewGauge( 139 | prometheus.GaugeOpts{ 140 | Namespace: namespace, 141 | Name: "workers_missing_count", 142 | Help: "The current number of missing workers.", 143 | }, 144 | ), 145 | locustWorkersDetail: prometheus.NewGaugeVec( 146 | prometheus.GaugeOpts{ 147 | Namespace: namespace, 148 | Subsystem: "worker", 149 | Name: "detail", 150 | Help: "The current status of a worker with user count", 151 | }, 152 | []string{"id", "state"}, 153 | ), 154 | locustNumRequests: prometheus.NewGaugeVec( 155 | prometheus.GaugeOpts{ 156 | Namespace: namespace, 157 | Subsystem: "requests", 158 | Name: "num_requests", 159 | }, 160 | []string{"method", "name"}, 161 | ), 162 | locustNumFailures: prometheus.NewGaugeVec( 163 | prometheus.GaugeOpts{ 164 | Namespace: namespace, 165 | Subsystem: "requests", 166 | Name: "num_failures", 167 | }, 168 | []string{"method", "name"}, 169 | ), 170 | locustAvgResponseTime: prometheus.NewGaugeVec( 171 | prometheus.GaugeOpts{ 172 | Namespace: namespace, 173 | Subsystem: "requests", 174 | Name: "avg_response_time", 175 | }, 176 | []string{"method", "name"}, 177 | ), 178 | locustCurrentFailPerSec: prometheus.NewGaugeVec( 179 | prometheus.GaugeOpts{ 180 | Namespace: namespace, 181 | Subsystem: "requests", 182 | Name: "current_fail_per_sec", 183 | }, 184 | []string{"method", "name"}, 185 | ), 186 | locustMinResponseTime: prometheus.NewGaugeVec( 187 | prometheus.GaugeOpts{ 188 | Namespace: namespace, 189 | Subsystem: "requests", 190 | Name: "min_response_time", 191 | }, 192 | []string{"method", "name"}, 193 | ), 194 | locustMaxResponseTime: prometheus.NewGaugeVec( 195 | prometheus.GaugeOpts{ 196 | Namespace: namespace, 197 | Subsystem: "requests", 198 | Name: "max_response_time", 199 | }, 200 | []string{"method", "name"}, 201 | ), 202 | locustCurrentRps: prometheus.NewGaugeVec( 203 | prometheus.GaugeOpts{ 204 | Namespace: namespace, 205 | Subsystem: "requests", 206 | Name: "current_rps", 207 | }, 208 | []string{"method", "name"}, 209 | ), 210 | locustMedianResponseTime: prometheus.NewGaugeVec( 211 | prometheus.GaugeOpts{ 212 | Namespace: namespace, 213 | Subsystem: "requests", 214 | Name: "median_response_time", 215 | }, 216 | []string{"method", "name"}, 217 | ), 218 | locustAvgContentLength: prometheus.NewGaugeVec( 219 | prometheus.GaugeOpts{ 220 | Namespace: namespace, 221 | Subsystem: "requests", 222 | Name: "avg_content_length", 223 | }, 224 | []string{"method", "name"}, 225 | ), 226 | locustErrors: prometheus.NewGaugeVec( 227 | prometheus.GaugeOpts{ 228 | Namespace: namespace, 229 | Name: "errors", 230 | Help: "The current number of errors.", 231 | }, 232 | []string{"method", "name", "error"}, 233 | ), 234 | totalScrapes: prometheus.NewCounter( 235 | prometheus.CounterOpts{ 236 | Namespace: namespace, 237 | Name: "total_scrapes", 238 | Help: "The total number of scrapes.", 239 | }, 240 | ), 241 | }, nil 242 | } 243 | 244 | // Describe function of Exporter 245 | func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { 246 | e.mutex.Lock() 247 | defer e.mutex.Unlock() 248 | 249 | ch <- e.locustUsers.Desc() 250 | ch <- e.locustWorkersCount.Desc() 251 | ch <- e.locustWorkersRunningCount.Desc() 252 | ch <- e.locustWorkersHatchingCount.Desc() 253 | ch <- e.locustWorkersMissingCount.Desc() 254 | ch <- e.locustUp.Desc() 255 | ch <- e.locustRunning.Desc() 256 | ch <- e.totalScrapes.Desc() 257 | ch <- e.locustFailRatio.Desc() 258 | ch <- e.locustCurrentResponseTimePercentileNinetyFifth.Desc() 259 | ch <- e.locustCurrentResponseTimePercentileFiftieth.Desc() 260 | 261 | e.locustNumRequests.Describe(ch) 262 | e.locustNumFailures.Describe(ch) 263 | e.locustAvgResponseTime.Describe(ch) 264 | e.locustCurrentFailPerSec.Describe(ch) 265 | e.locustMinResponseTime.Describe(ch) 266 | e.locustMaxResponseTime.Describe(ch) 267 | e.locustMedianResponseTime.Describe(ch) 268 | e.locustCurrentRps.Describe(ch) 269 | e.locustAvgContentLength.Describe(ch) 270 | e.locustErrors.Describe(ch) 271 | e.locustWorkersDetail.Describe(ch) 272 | } 273 | 274 | // Collect function of Exporter 275 | func (e *Exporter) Collect(ch chan<- prometheus.Metric) { 276 | e.mutex.Lock() 277 | defer e.mutex.Unlock() 278 | 279 | up := e.scrape(ch) 280 | ch <- prometheus.MustNewConstMetric(e.locustUp.Desc(), prometheus.GaugeValue, up) 281 | e.locustNumRequests.Collect(ch) 282 | e.locustNumFailures.Collect(ch) 283 | e.locustAvgResponseTime.Collect(ch) 284 | e.locustCurrentFailPerSec.Collect(ch) 285 | e.locustMinResponseTime.Collect(ch) 286 | e.locustMaxResponseTime.Collect(ch) 287 | e.locustCurrentRps.Collect(ch) 288 | e.locustMedianResponseTime.Collect(ch) 289 | e.locustAvgContentLength.Collect(ch) 290 | e.locustErrors.Collect(ch) 291 | e.locustWorkersDetail.Collect(ch) 292 | } 293 | 294 | type locustStats struct { 295 | Stats []struct { 296 | Method string `json:"method"` 297 | Name string `json:"name"` 298 | NumRequests int `json:"num_requests"` 299 | NumFailures int `json:"num_failures"` 300 | AvgResponseTime float64 `json:"avg_response_time"` 301 | CurrentFailPerSec float64 `json:"current_fail_per_sec"` 302 | MinResponseTime float64 `json:"min_response_time"` 303 | MaxResponseTime float64 `json:"max_response_time"` 304 | CurrentRps float64 `json:"current_rps"` 305 | MedianResponseTime float64 `json:"median_response_time"` 306 | AvgContentLength float64 `json:"avg_content_length"` 307 | } `json:"stats"` 308 | Errors []struct { 309 | Method string `json:"method"` 310 | Name string `json:"name"` 311 | Error string `json:"error"` 312 | Occurrences int `json:"occurrences"` 313 | } `json:"errors"` 314 | TotalRps float64 `json:"total_rps"` 315 | FailRatio float64 `json:"fail_ratio"` 316 | CurrentResponseTimePercentileNinetyFifth float64 `json:"current_response_time_percentile_95"` 317 | CurrentResponseTimePercentileFiftieth float64 `json:"current_response_time_percentile_50"` 318 | WorkerCount int `json:"worker_count,omitempty"` 319 | State string `json:"state"` 320 | UserCount int `json:"user_count"` 321 | Workers []struct { 322 | Id string `json:"id"` 323 | State string `json:"state"` 324 | UserCount int `json:"user_count"` 325 | } `json:"workers"` 326 | } 327 | 328 | func (e *Exporter) scrape(ch chan<- prometheus.Metric) (up float64) { 329 | e.totalScrapes.Inc() 330 | 331 | var locustStats locustStats 332 | 333 | body, err := e.fetch("/stats/requests") 334 | if err != nil { 335 | log.Errorf("Can't scrape Pack: %v", err) 336 | return 0 337 | } 338 | defer body.Close() 339 | 340 | bodyAll, err := ioutil.ReadAll(body) 341 | if err != nil { 342 | return 0 343 | } 344 | 345 | _ = json.Unmarshal([]byte(bodyAll), &locustStats) 346 | 347 | ch <- prometheus.MustNewConstMetric(e.locustUsers.Desc(), prometheus.GaugeValue, float64(locustStats.UserCount)) 348 | ch <- prometheus.MustNewConstMetric(e.locustFailRatio.Desc(), prometheus.GaugeValue, float64(locustStats.FailRatio)) 349 | ch <- prometheus.MustNewConstMetric(e.locustCurrentResponseTimePercentileNinetyFifth.Desc(), prometheus.GaugeValue, float64(locustStats.CurrentResponseTimePercentileNinetyFifth)) 350 | ch <- prometheus.MustNewConstMetric(e.locustCurrentResponseTimePercentileFiftieth.Desc(), prometheus.GaugeValue, float64(locustStats.CurrentResponseTimePercentileFiftieth)) 351 | ch <- prometheus.MustNewConstMetric(e.locustWorkersCount.Desc(), prometheus.GaugeValue, float64(len(locustStats.Workers))) 352 | ch <- prometheus.MustNewConstMetric(e.locustWorkersRunningCount.Desc(), prometheus.GaugeValue, countWorkersByState(locustStats, "running")) 353 | ch <- prometheus.MustNewConstMetric(e.locustWorkersHatchingCount.Desc(), prometheus.GaugeValue, countWorkersByState(locustStats, "hatching")) 354 | ch <- prometheus.MustNewConstMetric(e.locustWorkersMissingCount.Desc(), prometheus.GaugeValue, countWorkersByState(locustStats, "missing")) 355 | 356 | for _, r := range locustStats.Stats { 357 | if r.Name != "Total" && r.Name != "//stats/requests" { 358 | e.locustNumRequests.WithLabelValues(r.Method, r.Name).Set(float64(r.NumRequests)) 359 | e.locustNumFailures.WithLabelValues(r.Method, r.Name).Set(float64(r.NumFailures)) 360 | e.locustAvgResponseTime.WithLabelValues(r.Method, r.Name).Set(r.AvgResponseTime) 361 | e.locustCurrentFailPerSec.WithLabelValues(r.Method, r.Name).Set(r.CurrentFailPerSec) 362 | e.locustMinResponseTime.WithLabelValues(r.Method, r.Name).Set(r.MinResponseTime) 363 | e.locustMaxResponseTime.WithLabelValues(r.Method, r.Name).Set(r.MaxResponseTime) 364 | e.locustCurrentRps.WithLabelValues(r.Method, r.Name).Set(r.CurrentRps) 365 | e.locustMedianResponseTime.WithLabelValues(r.Method, r.Name).Set(r.MedianResponseTime) 366 | e.locustAvgContentLength.WithLabelValues(r.Method, r.Name).Set(r.AvgContentLength) 367 | } 368 | } 369 | 370 | for _, r := range locustStats.Errors { 371 | e.locustErrors.WithLabelValues(r.Method, r.Name, r.Error).Set(float64(r.Occurrences)) 372 | } 373 | 374 | for _, worker := range locustStats.Workers { 375 | e.locustWorkersDetail.WithLabelValues(worker.Id, worker.State).Set(float64(worker.UserCount)) 376 | } 377 | 378 | var running = 0 //stopped 379 | 380 | if locustStats.State == "hatching" { 381 | running = 1 382 | } else if locustStats.State == "running" { 383 | running = 2 384 | } 385 | 386 | ch <- prometheus.MustNewConstMetric(e.locustRunning.Desc(), prometheus.GaugeValue, float64(running)) 387 | 388 | return 1 389 | } 390 | 391 | func fetchHTTP(uri string, timeout time.Duration) func(endpoint string) (io.ReadCloser, error) { 392 | tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} 393 | client := http.Client{ 394 | Timeout: timeout, 395 | Transport: tr, 396 | } 397 | 398 | return func(endpoint string) (io.ReadCloser, error) { 399 | resp, err := client.Get(uri + endpoint) 400 | if err != nil { 401 | return nil, err 402 | } 403 | if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { 404 | resp.Body.Close() 405 | return nil, fmt.Errorf("HTTP status %d", resp.StatusCode) 406 | } 407 | return resp.Body, nil 408 | } 409 | } 410 | 411 | func countWorkersByState(stats locustStats, state string) float64 { 412 | var count = 0 413 | for _, worker := range stats.Workers { 414 | if worker.State == state { 415 | count++ 416 | } 417 | } 418 | 419 | return float64(count) 420 | } 421 | 422 | func main() { 423 | var ( 424 | listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9646").Envar("LOCUST_EXPORTER_WEB_LISTEN_ADDRESS").String() 425 | metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("LOCUST_EXPORTER_WEB_TELEMETRY_PATH").String() 426 | uri = kingpin.Flag("locust.uri", "URI of Locust.").Default("http://localhost:8089").Envar("LOCUST_EXPORTER_URI").String() 427 | NameSpace = kingpin.Flag("locust.namespace", "Namespace for prometheus metrics.").Default("locust").Envar("LOCUST_METRIC_NAMESPACE").String() 428 | timeout = kingpin.Flag("locust.timeout", "Scrape timeout").Default("5s").Envar("LOCUST_EXPORTER_TIMEOUT").Duration() 429 | ) 430 | 431 | log.AddFlags(kingpin.CommandLine) 432 | kingpin.Version(version.Print("locust_exporter")) 433 | kingpin.HelpFlag.Short('h') 434 | kingpin.Parse() 435 | 436 | namespace = *NameSpace 437 | log.Infoln("Starting locust_exporter", version.Info()) 438 | log.Infoln("Build context", version.BuildContext()) 439 | 440 | exporter, err := NewExporter(*uri, *timeout) 441 | if err != nil { 442 | log.Fatal(err) 443 | } 444 | prometheus.MustRegister(exporter) 445 | prometheus.MustRegister(version.NewCollector("locustexporter")) 446 | 447 | http.Handle(*metricsPath, promhttp.Handler()) 448 | http.HandleFunc("/quitquitquit", func(http.ResponseWriter, *http.Request) { os.Exit(0) }) 449 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 450 | _, _ = w.Write([]byte(`Locust Exporter

Locust Exporter

Metrics

`)) 451 | }) 452 | 453 | log.Infoln("Listening on", *listenAddress) 454 | log.Fatal(http.ListenAndServe(*listenAddress, nil)) 455 | } 456 | -------------------------------------------------------------------------------- /locust_dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [{ 4 | "builtIn": 1, 5 | "datasource": "-- Grafana --", 6 | "enable": true, 7 | "hide": true, 8 | "iconColor": "rgba(0, 211, 255, 1)", 9 | "limit": 100, 10 | "name": "Annotations & Alerts", 11 | "showIn": 0, 12 | "type": "dashboard" 13 | }] 14 | }, 15 | "description": "Locust Exporter - Container Solutions", 16 | "editable": true, 17 | "gnetId": null, 18 | "graphTooltip": 0, 19 | "id": 2, 20 | "links": [], 21 | "panels": [{ 22 | "cacheTimeout": null, 23 | "colorBackground": false, 24 | "colorPostfix": false, 25 | "colorValue": true, 26 | "colors": [ 27 | "rgba(245, 54, 54, 0.9)", 28 | "rgba(237, 129, 40, 0.89)", 29 | "rgba(50, 172, 45, 0.97)" 30 | ], 31 | "datasource": "Prometheus", 32 | "editable": true, 33 | "error": false, 34 | "format": "none", 35 | "gauge": { 36 | "maxValue": 100, 37 | "minValue": 0, 38 | "show": false, 39 | "thresholdLabels": false, 40 | "thresholdMarkers": true 41 | }, 42 | "gridPos": { 43 | "h": 7, 44 | "w": 4, 45 | "x": 0, 46 | "y": 0 47 | }, 48 | "id": 1, 49 | "interval": null, 50 | "links": [], 51 | "mappingType": 1, 52 | "mappingTypes": [{ 53 | "name": "value to text", 54 | "value": 1 55 | }, 56 | { 57 | "name": "range to text", 58 | "value": 2 59 | } 60 | ], 61 | "maxDataPoints": 100, 62 | "nullPointMode": "connected", 63 | "nullText": null, 64 | "postfix": "", 65 | "postfixFontSize": "50%", 66 | "prefix": "", 67 | "prefixFontSize": "50%", 68 | "rangeMaps": [{ 69 | "from": "null", 70 | "text": "N/A", 71 | "to": "null" 72 | }], 73 | "sparkline": { 74 | "fillColor": "rgba(31, 118, 189, 0.18)", 75 | "full": false, 76 | "lineColor": "rgb(31, 120, 193)", 77 | "show": true 78 | }, 79 | "tableColumn": "", 80 | "targets": [{ 81 | "expr": "locust_running", 82 | "interval": "", 83 | "intervalFactor": 2, 84 | "legendFormat": "", 85 | "metric": "", 86 | "refId": "A", 87 | "step": 20 88 | }], 89 | "thresholds": "0,2", 90 | "title": "Locust Status", 91 | "type": "singlestat", 92 | "valueFontSize": "80%", 93 | "valueMaps": [{ 94 | "op": "=", 95 | "text": "Hatching", 96 | "value": "1" 97 | }, 98 | { 99 | "op": "=", 100 | "text": "Stop", 101 | "value": "0" 102 | }, 103 | { 104 | "op": "=", 105 | "text": "Running", 106 | "value": "2" 107 | } 108 | ], 109 | "valueName": "avg" 110 | }, 111 | { 112 | "cacheTimeout": null, 113 | "colorBackground": false, 114 | "colorValue": false, 115 | "colors": [ 116 | "rgba(245, 54, 54, 0.9)", 117 | "rgba(237, 129, 40, 0.89)", 118 | "rgba(50, 172, 45, 0.97)" 119 | ], 120 | "datasource": "Prometheus", 121 | "editable": true, 122 | "error": false, 123 | "format": "none", 124 | "gauge": { 125 | "maxValue": 100, 126 | "minValue": 0, 127 | "show": false, 128 | "thresholdLabels": false, 129 | "thresholdMarkers": true 130 | }, 131 | "gridPos": { 132 | "h": 4, 133 | "w": 4, 134 | "x": 4, 135 | "y": 0 136 | }, 137 | "id": 2, 138 | "interval": null, 139 | "links": [], 140 | "mappingType": 1, 141 | "mappingTypes": [{ 142 | "name": "value to text", 143 | "value": 1 144 | }, 145 | { 146 | "name": "range to text", 147 | "value": 2 148 | } 149 | ], 150 | "maxDataPoints": 100, 151 | "nullPointMode": "connected", 152 | "nullText": null, 153 | "postfix": "", 154 | "postfixFontSize": "50%", 155 | "prefix": "", 156 | "prefixFontSize": "50%", 157 | "rangeMaps": [{ 158 | "from": "null", 159 | "text": "N/A", 160 | "to": "null" 161 | }], 162 | "sparkline": { 163 | "fillColor": "rgba(31, 118, 189, 0.18)", 164 | "full": false, 165 | "lineColor": "rgb(31, 120, 193)", 166 | "show": true 167 | }, 168 | "tableColumn": "", 169 | "targets": [{ 170 | "expr": "locust_users", 171 | "interval": "", 172 | "intervalFactor": 2, 173 | "legendFormat": "", 174 | "refId": "A", 175 | "step": 20 176 | }], 177 | "thresholds": "", 178 | "title": "Swarmed users", 179 | "type": "singlestat", 180 | "valueFontSize": "80%", 181 | "valueMaps": [{ 182 | "op": "=", 183 | "text": "N/A", 184 | "value": "null" 185 | }], 186 | "valueName": "avg" 187 | }, 188 | { 189 | "cacheTimeout": null, 190 | "colorBackground": false, 191 | "colorPostfix": false, 192 | "colorValue": false, 193 | "colors": [ 194 | "rgba(245, 54, 54, 0.9)", 195 | "rgba(237, 129, 40, 0.89)", 196 | "rgba(50, 172, 45, 0.97)" 197 | ], 198 | "datasource": "Prometheus", 199 | "decimals": 2, 200 | "editable": true, 201 | "error": false, 202 | "format": "short", 203 | "gauge": { 204 | "maxValue": 100, 205 | "minValue": 0, 206 | "show": false, 207 | "thresholdLabels": false, 208 | "thresholdMarkers": true 209 | }, 210 | "gridPos": { 211 | "h": 7, 212 | "w": 6, 213 | "x": 8, 214 | "y": 0 215 | }, 216 | "id": 4, 217 | "interval": null, 218 | "links": [], 219 | "mappingType": 1, 220 | "mappingTypes": [{ 221 | "name": "value to text", 222 | "value": 1 223 | }, 224 | { 225 | "name": "range to text", 226 | "value": 2 227 | } 228 | ], 229 | "maxDataPoints": 100, 230 | "nullPointMode": "connected", 231 | "nullText": null, 232 | "postfix": " req/s", 233 | "postfixFontSize": "50%", 234 | "prefix": "", 235 | "prefixFontSize": "50%", 236 | "rangeMaps": [{ 237 | "from": "null", 238 | "text": "N/A", 239 | "to": "null" 240 | }], 241 | "sparkline": { 242 | "fillColor": "rgba(31, 118, 189, 0.18)", 243 | "full": true, 244 | "lineColor": "rgb(31, 120, 193)", 245 | "show": true 246 | }, 247 | "tableColumn": "", 248 | "targets": [{ 249 | "expr": "sum(locust_requests_current_rps)", 250 | "intervalFactor": 2, 251 | "refId": "A", 252 | "step": 20 253 | }], 254 | "thresholds": "", 255 | "title": "Current RPS", 256 | "type": "singlestat", 257 | "valueFontSize": "80%", 258 | "valueMaps": [{ 259 | "op": "=", 260 | "text": "N/A", 261 | "value": "null" 262 | }], 263 | "valueName": "avg" 264 | }, 265 | { 266 | "datasource": null, 267 | "gridPos": { 268 | "h": 4, 269 | "w": 4, 270 | "x": 14, 271 | "y": 0 272 | }, 273 | "id": 18, 274 | "options": { 275 | "colorMode": "value", 276 | "fieldOptions": { 277 | "calcs": [ 278 | "mean" 279 | ], 280 | "defaults": { 281 | "mappings": [], 282 | "thresholds": { 283 | "mode": "absolute", 284 | "steps": [{ 285 | "color": "green", 286 | "value": null 287 | }] 288 | } 289 | }, 290 | "overrides": [], 291 | "values": false 292 | }, 293 | "graphMode": "area", 294 | "justifyMode": "auto", 295 | "orientation": "auto" 296 | }, 297 | "pluginVersion": "6.7.1", 298 | "targets": [{ 299 | "expr": "locust_requests_num_requests{method=~\"\"}", 300 | "interval": "", 301 | "legendFormat": "", 302 | "refId": "A" 303 | }], 304 | "timeFrom": null, 305 | "timeShift": null, 306 | "title": "Requests", 307 | "type": "stat" 308 | }, 309 | { 310 | "datasource": null, 311 | "gridPos": { 312 | "h": 7, 313 | "w": 6, 314 | "x": 18, 315 | "y": 0 316 | }, 317 | "id": 21, 318 | "options": { 319 | "fieldOptions": { 320 | "calcs": [ 321 | "last" 322 | ], 323 | "defaults": { 324 | "mappings": [], 325 | "thresholds": { 326 | "mode": "absolute", 327 | "steps": [{ 328 | "color": "semi-dark-red", 329 | "value": null 330 | }] 331 | }, 332 | "unit": "percentunit" 333 | }, 334 | "overrides": [], 335 | "values": false 336 | }, 337 | "orientation": "auto", 338 | "showThresholdLabels": false, 339 | "showThresholdMarkers": true 340 | }, 341 | "pluginVersion": "6.7.1", 342 | "targets": [{ 343 | "expr": "locust_requests_fail_ratio", 344 | "interval": "", 345 | "legendFormat": "", 346 | "refId": "A" 347 | }], 348 | "timeFrom": null, 349 | "timeShift": null, 350 | "title": "Fails", 351 | "type": "gauge" 352 | }, 353 | { 354 | "cacheTimeout": null, 355 | "colorBackground": false, 356 | "colorValue": false, 357 | "colors": [ 358 | "rgba(245, 54, 54, 0.9)", 359 | "rgba(237, 129, 40, 0.89)", 360 | "rgba(50, 172, 45, 0.97)" 361 | ], 362 | "datasource": "Prometheus", 363 | "editable": true, 364 | "error": false, 365 | "format": "none", 366 | "gauge": { 367 | "maxValue": 100, 368 | "minValue": 0, 369 | "show": false, 370 | "thresholdLabels": false, 371 | "thresholdMarkers": true 372 | }, 373 | "gridPos": { 374 | "h": 3, 375 | "w": 4, 376 | "x": 4, 377 | "y": 4 378 | }, 379 | "id": 3, 380 | "interval": null, 381 | "links": [], 382 | "mappingType": 1, 383 | "mappingTypes": [{ 384 | "name": "value to text", 385 | "value": 1 386 | }, 387 | { 388 | "name": "range to text", 389 | "value": 2 390 | } 391 | ], 392 | "maxDataPoints": 100, 393 | "nullPointMode": "connected", 394 | "nullText": null, 395 | "postfix": "", 396 | "postfixFontSize": "50%", 397 | "prefix": "", 398 | "prefixFontSize": "50%", 399 | "rangeMaps": [{ 400 | "from": "null", 401 | "text": "N/A", 402 | "to": "null" 403 | }], 404 | "sparkline": { 405 | "fillColor": "rgba(31, 118, 189, 0.18)", 406 | "full": false, 407 | "lineColor": "rgb(31, 120, 193)", 408 | "show": true 409 | }, 410 | "tableColumn": "", 411 | "targets": [{ 412 | "expr": "locust_workers_count", 413 | "interval": "", 414 | "intervalFactor": 2, 415 | "legendFormat": "", 416 | "refId": "A", 417 | "step": 20 418 | }], 419 | "thresholds": "", 420 | "title": "Connected workers", 421 | "type": "singlestat", 422 | "valueFontSize": "80%", 423 | "valueMaps": [{ 424 | "op": "=", 425 | "text": "N/A", 426 | "value": "null" 427 | }], 428 | "valueName": "avg" 429 | }, 430 | { 431 | "datasource": null, 432 | "gridPos": { 433 | "h": 3, 434 | "w": 4, 435 | "x": 14, 436 | "y": 4 437 | }, 438 | "id": 19, 439 | "options": { 440 | "colorMode": "value", 441 | "fieldOptions": { 442 | "calcs": [ 443 | "mean" 444 | ], 445 | "defaults": { 446 | "mappings": [], 447 | "thresholds": { 448 | "mode": "absolute", 449 | "steps": [{ 450 | "color": "red", 451 | "value": null 452 | }] 453 | } 454 | }, 455 | "overrides": [], 456 | "values": false 457 | }, 458 | "graphMode": "area", 459 | "justifyMode": "auto", 460 | "orientation": "auto" 461 | }, 462 | "pluginVersion": "6.7.1", 463 | "targets": [{ 464 | "expr": "locust_requests_num_failures{method=~\"\"}", 465 | "interval": "", 466 | "legendFormat": "", 467 | "refId": "A" 468 | }], 469 | "timeFrom": null, 470 | "timeShift": null, 471 | "title": "Failers", 472 | "type": "stat" 473 | }, 474 | { 475 | "columns": [], 476 | "datasource": null, 477 | "fontSize": "100%", 478 | "gridPos": { 479 | "h": 6, 480 | "w": 24, 481 | "x": 0, 482 | "y": 7 483 | }, 484 | "id": 14, 485 | "pageSize": null, 486 | "showHeader": true, 487 | "sort": { 488 | "col": 0, 489 | "desc": true 490 | }, 491 | "styles": [{ 492 | "alias": "Time", 493 | "align": "auto", 494 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 495 | "pattern": "Time", 496 | "type": "hidden" 497 | }, 498 | { 499 | "alias": "Method", 500 | "align": "auto", 501 | "colorMode": null, 502 | "colors": [ 503 | "rgba(245, 54, 54, 0.9)", 504 | "rgba(237, 129, 40, 0.89)", 505 | "rgba(50, 172, 45, 0.97)" 506 | ], 507 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 508 | "decimals": 2, 509 | "mappingType": 1, 510 | "pattern": "method", 511 | "preserveFormat": false, 512 | "thresholds": [], 513 | "type": "string", 514 | "unit": "short" 515 | }, 516 | { 517 | "alias": "URL", 518 | "align": "auto", 519 | "colorMode": null, 520 | "colors": [ 521 | "rgba(245, 54, 54, 0.9)", 522 | "rgba(237, 129, 40, 0.89)", 523 | "rgba(50, 172, 45, 0.97)" 524 | ], 525 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 526 | "decimals": 2, 527 | "mappingType": 1, 528 | "pattern": "name", 529 | "thresholds": [], 530 | "type": "string", 531 | "unit": "short" 532 | }, 533 | { 534 | "alias": "MIN RT", 535 | "align": "", 536 | "colorMode": null, 537 | "colors": [ 538 | "rgba(245, 54, 54, 0.9)", 539 | "rgba(237, 129, 40, 0.89)", 540 | "rgba(50, 172, 45, 0.97)" 541 | ], 542 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 543 | "decimals": 0, 544 | "mappingType": 1, 545 | "pattern": "Value #A", 546 | "thresholds": [], 547 | "type": "number", 548 | "unit": "ms" 549 | }, 550 | { 551 | "alias": "Errors", 552 | "align": "", 553 | "colorMode": null, 554 | "colors": [ 555 | "rgba(245, 54, 54, 0.9)", 556 | "rgba(237, 129, 40, 0.89)", 557 | "rgba(50, 172, 45, 0.97)" 558 | ], 559 | "decimals": 0, 560 | "pattern": "Value #B", 561 | "thresholds": [], 562 | "type": "number", 563 | "unit": "none" 564 | }, 565 | { 566 | "alias": "MAX RT", 567 | "align": "", 568 | "colorMode": null, 569 | "colors": [ 570 | "rgba(245, 54, 54, 0.9)", 571 | "rgba(237, 129, 40, 0.89)", 572 | "rgba(50, 172, 45, 0.97)" 573 | ], 574 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 575 | "decimals": 0, 576 | "mappingType": 1, 577 | "pattern": "Value #C", 578 | "thresholds": [], 579 | "type": "number", 580 | "unit": "ms" 581 | }, 582 | { 583 | "alias": "MEDIAN RT", 584 | "align": "auto", 585 | "colorMode": null, 586 | "colors": [ 587 | "rgba(245, 54, 54, 0.9)", 588 | "rgba(237, 129, 40, 0.89)", 589 | "rgba(50, 172, 45, 0.97)" 590 | ], 591 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 592 | "decimals": 0, 593 | "mappingType": 1, 594 | "pattern": "Value #D", 595 | "thresholds": [], 596 | "type": "number", 597 | "unit": "ms" 598 | }, 599 | { 600 | "alias": "AVG RT", 601 | "align": "auto", 602 | "colorMode": null, 603 | "colors": [ 604 | "rgba(245, 54, 54, 0.9)", 605 | "rgba(237, 129, 40, 0.89)", 606 | "rgba(50, 172, 45, 0.97)" 607 | ], 608 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 609 | "decimals": 2, 610 | "mappingType": 1, 611 | "pattern": "Value #E", 612 | "thresholds": [], 613 | "type": "number", 614 | "unit": "ms" 615 | }, 616 | { 617 | "alias": "Errors Ratio", 618 | "align": "auto", 619 | "colorMode": null, 620 | "colors": [ 621 | "rgba(245, 54, 54, 0.9)", 622 | "rgba(237, 129, 40, 0.89)", 623 | "rgba(50, 172, 45, 0.97)" 624 | ], 625 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 626 | "decimals": 2, 627 | "mappingType": 1, 628 | "pattern": "Value #H", 629 | "thresholds": [], 630 | "type": "number", 631 | "unit": "reqps" 632 | }, 633 | { 634 | "alias": "Requests", 635 | "align": "auto", 636 | "colorMode": null, 637 | "colors": [ 638 | "rgba(245, 54, 54, 0.9)", 639 | "rgba(237, 129, 40, 0.89)", 640 | "rgba(50, 172, 45, 0.97)" 641 | ], 642 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 643 | "decimals": 0, 644 | "mappingType": 1, 645 | "pattern": "Value #F", 646 | "thresholds": [], 647 | "type": "number", 648 | "unit": "none" 649 | }, 650 | { 651 | "alias": "Requests Ratio", 652 | "align": "auto", 653 | "colorMode": null, 654 | "colors": [ 655 | "rgba(245, 54, 54, 0.9)", 656 | "rgba(237, 129, 40, 0.89)", 657 | "rgba(50, 172, 45, 0.97)" 658 | ], 659 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 660 | "decimals": 2, 661 | "mappingType": 1, 662 | "pattern": "Value #G", 663 | "thresholds": [], 664 | "type": "number", 665 | "unit": "reqps" 666 | }, 667 | { 668 | "alias": "Content", 669 | "align": "auto", 670 | "colorMode": null, 671 | "colors": [ 672 | "rgba(245, 54, 54, 0.9)", 673 | "rgba(237, 129, 40, 0.89)", 674 | "rgba(50, 172, 45, 0.97)" 675 | ], 676 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 677 | "decimals": 2, 678 | "mappingType": 1, 679 | "pattern": "Value #I", 680 | "thresholds": [], 681 | "type": "number", 682 | "unit": "bytes" 683 | } 684 | ], 685 | "targets": [{ 686 | "expr": "sum(locust_requests_avg_content_length{method=~\".+\"}) by (method, name)", 687 | "format": "table", 688 | "instant": true, 689 | "interval": "", 690 | "legendFormat": "", 691 | "refId": "I" 692 | }, 693 | { 694 | "expr": "sum(locust_requests_min_response_time{method=~\".+\"}) by (method, name)", 695 | "format": "table", 696 | "instant": true, 697 | "interval": "", 698 | "legendFormat": "", 699 | "refId": "A" 700 | }, 701 | { 702 | "expr": "sum(locust_requests_max_response_time{method=~\".+\"}) by (method, name)", 703 | "format": "table", 704 | "instant": true, 705 | "interval": "", 706 | "legendFormat": "", 707 | "refId": "C" 708 | }, 709 | { 710 | "expr": "sum(locust_requests_avg_response_time{method=~\".+\"}) by (method, name)", 711 | "format": "table", 712 | "instant": true, 713 | "interval": "", 714 | "legendFormat": "", 715 | "refId": "E" 716 | }, 717 | { 718 | "expr": "sum(locust_requests_median_response_time{method=~\".+\"}) by (method, name)", 719 | "format": "table", 720 | "instant": true, 721 | "interval": "", 722 | "legendFormat": "", 723 | "refId": "D" 724 | }, 725 | { 726 | "expr": "sum(locust_requests_num_failures{method=~\".+\"}) by (method, name)", 727 | "format": "table", 728 | "instant": true, 729 | "interval": "", 730 | "legendFormat": "", 731 | "refId": "B" 732 | }, 733 | { 734 | "expr": "sum(locust_requests_current_fail_per_sec{method=~\".+\"}) by (method, name)", 735 | "format": "table", 736 | "instant": true, 737 | "interval": "", 738 | "legendFormat": "", 739 | "refId": "H" 740 | }, 741 | { 742 | "expr": "sum(locust_requests_num_requests{method=~\".+\"}) by (method, name)", 743 | "format": "table", 744 | "instant": true, 745 | "interval": "", 746 | "legendFormat": "", 747 | "refId": "F" 748 | }, 749 | { 750 | "expr": "sum(locust_requests_current_rps{method=~\".+\"}) by (method, name)", 751 | "format": "table", 752 | "instant": true, 753 | "interval": "", 754 | "legendFormat": "", 755 | "refId": "G" 756 | } 757 | ], 758 | "timeFrom": null, 759 | "timeShift": null, 760 | "title": "Endpoints", 761 | "transform": "table", 762 | "type": "table" 763 | }, 764 | { 765 | "columns": [], 766 | "datasource": null, 767 | "fontSize": "100%", 768 | "gridPos": { 769 | "h": 3, 770 | "w": 24, 771 | "x": 0, 772 | "y": 13 773 | }, 774 | "id": 16, 775 | "pageSize": null, 776 | "showHeader": true, 777 | "sort": { 778 | "col": 0, 779 | "desc": true 780 | }, 781 | "styles": [{ 782 | "alias": "Time", 783 | "align": "auto", 784 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 785 | "pattern": "Time", 786 | "type": "hidden" 787 | }, 788 | { 789 | "alias": "Requests", 790 | "align": "auto", 791 | "colorMode": null, 792 | "colors": [ 793 | "rgba(245, 54, 54, 0.9)", 794 | "rgba(237, 129, 40, 0.89)", 795 | "rgba(50, 172, 45, 0.97)" 796 | ], 797 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 798 | "decimals": 0, 799 | "mappingType": 1, 800 | "pattern": "Value", 801 | "thresholds": [], 802 | "type": "number", 803 | "unit": "none" 804 | }, 805 | { 806 | "alias": "Method", 807 | "align": "auto", 808 | "colorMode": null, 809 | "colors": [ 810 | "rgba(245, 54, 54, 0.9)", 811 | "rgba(237, 129, 40, 0.89)", 812 | "rgba(50, 172, 45, 0.97)" 813 | ], 814 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 815 | "decimals": 2, 816 | "mappingType": 1, 817 | "pattern": "method", 818 | "thresholds": [], 819 | "type": "string", 820 | "unit": "short" 821 | }, 822 | { 823 | "alias": "URL", 824 | "align": "auto", 825 | "colorMode": null, 826 | "colors": [ 827 | "rgba(245, 54, 54, 0.9)", 828 | "rgba(237, 129, 40, 0.89)", 829 | "rgba(50, 172, 45, 0.97)" 830 | ], 831 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 832 | "decimals": 2, 833 | "mappingType": 1, 834 | "pattern": "name", 835 | "thresholds": [], 836 | "type": "string", 837 | "unit": "short" 838 | }, 839 | { 840 | "alias": "Error", 841 | "align": "auto", 842 | "colorMode": null, 843 | "colors": [ 844 | "rgba(245, 54, 54, 0.9)", 845 | "rgba(237, 129, 40, 0.89)", 846 | "rgba(50, 172, 45, 0.97)" 847 | ], 848 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 849 | "decimals": 2, 850 | "link": false, 851 | "mappingType": 1, 852 | "pattern": "error", 853 | "thresholds": [], 854 | "type": "string", 855 | "unit": "short" 856 | }, 857 | { 858 | "alias": "", 859 | "align": "left", 860 | "colorMode": null, 861 | "colors": [ 862 | "rgba(245, 54, 54, 0.9)", 863 | "rgba(237, 129, 40, 0.89)", 864 | "rgba(50, 172, 45, 0.97)" 865 | ], 866 | "decimals": 2, 867 | "pattern": "/.*/", 868 | "thresholds": [], 869 | "type": "number", 870 | "unit": "short" 871 | } 872 | ], 873 | "targets": [{ 874 | "expr": "sum(locust_errors) by (method, name, error)", 875 | "format": "table", 876 | "hide": false, 877 | "instant": true, 878 | "interval": "", 879 | "intervalFactor": 1, 880 | "legendFormat": "", 881 | "refId": "A" 882 | }], 883 | "timeFrom": null, 884 | "timeShift": null, 885 | "title": "Errors", 886 | "transform": "table", 887 | "type": "table" 888 | }, 889 | { 890 | "aliasColors": {}, 891 | "bars": false, 892 | "dashLength": 10, 893 | "dashes": false, 894 | "datasource": "Prometheus", 895 | "decimals": 0, 896 | "editable": true, 897 | "error": false, 898 | "fill": 1, 899 | "fillGradient": 0, 900 | "gridPos": { 901 | "h": 8, 902 | "w": 17, 903 | "x": 0, 904 | "y": 16 905 | }, 906 | "hiddenSeries": false, 907 | "id": 6, 908 | "legend": { 909 | "avg": false, 910 | "current": false, 911 | "max": false, 912 | "min": false, 913 | "show": true, 914 | "total": false, 915 | "values": false 916 | }, 917 | "lines": true, 918 | "linewidth": 1, 919 | "links": [], 920 | "nullPointMode": "connected", 921 | "options": { 922 | "dataLinks": [] 923 | }, 924 | "paceLength": 10, 925 | "percentage": false, 926 | "pointradius": 5, 927 | "points": false, 928 | "renderer": "flot", 929 | "seriesOverrides": [{ 930 | "alias": "AVG MAX", 931 | "yaxis": 2 932 | }], 933 | "spaceLength": 10, 934 | "stack": false, 935 | "steppedLine": false, 936 | "targets": [{ 937 | "expr": "avg(locust_requests_max_response_time)", 938 | "intervalFactor": 2, 939 | "legendFormat": "AVG MAX", 940 | "refId": "A", 941 | "step": 2 942 | }, 943 | { 944 | "expr": "avg(locust_requests_min_response_time)", 945 | "intervalFactor": 2, 946 | "legendFormat": "AVG MIN", 947 | "metric": "", 948 | "refId": "B", 949 | "step": 2 950 | }, 951 | { 952 | "expr": "avg(locust_requests_avg_response_time)", 953 | "intervalFactor": 2, 954 | "legendFormat": "AVG AVG", 955 | "metric": "", 956 | "refId": "C", 957 | "step": 2 958 | }, 959 | { 960 | "expr": "avg(locust_requests_median_response_time)", 961 | "intervalFactor": 2, 962 | "legendFormat": "AVG MEDIAN", 963 | "refId": "D", 964 | "step": 2 965 | } 966 | ], 967 | "thresholds": [], 968 | "timeFrom": null, 969 | "timeRegions": [], 970 | "timeShift": null, 971 | "title": "Response Times", 972 | "tooltip": { 973 | "msResolution": false, 974 | "shared": true, 975 | "sort": 0, 976 | "value_type": "individual" 977 | }, 978 | "type": "graph", 979 | "xaxis": { 980 | "buckets": null, 981 | "mode": "time", 982 | "name": null, 983 | "show": true, 984 | "values": [] 985 | }, 986 | "yaxes": [{ 987 | "decimals": 0, 988 | "format": "ms", 989 | "label": null, 990 | "logBase": 1, 991 | "max": null, 992 | "min": null, 993 | "show": true 994 | }, 995 | { 996 | "decimals": 0, 997 | "format": "ms", 998 | "label": null, 999 | "logBase": 1, 1000 | "max": null, 1001 | "min": null, 1002 | "show": true 1003 | } 1004 | ], 1005 | "yaxis": { 1006 | "align": false, 1007 | "alignLevel": null 1008 | } 1009 | }, 1010 | { 1011 | "aliasColors": {}, 1012 | "bars": false, 1013 | "dashLength": 10, 1014 | "dashes": false, 1015 | "datasource": "Prometheus", 1016 | "decimals": 0, 1017 | "editable": true, 1018 | "error": false, 1019 | "fill": 1, 1020 | "fillGradient": 0, 1021 | "gridPos": { 1022 | "h": 8, 1023 | "w": 7, 1024 | "x": 17, 1025 | "y": 16 1026 | }, 1027 | "hiddenSeries": false, 1028 | "id": 15, 1029 | "interval": "", 1030 | "legend": { 1031 | "avg": false, 1032 | "current": false, 1033 | "max": false, 1034 | "min": false, 1035 | "show": true, 1036 | "total": false, 1037 | "values": false 1038 | }, 1039 | "lines": true, 1040 | "linewidth": 1, 1041 | "links": [], 1042 | "nullPointMode": "connected", 1043 | "options": { 1044 | "dataLinks": [] 1045 | }, 1046 | "paceLength": 10, 1047 | "percentage": false, 1048 | "pointradius": 5, 1049 | "points": false, 1050 | "renderer": "flot", 1051 | "seriesOverrides": [{ 1052 | "alias": "AVG MAX", 1053 | "yaxis": 2 1054 | }], 1055 | "spaceLength": 10, 1056 | "stack": false, 1057 | "steppedLine": false, 1058 | "targets": [{ 1059 | "expr": "locust_requests_current_response_time_percentile_95", 1060 | "interval": "", 1061 | "intervalFactor": 2, 1062 | "legendFormat": "P95", 1063 | "refId": "D", 1064 | "step": 2 1065 | }, 1066 | { 1067 | "expr": "locust_requests_current_response_time_percentile_50", 1068 | "interval": "", 1069 | "legendFormat": "P50", 1070 | "refId": "A" 1071 | } 1072 | ], 1073 | "thresholds": [], 1074 | "timeFrom": null, 1075 | "timeRegions": [], 1076 | "timeShift": null, 1077 | "title": "Response Times", 1078 | "tooltip": { 1079 | "msResolution": false, 1080 | "shared": true, 1081 | "sort": 0, 1082 | "value_type": "individual" 1083 | }, 1084 | "type": "graph", 1085 | "xaxis": { 1086 | "buckets": null, 1087 | "mode": "time", 1088 | "name": null, 1089 | "show": true, 1090 | "values": [] 1091 | }, 1092 | "yaxes": [{ 1093 | "decimals": 0, 1094 | "format": "ms", 1095 | "label": null, 1096 | "logBase": 1, 1097 | "max": null, 1098 | "min": null, 1099 | "show": true 1100 | }, 1101 | { 1102 | "decimals": 0, 1103 | "format": "ms", 1104 | "label": null, 1105 | "logBase": 1, 1106 | "max": null, 1107 | "min": null, 1108 | "show": true 1109 | } 1110 | ], 1111 | "yaxis": { 1112 | "align": false, 1113 | "alignLevel": null 1114 | } 1115 | }, 1116 | { 1117 | "aliasColors": {}, 1118 | "bars": false, 1119 | "dashLength": 10, 1120 | "dashes": false, 1121 | "datasource": "Prometheus", 1122 | "decimals": 0, 1123 | "fill": 1, 1124 | "fillGradient": 0, 1125 | "gridPos": { 1126 | "h": 8, 1127 | "w": 12, 1128 | "x": 0, 1129 | "y": 24 1130 | }, 1131 | "hiddenSeries": false, 1132 | "id": 9, 1133 | "legend": { 1134 | "avg": false, 1135 | "current": false, 1136 | "max": false, 1137 | "min": false, 1138 | "show": false, 1139 | "total": false, 1140 | "values": false 1141 | }, 1142 | "lines": true, 1143 | "linewidth": 1, 1144 | "links": [], 1145 | "nullPointMode": "null", 1146 | "options": { 1147 | "dataLinks": [] 1148 | }, 1149 | "paceLength": 10, 1150 | "percentage": false, 1151 | "pointradius": 2, 1152 | "points": false, 1153 | "renderer": "flot", 1154 | "seriesOverrides": [], 1155 | "spaceLength": 10, 1156 | "stack": false, 1157 | "steppedLine": false, 1158 | "targets": [{ 1159 | "expr": "locust_users", 1160 | "format": "time_series", 1161 | "interval": "", 1162 | "intervalFactor": 1, 1163 | "legendFormat": "Users", 1164 | "refId": "A" 1165 | }], 1166 | "thresholds": [], 1167 | "timeFrom": null, 1168 | "timeRegions": [], 1169 | "timeShift": null, 1170 | "title": "Users", 1171 | "tooltip": { 1172 | "shared": true, 1173 | "sort": 0, 1174 | "value_type": "individual" 1175 | }, 1176 | "type": "graph", 1177 | "xaxis": { 1178 | "buckets": null, 1179 | "mode": "time", 1180 | "name": null, 1181 | "show": true, 1182 | "values": [] 1183 | }, 1184 | "yaxes": [{ 1185 | "format": "short", 1186 | "label": null, 1187 | "logBase": 1, 1188 | "max": null, 1189 | "min": null, 1190 | "show": true 1191 | }, 1192 | { 1193 | "decimals": null, 1194 | "format": "short", 1195 | "label": null, 1196 | "logBase": 1, 1197 | "max": null, 1198 | "min": null, 1199 | "show": true 1200 | } 1201 | ], 1202 | "yaxis": { 1203 | "align": false, 1204 | "alignLevel": null 1205 | } 1206 | }, 1207 | { 1208 | "aliasColors": {}, 1209 | "bars": false, 1210 | "dashLength": 10, 1211 | "dashes": false, 1212 | "datasource": "Prometheus", 1213 | "decimals": 0, 1214 | "fill": 1, 1215 | "fillGradient": 0, 1216 | "gridPos": { 1217 | "h": 8, 1218 | "w": 12, 1219 | "x": 12, 1220 | "y": 24 1221 | }, 1222 | "hiddenSeries": false, 1223 | "id": 12, 1224 | "interval": "", 1225 | "legend": { 1226 | "avg": false, 1227 | "current": false, 1228 | "max": false, 1229 | "min": false, 1230 | "show": false, 1231 | "total": false, 1232 | "values": false 1233 | }, 1234 | "lines": true, 1235 | "linewidth": 1, 1236 | "links": [], 1237 | "nullPointMode": "null", 1238 | "options": { 1239 | "dataLinks": [] 1240 | }, 1241 | "paceLength": 10, 1242 | "percentage": false, 1243 | "pointradius": 2, 1244 | "points": false, 1245 | "renderer": "flot", 1246 | "seriesOverrides": [], 1247 | "spaceLength": 10, 1248 | "stack": false, 1249 | "steppedLine": false, 1250 | "targets": [{ 1251 | "expr": "locust_workers_count", 1252 | "format": "time_series", 1253 | "instant": false, 1254 | "interval": "", 1255 | "intervalFactor": 1, 1256 | "legendFormat": "Workers", 1257 | "refId": "A" 1258 | }], 1259 | "thresholds": [], 1260 | "timeFrom": null, 1261 | "timeRegions": [], 1262 | "timeShift": null, 1263 | "title": "Workers", 1264 | "tooltip": { 1265 | "shared": true, 1266 | "sort": 0, 1267 | "value_type": "individual" 1268 | }, 1269 | "type": "graph", 1270 | "xaxis": { 1271 | "buckets": null, 1272 | "mode": "time", 1273 | "name": null, 1274 | "show": true, 1275 | "values": [] 1276 | }, 1277 | "yaxes": [{ 1278 | "decimals": 0, 1279 | "format": "short", 1280 | "label": null, 1281 | "logBase": 1, 1282 | "max": null, 1283 | "min": null, 1284 | "show": true 1285 | }, 1286 | { 1287 | "decimals": 0, 1288 | "format": "short", 1289 | "label": null, 1290 | "logBase": 1, 1291 | "max": null, 1292 | "min": null, 1293 | "show": false 1294 | } 1295 | ], 1296 | "yaxis": { 1297 | "align": false, 1298 | "alignLevel": null 1299 | } 1300 | }, 1301 | { 1302 | "aliasColors": {}, 1303 | "bars": false, 1304 | "dashLength": 10, 1305 | "dashes": false, 1306 | "datasource": "Prometheus", 1307 | "editable": true, 1308 | "error": false, 1309 | "fill": 1, 1310 | "fillGradient": 0, 1311 | "gridPos": { 1312 | "h": 7, 1313 | "w": 24, 1314 | "x": 0, 1315 | "y": 32 1316 | }, 1317 | "hiddenSeries": false, 1318 | "id": 7, 1319 | "legend": { 1320 | "avg": false, 1321 | "current": false, 1322 | "max": false, 1323 | "min": false, 1324 | "show": true, 1325 | "total": false, 1326 | "values": false 1327 | }, 1328 | "lines": true, 1329 | "linewidth": 1, 1330 | "links": [], 1331 | "nullPointMode": "connected", 1332 | "options": { 1333 | "dataLinks": [] 1334 | }, 1335 | "paceLength": 10, 1336 | "percentage": false, 1337 | "pointradius": 5, 1338 | "points": false, 1339 | "renderer": "flot", 1340 | "seriesOverrides": [{ 1341 | "alias": "fail_ratio [%]", 1342 | "yaxis": 2 1343 | }], 1344 | "spaceLength": 10, 1345 | "stack": false, 1346 | "steppedLine": false, 1347 | "targets": [{ 1348 | "expr": "rate(locust_requests_num_requests{method=~\".+\"}[1m])", 1349 | "format": "time_series", 1350 | "interval": "", 1351 | "intervalFactor": 2, 1352 | "legendFormat": "{{ method}} - {{name}}", 1353 | "refId": "A", 1354 | "step": 2 1355 | }, 1356 | { 1357 | "expr": " sum(rate(locust_requests_num_requests{method=~\".+\"}[1m]))", 1358 | "format": "time_series", 1359 | "interval": "", 1360 | "intervalFactor": 1, 1361 | "legendFormat": "total", 1362 | "refId": "C" 1363 | }, 1364 | { 1365 | "expr": "locust_requests_fail_ratio * 100", 1366 | "format": "time_series", 1367 | "interval": "", 1368 | "intervalFactor": 2, 1369 | "legendFormat": "fail_ratio [%]", 1370 | "refId": "B", 1371 | "step": 2 1372 | } 1373 | ], 1374 | "thresholds": [], 1375 | "timeFrom": null, 1376 | "timeRegions": [], 1377 | "timeShift": null, 1378 | "title": "Requests per endpoint / s", 1379 | "tooltip": { 1380 | "msResolution": false, 1381 | "shared": true, 1382 | "sort": 0, 1383 | "value_type": "individual" 1384 | }, 1385 | "type": "graph", 1386 | "xaxis": { 1387 | "buckets": null, 1388 | "mode": "time", 1389 | "name": null, 1390 | "show": true, 1391 | "values": [] 1392 | }, 1393 | "yaxes": [{ 1394 | "format": "reqps", 1395 | "label": null, 1396 | "logBase": 1, 1397 | "max": null, 1398 | "min": null, 1399 | "show": true 1400 | }, 1401 | { 1402 | "format": "percent", 1403 | "label": null, 1404 | "logBase": 1, 1405 | "max": null, 1406 | "min": "0", 1407 | "show": true 1408 | } 1409 | ], 1410 | "yaxis": { 1411 | "align": false, 1412 | "alignLevel": null 1413 | } 1414 | } 1415 | ], 1416 | "refresh": "10s", 1417 | "schemaVersion": 22, 1418 | "style": "dark", 1419 | "tags": [ 1420 | "locust" 1421 | ], 1422 | "templating": { 1423 | "list": [] 1424 | }, 1425 | "time": { 1426 | "from": "now-15m", 1427 | "to": "now" 1428 | }, 1429 | "timepicker": { 1430 | "refresh_intervals": [ 1431 | "5s", 1432 | "10s", 1433 | "30s", 1434 | "1m", 1435 | "5m", 1436 | "15m", 1437 | "30m", 1438 | "1h", 1439 | "2h", 1440 | "1d" 1441 | ], 1442 | "time_options": [ 1443 | "5m", 1444 | "15m", 1445 | "1h", 1446 | "6h", 1447 | "12h", 1448 | "24h", 1449 | "2d", 1450 | "7d", 1451 | "30d" 1452 | ] 1453 | }, 1454 | "timezone": "browser", 1455 | "title": "Locust", 1456 | "uid": "0WllLp6mz", 1457 | "variables": { 1458 | "list": [] 1459 | }, 1460 | "version": 18 1461 | } --------------------------------------------------------------------------------