├── .github └── workflows │ └── build.yml ├── .gitignore ├── .promu.yml ├── LICENSE.txt ├── Makefile ├── Makefile.common ├── README.md ├── VERSION ├── go.mod ├── go.sum ├── main.go └── statscollector.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.18 21 | 22 | - name: Prepare 23 | run: make promu 24 | 25 | - name: Build 26 | run: promu crossbuild -v 27 | 28 | - name: Archive production artifacts 29 | uses: actions/upload-artifact@v3 30 | with: 31 | name: binaries 32 | path: .build/ 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | kamailio_exporter 2 | kamailio_exporter.exe 3 | .pcversion 4 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | verbose: true 2 | go: 3 | version: 1.18 4 | cgo: false 5 | repository: 6 | path: github.com/pascomnet/kamailio_exporter 7 | build: 8 | flags: -a -tags netgo 9 | ldflags: | 10 | -s 11 | -X main.Version={{.Version}} 12 | -X main.Revision={{.Revision}} 13 | -X main.Branch={{.Branch}} 14 | -X main.BuildUser={{user}}@{{host}} 15 | -X main.BuildDate={{date "20060102-15:04:05"}} 16 | tarball: 17 | prefix: . 18 | files: 19 | - LICENSE 20 | crossbuild: 21 | platforms: 22 | - linux/amd64 23 | - linux/arm64 24 | - darwin/amd64 25 | - darwin/arm64 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 pascom GmbH & Co. KG, Thomas Weber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | DOCKER_IMAGE_NAME ?= kamailio-exporter 17 | -------------------------------------------------------------------------------- /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.12.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.39.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kamailio Exporter for Prometheus 2 | 3 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/pascomnet/kamailio_exporter/blob/master/LICENSE) 4 | ![](https://github.com/hashicorp/nomad-driver-podman/workflows/build/badge.svg) 5 | 6 | A [Kamailio](https://www.kamailio.org/) exporter for Prometheus. 7 | It exports a range of core and often used module statistics as well as scripted metrics. 8 | 9 | This project was presented at [Kamailioworld 2019](https://www.kamailioworld.com/k07/) 10 | 11 | [![IMAGE ALT TEXT](http://img.youtube.com/vi/wQ393kz5G_k/0.jpg)](http://www.youtube.com/watch?v=wQ393kz5G_k "Measuring Kamailio") 12 | 13 | 14 | ## Kamailio configuration 15 | 16 | The Exporter needs a [BINRPC](http://kamailio.org/docs/modules/stable/modules/ctl.html) connection to Kamailio. 17 | So you have to load and configure the [CTL](http://kamailio.org/docs/modules/stable/modules/ctl.html) module. 18 | 19 | If you run the Exporter and Kamailio on the same Machine, it's recommended to use a unix socket for the connection. 20 | The path for the socket defaults to "unix:/var/run/kamailio/kamailio_ctl" and can be used out of the box. 21 | 22 | Depending on your deployment, you might want to open a tcp socket on a *private or firewalled* interface. 23 | This allows you in example to run the Exporter as a Sidecar to your Kamailio Container in a Dockerized environment. 24 | 25 | ``` 26 | modparam("ctl", "binrpc", "tcp:192.168.1.10:2046") 27 | ``` 28 | 29 | ## Running the Exporter 30 | 31 | Download or build the kamailio_exporter binary and start it. If you do so, it'll try to reach Kamailio on the default 32 | unix domain socket /var/run/kamailio/kamailio_ctl. Metrics are exposed on all available interfaces on port 9494 and 33 | http path "/metrics". 34 | 35 | ### Options 36 | 37 | You can configure some things by using command line options or docker/systemd-friendly environment variables: 38 | 39 | #### Connecting to Kamailio 40 | 41 | * --socketPath=/some/path : Path to Kamailio unix domain socket (default: "/var/run/kamailio/kamailio_ctl") (env variable: SOCKET_PATH) 42 | * --host=1.2.3.4 : Kamailio ip or hostname. Domain socket is used if no host is defined. (env variable: HOST) 43 | * --port=3012 : Kamailio port (default: 3012) (env variable: PORT) 44 | * --rtpmetricsPath="" : Listen on this http scrape path to expose rtpengine metrics if the value is non empty 45 | (env variable: RTPMETRICS_PATH). 46 | However there is not yet an option available to set the adress and port of rtpengine, it's currently hardcoded to 47 | http://127.0.0.1:9901/metrics 48 | * --customKamailioMetricsURL="" : URL to query kamailio xhttp_prom metrics and add them to the returned metrics 49 | (env variable: CUSTOM_KAMAILIO_METRICS_URL). 50 | 51 | 52 | 53 | #### Expose metrics via http 54 | 55 | * --bindIp=127.0.0.1 : Listen on this ip for scrape requests (default: "0.0.0.0") (env variable: BIND_IP) 56 | * --bindPort=9494 : Listen on this port for scrape requests (default: 9494) (env variable: BIND_PORT) 57 | * --metricsPath=/metrics : The http scrape path (default: "/metrics") (env variable: METRICS_PATH) 58 | 59 | #### Misc 60 | 61 | * --debug : Enable debug logging (env variable: DEBUG) 62 | 63 | ## Exported core and module metrics 64 | 65 | Metrics are generated by running "stats.fetch all" RPC call. 66 | Then a series of defined stats from core, shm, sl, tcp and tmx are turned into metrics. 67 | 68 | ``` 69 | # HELP kamailio_bad_msg_hdr Messages with bad message header 70 | # TYPE kamailio_bad_msg_hdr counter 71 | kamailio_bad_msg_hdr 0 72 | # HELP kamailio_bad_uri_total Messages with bad uri 73 | # TYPE kamailio_bad_uri_total counter 74 | kamailio_bad_uri_total 0 75 | # HELP kamailio_core_rcv_reply_total Received replies by code 76 | # TYPE kamailio_core_rcv_reply_total counter 77 | kamailio_core_rcv_reply_total{code="18x"} 0 78 | kamailio_core_rcv_reply_total{code="1xx"} 0 79 | kamailio_core_rcv_reply_total{code="2xx"} 0 80 | kamailio_core_rcv_reply_total{code="3xx"} 0 81 | kamailio_core_rcv_reply_total{code="401"} 0 82 | kamailio_core_rcv_reply_total{code="404"} 0 83 | kamailio_core_rcv_reply_total{code="407"} 0 84 | kamailio_core_rcv_reply_total{code="480"} 0 85 | kamailio_core_rcv_reply_total{code="486"} 0 86 | kamailio_core_rcv_reply_total{code="4xx"} 0 87 | kamailio_core_rcv_reply_total{code="5xx"} 0 88 | kamailio_core_rcv_reply_total{code="6xx"} 0 89 | # HELP kamailio_core_rcv_request_total Received requests by method 90 | # TYPE kamailio_core_rcv_request_total counter 91 | kamailio_core_rcv_request_total{method="ack"} 0 92 | kamailio_core_rcv_request_total{method="bye"} 0 93 | kamailio_core_rcv_request_total{method="cancel"} 0 94 | kamailio_core_rcv_request_total{method="info"} 0 95 | kamailio_core_rcv_request_total{method="invite"} 0 96 | kamailio_core_rcv_request_total{method="message"} 0 97 | kamailio_core_rcv_request_total{method="notify"} 0 98 | kamailio_core_rcv_request_total{method="options"} 0 99 | kamailio_core_rcv_request_total{method="prack"} 0 100 | kamailio_core_rcv_request_total{method="publish"} 0 101 | kamailio_core_rcv_request_total{method="refer"} 0 102 | kamailio_core_rcv_request_total{method="register"} 0 103 | kamailio_core_rcv_request_total{method="subscribe"} 0 104 | kamailio_core_rcv_request_total{method="unsupported"} 0 105 | kamailio_core_rcv_request_total{method="update"} 0 106 | # HELP kamailio_core_reply_total Reply counters 107 | # TYPE kamailio_core_reply_total counter 108 | kamailio_core_reply_total{type="drop"} 0 109 | kamailio_core_reply_total{type="err"} 0 110 | kamailio_core_reply_total{type="fwd"} 0 111 | kamailio_core_reply_total{type="rcv"} 0 112 | # HELP kamailio_core_request_total Request counters 113 | # TYPE kamailio_core_request_total counter 114 | kamailio_core_request_total{method="drop"} 0 115 | kamailio_core_request_total{method="err"} 0 116 | kamailio_core_request_total{method="fwd"} 0 117 | kamailio_core_request_total{method="rcv"} 0 118 | # HELP kamailio_dns_failed_request_total Failed dns requests 119 | # TYPE kamailio_dns_failed_request_total counter 120 | kamailio_dns_failed_request_total 0 121 | # HELP kamailio_shm_bytes Shared memory sizes 122 | # TYPE kamailio_shm_bytes gauge 123 | kamailio_shm_bytes{type="free"} 6.3184376e+07 124 | kamailio_shm_bytes{type="max_used"} 3.924488e+06 125 | kamailio_shm_bytes{type="real_used"} 3.924488e+06 126 | kamailio_shm_bytes{type="total"} 6.7108864e+07 127 | kamailio_shm_bytes{type="used"} 3.030808e+06 128 | # HELP kamailio_shm_fragments Shared memory fragment count 129 | # TYPE kamailio_shm_fragments gauge 130 | kamailio_shm_fragments 1 131 | # HELP kamailio_sl_reply_total Stateless replies by code 132 | # TYPE kamailio_sl_reply_total counter 133 | kamailio_sl_reply_total{code="1xx"} 0 134 | kamailio_sl_reply_total{code="200"} 0 135 | kamailio_sl_reply_total{code="202"} 0 136 | kamailio_sl_reply_total{code="2xx"} 0 137 | kamailio_sl_reply_total{code="300"} 0 138 | kamailio_sl_reply_total{code="301"} 0 139 | kamailio_sl_reply_total{code="302"} 0 140 | kamailio_sl_reply_total{code="3xx"} 0 141 | kamailio_sl_reply_total{code="400"} 0 142 | kamailio_sl_reply_total{code="401"} 0 143 | kamailio_sl_reply_total{code="403"} 0 144 | kamailio_sl_reply_total{code="404"} 0 145 | kamailio_sl_reply_total{code="407"} 0 146 | kamailio_sl_reply_total{code="408"} 0 147 | kamailio_sl_reply_total{code="483"} 0 148 | kamailio_sl_reply_total{code="4xx"} 0 149 | kamailio_sl_reply_total{code="500"} 0 150 | kamailio_sl_reply_total{code="5xx"} 0 151 | kamailio_sl_reply_total{code="6xx"} 0 152 | # HELP kamailio_sl_type_total Stateless replies by type 153 | # TYPE kamailio_sl_type_total counter 154 | kamailio_sl_type_total{type="failure"} 0 155 | kamailio_sl_type_total{type="received_ack"} 0 156 | kamailio_sl_type_total{type="sent_err_reply"} 0 157 | kamailio_sl_type_total{type="sent_reply"} 0 158 | kamailio_sl_type_total{type="xxx_reply"} 0 159 | # HELP kamailio_tcp_connections Opened TCP connections 160 | # TYPE kamailio_tcp_connections gauge 161 | kamailio_tcp_connections 0 162 | # HELP kamailio_tcp_total TCP connection counters 163 | # TYPE kamailio_tcp_total counter 164 | kamailio_tcp_total{type="con_reset"} 0 165 | kamailio_tcp_total{type="con_timeout"} 0 166 | kamailio_tcp_total{type="connect_failed"} 0 167 | kamailio_tcp_total{type="connect_success"} 0 168 | kamailio_tcp_total{type="established"} 0 169 | kamailio_tcp_total{type="local_reject"} 0 170 | kamailio_tcp_total{type="passive_open"} 0 171 | kamailio_tcp_total{type="send_timeout"} 0 172 | kamailio_tcp_total{type="sendq_full"} 0 173 | # HELP kamailio_tcp_writequeue TCP write queue size 174 | # TYPE kamailio_tcp_writequeue gauge 175 | kamailio_tcp_writequeue 0 176 | # HELP kamailio_tmx Ongoing Transactions 177 | # TYPE kamailio_tmx gauge 178 | kamailio_tmx{type="active"} 0 179 | kamailio_tmx{type="inuse"} 0 180 | # HELP kamailio_tmx_code_total Completed Transaction counters by code 181 | # TYPE kamailio_tmx_code_total counter 182 | kamailio_tmx_code_total{code="2xx"} 0 183 | kamailio_tmx_code_total{code="3xx"} 0 184 | kamailio_tmx_code_total{code="4xx"} 0 185 | kamailio_tmx_code_total{code="5xx"} 0 186 | kamailio_tmx_code_total{code="6xx"} 0 187 | # HELP kamailio_tmx_rpl_total Tmx reply counters 188 | # TYPE kamailio_tmx_rpl_total counter 189 | kamailio_tmx_rpl_total{type="absorbed"} 0 190 | kamailio_tmx_rpl_total{type="generated"} 0 191 | kamailio_tmx_rpl_total{type="received"} 0 192 | kamailio_tmx_rpl_total{type="relayed"} 0 193 | kamailio_tmx_rpl_total{type="sent"} 0 194 | # HELP kamailio_tmx_type_total Completed Transaction counters by type 195 | # TYPE kamailio_tmx_type_total counter 196 | kamailio_tmx_type_total{type="uac"} 0 197 | kamailio_tmx_type_total{type="uas"} 0 198 | ``` 199 | ## Pkg / Private memory metrics 200 | 201 | A series of metrics is exported for each Kamailio child process: 202 | 203 | ``` 204 | # HELP kamilio_pkgmem_frags Private memory total frags 205 | kamilio_pkgmem_frags{entry="0"} 5 206 | # HELP kamilio_pkgmem_free Private memory free 207 | kamilio_pkgmem_free{entry="0"} 529136 208 | # HELP kamilio_pkgmem_real Private memory real used 209 | kamilio_pkgmem_real{entry="0"} 890336 210 | # HELP kamilio_pkgmem_size Private memory total size 211 | kamilio_pkgmem_size{entry="0"} 8.388608e+06 212 | # HELP kamilio_pkgmem_used Private memory used 213 | kamilio_pkgmem_used{entry="0"} 529136 214 | ``` 215 | ## Kamailio xhttp_prom metrics 216 | If your kamailio version supports it and is configured correctly, 217 | the exporter can query kamailio's xhttp_prom metrics and combine them with the other metrics generated by this exporter. 218 | See `--customKamailioMetricsURL`. 219 | 220 | This allows you to configure and fill custom counters and gauges using the builtin prometheus functions in Kamailio, with labels. 221 | 222 | If you want to use it, you need to enable and configure the `xhttp` and `xhttp_prom` modules in Kamailio. See [Kamailio module docs](https://kamailio.org/docs/modules/devel/modules/xhttp_prom.html) as a starting point. Be careful when setting the `xhttp_prom_stats` parameter. You might get unwanted results if you expose too many native metrics. 223 | 224 | ## TCP/TLS details 225 | 226 | Some additional values from `core.tcp_info`: 227 | 228 | ``` 229 | # HELP kamailio_tcp_max_connections TCP connection limit 230 | kamailio_tcp_max_connections 16384 231 | # HELP kamailio_tcp_readers TCP readers 232 | kamailio_tcp_readers 8 233 | # HELP kamailio_tls_connections Opened TLS connections 234 | kamailio_tls_connections 0 235 | # HELP kamailio_tls_max_connections TLS connection limit 236 | kamailio_tls_max_connections 16384 237 | ``` 238 | ## RTPEngine connection state 239 | ``` 240 | # HELP kamailio_rtpengine_enabled rtpengine connection status- 241 | kamailio_rtpengine_enabled{index="0",set="0",url="udp:127.0.0.1:22222",weight="1"} 1 242 | ``` 243 | ## Scripted metrics 244 | 245 | Often you might want to record some values from your own business logic. As usual in the Kamailio ecosystem, 246 | there is already a module for this purpose: [statistics](http://kamailio.org/docs/modules/stable/modules/statistics.html) 247 | 248 | Statistics Module can be used both from Kamailio native scripts and all KEMI Languages, e.g. Lua or Python. 249 | 250 | Configuration and usage is quite simple. Of course you need to load it: 251 | 252 | ``` 253 | loadmodule "statistics.so" 254 | ``` 255 | 256 | Next, all to-be-exported metrics have to be declared as statistic variable: 257 | 258 | ``` 259 | modparam("statistics", "variable", "my_custom_value_total") 260 | ``` 261 | 262 | Finally, in some route block, you have to populate the statistic variable with a value: 263 | 264 | ``` 265 | update_stat("my_custom_value_total", "+1"); 266 | ``` 267 | 268 | A scraped metric will look like this: 269 | 270 | ``` 271 | # HELP kamailio_my_custom_value_totalScripted metric my_custom_value_total 272 | # TYPE kamailio_my_custom_value_total counter 273 | kamailio_my_custom_value_total 1 274 | ``` 275 | 276 | ### Scripted metric details 277 | 278 | * the statistic variable name is prefixed by "kamailio_" and changed to lower-case 279 | * a suffix of "_total", "_seconds" or "_bytes" will export a Prometheus Counter, omitting the suffix produces a Prometheus Gauge, see [metric types](https://prometheus.io/docs/concepts/metric_types/). 280 | 281 | ## Building 282 | 283 | Kamailio Exporter uses the [go module system](https://github.com/golang/go/wiki/Modules) and thus the minimum go version is 1.11. 284 | Building the binary is straight forward: 285 | 286 | 1. clone or download the source code 287 | 1. change directory 288 | 1. go build -o kamailio_exporter 289 | 290 | 291 | ## Acknowledgements 292 | 293 | Kudos to Florent Chauveau for his golang BINRPC implementation: https://github.com/florentchauveau/go-kamailio-binrpc. 294 | Also noteworthy is, that he provides an alternative implementation for scraping Kamailio statistics: https://github.com/florentchauveau/kamailio_exporter 295 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pascomnet/kamailio_exporter 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/florentchauveau/go-kamailio-binrpc/v3 v3.2.0 7 | github.com/prometheus/client_golang v1.12.1 8 | github.com/prometheus/client_model v0.2.0 9 | github.com/prometheus/common v0.32.1 10 | github.com/sirupsen/logrus v1.8.1 11 | gopkg.in/urfave/cli.v1 v1.20.0 12 | ) 13 | 14 | require ( 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 17 | github.com/golang/protobuf v1.5.2 // indirect 18 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 19 | github.com/prometheus/procfs v0.7.3 // indirect 20 | golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf // indirect 21 | google.golang.org/protobuf v1.27.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 41 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 42 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 46 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 47 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 48 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 49 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 50 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 51 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 52 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 53 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 58 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 59 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 60 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 61 | github.com/florentchauveau/go-kamailio-binrpc/v3 v3.2.0 h1:0IR+Ck/QKC9aIarfNVQdKzDZwF7OP8ekFM8M7ZHb6UY= 62 | github.com/florentchauveau/go-kamailio-binrpc/v3 v3.2.0/go.mod h1:6g5QZzU9yUJao66NQsR2G7Jbo5MU2s/GZWRbqx6lgNc= 63 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 64 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 65 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 66 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 67 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 68 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 69 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 70 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 71 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 72 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 73 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 74 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 75 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 76 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 77 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 78 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 79 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 80 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 81 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 82 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 83 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 84 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 88 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 89 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 90 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 91 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 92 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 93 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 94 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 95 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 96 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 97 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 98 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 99 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 100 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 101 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 102 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 103 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 104 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 105 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 106 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 107 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 108 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 110 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 111 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 112 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 113 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 114 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 115 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 116 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 117 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 118 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 119 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 120 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 121 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 122 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 123 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 124 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 125 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 126 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 127 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 128 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 129 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 130 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 131 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 132 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 133 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 134 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 135 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 136 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 137 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 138 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 139 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 140 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 141 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 142 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 143 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 144 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 145 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 146 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 147 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 148 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 149 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 150 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 151 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 152 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 153 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 154 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 155 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 156 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 157 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 158 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 159 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 160 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 161 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 162 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 163 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 164 | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= 165 | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 166 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 167 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 168 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 169 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 170 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 171 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 172 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 173 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 174 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 175 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 176 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 177 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 178 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 179 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 180 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= 181 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 182 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 183 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 184 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 185 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 186 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 187 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 188 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 189 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 190 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 191 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 192 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 193 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 194 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 195 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 196 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 197 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 198 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 199 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 200 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 201 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 202 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 203 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 204 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 205 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 206 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 207 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 208 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 209 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 210 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 211 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 212 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 213 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 214 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 215 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 216 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 217 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 218 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 219 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 220 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 221 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 222 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 223 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 224 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 225 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 226 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 227 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 228 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 229 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 230 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 231 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 232 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 233 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 234 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 235 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 236 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 237 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 238 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 239 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 240 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 241 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 242 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 243 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 244 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 245 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 246 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 247 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 248 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 249 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 250 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 252 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 253 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 254 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 255 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 256 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 257 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 258 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 259 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 260 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 261 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 262 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 263 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 264 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 265 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 266 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 267 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 268 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 269 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 270 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 271 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 272 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 273 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 274 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 279 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 280 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 281 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 283 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 284 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 285 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 286 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 287 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 318 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 319 | golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf h1:Fm4IcnUL803i92qDlmB0obyHmosDrxZWxJL3gIeNqOw= 320 | golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 321 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 322 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 323 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 324 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 325 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 326 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 327 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 328 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 329 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 330 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 331 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 332 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 333 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 334 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 335 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 336 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 337 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 338 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 339 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 340 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 341 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 342 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 343 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 344 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 345 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 346 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 347 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 348 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 349 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 350 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 351 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 352 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 353 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 354 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 355 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 356 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 357 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 358 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 359 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 360 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 361 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 362 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 363 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 364 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 365 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 366 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 367 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 368 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 369 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 370 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 371 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 372 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 373 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 374 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 375 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 376 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 377 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 378 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 379 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 380 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 381 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 382 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 383 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 384 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 385 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 386 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 387 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 388 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 389 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 390 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 391 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 392 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 393 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 394 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 395 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 396 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 397 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 398 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 399 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 400 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 401 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 402 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 403 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 404 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 405 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 406 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 407 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 408 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 409 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 410 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 411 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 412 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 413 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 414 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 415 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 416 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 417 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 418 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 419 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 420 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 421 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 422 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 423 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 424 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 425 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 426 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 427 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 428 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 429 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 430 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 431 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 432 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 433 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 434 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 435 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 436 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 437 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 438 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 439 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 440 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 441 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 442 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 443 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 444 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 445 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 446 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 447 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 448 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 449 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 450 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 451 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 452 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 453 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 454 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 455 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 456 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 457 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 458 | gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= 459 | gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= 460 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 461 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 462 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 463 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 464 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 465 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 466 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 467 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 468 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 469 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 470 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 471 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 472 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 473 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 474 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 475 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 476 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 477 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2021 Thomas Weber, pascom GmbH & Co. Kg 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package main 23 | 24 | import ( 25 | "bytes" 26 | "fmt" 27 | "io" 28 | "net/http" 29 | "os" 30 | 31 | "github.com/prometheus/client_golang/prometheus" 32 | "github.com/prometheus/client_golang/prometheus/promhttp" 33 | dto "github.com/prometheus/client_model/go" 34 | "github.com/prometheus/common/expfmt" 35 | log "github.com/sirupsen/logrus" 36 | "gopkg.in/urfave/cli.v1" 37 | ) 38 | 39 | var Version string 40 | 41 | func main() { 42 | app := cli.NewApp() 43 | app.Name = "Kamailio exporter" 44 | app.Usage = "Expose Kamailio statistics as http endpoint for prometheus." 45 | app.Version = Version 46 | // define cli flags 47 | app.Flags = []cli.Flag{ 48 | cli.BoolFlag{ 49 | Name: "debug", 50 | Usage: "Enable debug logging", 51 | EnvVar: "DEBUG", 52 | }, 53 | cli.StringFlag{ 54 | Name: "socketPath", 55 | Value: "/var/run/kamailio/kamailio_ctl", 56 | Usage: "Path to Kamailio unix domain socket", 57 | EnvVar: "SOCKET_PATH", 58 | }, 59 | cli.StringFlag{ 60 | Name: "host", 61 | Usage: "Kamailio ip or hostname. Domain socket is used if no host is defined.", 62 | EnvVar: "HOST", 63 | }, 64 | cli.IntFlag{ 65 | Name: "port", 66 | Value: 3012, 67 | Usage: "Kamailio port", 68 | EnvVar: "PORT", 69 | }, 70 | cli.StringFlag{ 71 | Name: "bindIp", 72 | Value: "0.0.0.0", 73 | Usage: "Listen on this ip for scrape requests", 74 | EnvVar: "BIND_IP", 75 | }, 76 | cli.IntFlag{ 77 | Name: "bindPort", 78 | Value: 9494, 79 | Usage: "Listen on this port for scrape requests", 80 | EnvVar: "BIND_PORT", 81 | }, 82 | cli.StringFlag{ 83 | Name: "metricsPath", 84 | Value: "/metrics", 85 | Usage: "The http scrape path", 86 | EnvVar: "METRICS_PATH", 87 | }, 88 | cli.StringFlag{ 89 | Name: "rtpmetricsPath", 90 | Value: "", 91 | Usage: "The http scrape path for rtpengine metrics", 92 | EnvVar: "RTPMETRICS_PATH", 93 | }, 94 | cli.StringFlag{ 95 | Name: "customKamailioMetricsURL", 96 | Value: "", 97 | Usage: "URL to request user defined metrics from kamailio", 98 | EnvVar: "CUSTOM_KAMAILIO_METRICS_URL", 99 | }, 100 | } 101 | app.Action = appAction 102 | // then start the application 103 | err := app.Run(os.Args) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | } 108 | 109 | // start the application 110 | func appAction(c *cli.Context) error { 111 | log.Info("Starting kamailio exporter") 112 | 113 | if c.Bool("debug") { 114 | log.SetLevel(log.DebugLevel) 115 | log.Debug("Debug logging is enabled") 116 | } 117 | 118 | // create a collector 119 | collector, err := NewStatsCollector(c) 120 | if err != nil { 121 | return err 122 | } 123 | // and register it in prometheus API 124 | prometheus.MustRegister(collector) 125 | 126 | metricsPath := c.String("metricsPath") 127 | listenAddress := fmt.Sprintf("%s:%d", c.String("bindIp"), c.Int("bindPort")) 128 | // wire "/" to return some helpful info 129 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 130 | w.Write([]byte(` 131 | Kamailio Exporter 132 | 133 |

This is a prometheus metric exporter for Kamailio.

134 |

Browse ` + metricsPath + ` 135 | to get the metrics.

136 | 137 | `)) 138 | }) 139 | rtpmetricsPath := c.String("rtpmetricsPath") 140 | if rtpmetricsPath != "" { 141 | log.Info("Enabling rtp metrics @", rtpmetricsPath) 142 | http.HandleFunc(rtpmetricsPath, func(w http.ResponseWriter, r *http.Request) { 143 | resp, err := http.Get("http://127.0.0.1:9901/metrics") 144 | if err != nil { 145 | log.Error(err) 146 | http.Error(w, 147 | fmt.Sprintf("Failed to connect to rtpengine: %s", err.Error()), 148 | http.StatusServiceUnavailable) 149 | return 150 | } 151 | defer resp.Body.Close() 152 | resp2, err := io.ReadAll(resp.Body) 153 | if err != nil { 154 | log.Error(err) 155 | http.Error(w, 156 | fmt.Sprintf("Failed to read response from rtpengine: %s", err.Error()), 157 | http.StatusInternalServerError) 158 | return 159 | } 160 | w.Write(resp2) 161 | }) 162 | } 163 | 164 | if customMetricsURL := c.String("customKamailioMetricsURL"); customMetricsURL != "" { 165 | http.Handle(metricsPath, handlerWithUserDefinedMetrics(customMetricsURL)) 166 | } else { 167 | http.Handle(metricsPath, promhttp.Handler()) 168 | } 169 | 170 | // start http server 171 | log.Info("Listening on ", listenAddress, metricsPath) 172 | return http.ListenAndServe(listenAddress, nil) 173 | } 174 | 175 | // Request user defined metrics and parse them into proper data objects 176 | func gatherUserDefinedMetrics(url string) ([]*dto.MetricFamily, error) { 177 | resp, err := http.Get(url) 178 | if err != nil { 179 | log.Error("Failed to query kamailio user defined metrics", err) 180 | return nil, err 181 | } else if resp.StatusCode != http.StatusOK { 182 | resp.Body.Close() 183 | log.Errorf("Requesting user defined kamailio metrics returned status code: %v", resp.StatusCode) 184 | return nil, err 185 | } 186 | 187 | defer resp.Body.Close() 188 | respBytes, err := io.ReadAll(resp.Body) 189 | if err != nil { 190 | log.Error("Failed to read kamailio user defined metrics", err) 191 | return nil, err 192 | } 193 | 194 | parser := expfmt.TextParser{} 195 | parsed, err := parser.TextToMetricFamilies(bytes.NewReader(respBytes)) 196 | if err != nil { 197 | return nil, err 198 | } 199 | 200 | result := []*dto.MetricFamily{} 201 | for _, mf := range parsed { 202 | result = append(result, mf) 203 | } 204 | 205 | return result, nil 206 | } 207 | 208 | func handlerWithUserDefinedMetrics(userDefinedMetricsURL string) http.Handler { 209 | gatherer := func() ([]*dto.MetricFamily, error) { 210 | ours, err := prometheus.DefaultGatherer.Gather() 211 | if err != nil { 212 | return ours, err 213 | } 214 | theirs, err := gatherUserDefinedMetrics(userDefinedMetricsURL) 215 | if err != nil { 216 | log.Error("Scraping user defined metrics failed", err) 217 | return ours, nil 218 | } 219 | return append(ours, theirs...), nil 220 | } 221 | 222 | // defaults like promhttp.Handler(), except using our own gatherer 223 | return promhttp.InstrumentMetricHandler( 224 | prometheus.DefaultRegisterer, 225 | promhttp.HandlerFor(prometheus.GathererFunc(gatherer), promhttp.HandlerOpts{})) 226 | } 227 | -------------------------------------------------------------------------------- /statscollector.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2021 Thomas Weber, pascom GmbH & Co. Kg 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "net" 27 | "strconv" 28 | "strings" 29 | 30 | binrpc "github.com/florentchauveau/go-kamailio-binrpc/v3" 31 | "github.com/prometheus/client_golang/prometheus" 32 | log "github.com/sirupsen/logrus" 33 | "gopkg.in/urfave/cli.v1" 34 | ) 35 | 36 | // declare a series of prometheus metric descriptions 37 | // we can reuse them for each scrape 38 | var ( 39 | core_request_total = prometheus.NewDesc( 40 | "kamailio_core_request_total", 41 | "Request counters", 42 | []string{"method"}, nil) 43 | 44 | core_rcv_request_total = prometheus.NewDesc( 45 | "kamailio_core_rcv_request_total", 46 | "Received requests by method", 47 | []string{"method"}, nil) 48 | 49 | core_reply_total = prometheus.NewDesc( 50 | "kamailio_core_reply_total", 51 | "Reply counters", 52 | []string{"type"}, nil) 53 | 54 | core_rcv_reply_total = prometheus.NewDesc( 55 | "kamailio_core_rcv_reply_total", 56 | "Received replies by code", 57 | []string{"code"}, nil) 58 | 59 | shmem_bytes = prometheus.NewDesc( 60 | "kamailio_shm_bytes", 61 | "Shared memory sizes", 62 | []string{"type"}, nil) 63 | 64 | shmem_fragments = prometheus.NewDesc( 65 | "kamailio_shm_fragments", 66 | "Shared memory fragment count", 67 | []string{}, nil) 68 | 69 | dns_failed = prometheus.NewDesc( 70 | "kamailio_dns_failed_request_total", 71 | "Failed dns requests", 72 | []string{}, nil) 73 | 74 | bad_uri = prometheus.NewDesc( 75 | "kamailio_bad_uri_total", 76 | "Messages with bad uri", 77 | []string{}, nil) 78 | 79 | bad_msg_hdr = prometheus.NewDesc( 80 | "kamailio_bad_msg_hdr", 81 | "Messages with bad message header", 82 | []string{}, nil) 83 | 84 | sl_reply_total = prometheus.NewDesc( 85 | "kamailio_sl_reply_total", 86 | "Stateless replies by code", 87 | []string{"code"}, nil) 88 | 89 | sl_type_total = prometheus.NewDesc( 90 | "kamailio_sl_type_total", 91 | "Stateless replies by type", 92 | []string{"type"}, nil) 93 | 94 | tcp_total = prometheus.NewDesc( 95 | "kamailio_tcp_total", 96 | "TCP connection counters", 97 | []string{"type"}, nil) 98 | 99 | tcp_connections = prometheus.NewDesc( 100 | "kamailio_tcp_connections", 101 | "Opened TCP connections", 102 | []string{}, nil) 103 | 104 | tcp_writequeue = prometheus.NewDesc( 105 | "kamailio_tcp_writequeue", 106 | "TCP write queue size", 107 | []string{}, nil) 108 | 109 | tmx_code_total = prometheus.NewDesc( 110 | "kamailio_tmx_code_total", 111 | "Completed Transaction counters by code", 112 | []string{"code"}, nil) 113 | 114 | tmx_type_total = prometheus.NewDesc( 115 | "kamailio_tmx_type_total", 116 | "Completed Transaction counters by type", 117 | []string{"type"}, nil) 118 | 119 | tmx = prometheus.NewDesc( 120 | "kamailio_tmx", 121 | "Ongoing Transactions", 122 | []string{"type"}, nil) 123 | 124 | tmx_rpl_total = prometheus.NewDesc( 125 | "kamailio_tmx_rpl_total", 126 | "Tmx reply counters", 127 | []string{"type"}, nil) 128 | 129 | dialog = prometheus.NewDesc( 130 | "kamailio_dialog", 131 | "Ongoing Dialogs", 132 | []string{"type"}, nil) 133 | 134 | pkgmem_used = prometheus.NewDesc( 135 | "kamailio_pkgmem_used", 136 | "Private memory used", 137 | []string{"entry"}, 138 | nil) 139 | 140 | pkgmem_free = prometheus.NewDesc( 141 | "kamailio_pkgmem_free", 142 | "Private memory free", 143 | []string{"entry"}, 144 | nil) 145 | 146 | pkgmem_real = prometheus.NewDesc( 147 | "kamailio_pkgmem_real", 148 | "Private memory real used", 149 | []string{"entry"}, 150 | nil) 151 | 152 | pkgmem_size = prometheus.NewDesc( 153 | "kamailio_pkgmem_size", 154 | "Private memory total size", 155 | []string{"entry"}, 156 | nil) 157 | 158 | pkgmem_frags = prometheus.NewDesc( 159 | "kamailio_pkgmem_frags", 160 | "Private memory total frags", 161 | []string{"entry"}, 162 | nil) 163 | 164 | tcp_readers = prometheus.NewDesc( 165 | "kamailio_tcp_readers", 166 | "TCP readers", 167 | []string{}, 168 | nil) 169 | 170 | tcp_max_connections = prometheus.NewDesc( 171 | "kamailio_tcp_max_connections", 172 | "TCP connection limit", 173 | []string{}, 174 | nil) 175 | 176 | tls_max_connections = prometheus.NewDesc( 177 | "kamailio_tls_max_connections", 178 | "TLS connection limit", 179 | []string{}, 180 | nil) 181 | 182 | tls_connections = prometheus.NewDesc( 183 | "kamailio_tls_connections", 184 | "Opened TLS connections", 185 | []string{}, 186 | nil) 187 | 188 | rtpengine_enabled = prometheus.NewDesc( 189 | "kamailio_rtpengine_enabled", 190 | "rtpengine connection status", 191 | []string{"url", "set", "index", "weight"}, 192 | nil) 193 | ) 194 | 195 | type PkgStatsEntry struct { 196 | entry int 197 | used int 198 | free int 199 | real_used int 200 | total_size int 201 | total_frags int 202 | } 203 | 204 | // the actual Collector object 205 | type StatsCollector struct { 206 | cliContext *cli.Context 207 | socketPath string 208 | kamailioHost string 209 | kamailioPort int 210 | } 211 | 212 | // produce a new StatsCollector object 213 | func NewStatsCollector(cliContext *cli.Context) (*StatsCollector, error) { 214 | 215 | // fill the Collector struct 216 | collector := &StatsCollector{ 217 | cliContext: cliContext, 218 | socketPath: cliContext.String("socketPath"), 219 | kamailioHost: cliContext.String("host"), 220 | kamailioPort: cliContext.Int("port"), 221 | } 222 | 223 | // fine, return the created object struct 224 | return collector, nil 225 | } 226 | 227 | // part of the prometheus.Collector interface 228 | func (c *StatsCollector) Describe(descriptionChannel chan<- *prometheus.Desc) { 229 | // DescribeByCollect is a helper to implement the Describe method of a custom 230 | // Collector. It collects the metrics from the provided Collector and sends 231 | // their descriptors to the provided channel. 232 | prometheus.DescribeByCollect(c, descriptionChannel) 233 | } 234 | 235 | // part of the prometheus.Collector interface 236 | func (c *StatsCollector) Collect(metricChannel chan<- prometheus.Metric) { 237 | 238 | // TODO measure rpc time 239 | //timer := prometheus.NewTimer(rpc_request_duration) 240 | //defer timer.ObserveDuration() 241 | 242 | // establish connection to Kamailio server 243 | var err error 244 | var conn net.Conn 245 | if c.kamailioHost == "" { 246 | log.Debug("Requesting stats from kamailio via domain socket ", c.socketPath) 247 | conn, err = net.Dial("unix", c.socketPath) 248 | } else { 249 | address := fmt.Sprintf("%s:%d", c.kamailioHost, c.kamailioPort) 250 | log.Debug("Requesting stats from kamailio via binrpc ", address) 251 | conn, err = net.Dial("tcp", address) 252 | } 253 | if err != nil { 254 | log.Error("Can not connect to kamailio: ", err) 255 | return 256 | } 257 | 258 | defer conn.Close() 259 | // TODO 260 | // c.conn.SetDeadline(time.Now().Add(c.Timeout)) 261 | 262 | // WritePacket returns the cookie generated 263 | cookie, err := binrpc.WritePacket(conn, "stats.fetch", "all") 264 | if err != nil { 265 | log.Error("Can not request stats: ", err) 266 | return 267 | } 268 | 269 | // the cookie is passed again for verification 270 | // we receive records in response 271 | records, err := binrpc.ReadPacket(conn, cookie) 272 | if err != nil { 273 | log.Error("Can not fetch stats: ", err) 274 | return 275 | } 276 | 277 | // convert the structure into a simple key=>value map 278 | items, _ := records[0].StructItems() 279 | completeStatMap := make(map[string]string) 280 | for _, item := range items { 281 | value, _ := item.Value.String() 282 | completeStatMap[item.Key] = value 283 | } 284 | // and produce various prometheus.Metric for well-known stats 285 | produceMetrics(completeStatMap, metricChannel) 286 | // produce prometheus.Metric objects for scripted stats (if any) 287 | convertScriptedMetrics(completeStatMap, metricChannel) 288 | 289 | // now fetch pkg stats 290 | cookie, err = binrpc.WritePacket(conn, "pkg.stats") 291 | if err != nil { 292 | log.Error("Can not request pkg.stats: ", err) 293 | return 294 | } 295 | 296 | records, err = binrpc.ReadPacket(conn, cookie) 297 | if err != nil { 298 | log.Error("Can not fetch pkg.stats: ", err) 299 | return 300 | } 301 | 302 | // convert each pkg entry to a series of metrics 303 | for _, record := range records { 304 | items, _ = record.StructItems() 305 | entry := PkgStatsEntry{} 306 | for _, item := range items { 307 | switch item.Key { 308 | case "entry": 309 | entry.entry, _ = item.Value.Int() 310 | case "used": 311 | entry.used, _ = item.Value.Int() 312 | case "free": 313 | entry.free, _ = item.Value.Int() 314 | case "real_used": 315 | entry.real_used, _ = item.Value.Int() 316 | case "total_size": 317 | entry.total_size, _ = item.Value.Int() 318 | case "total_frags": 319 | entry.total_frags, _ = item.Value.Int() 320 | } 321 | } 322 | sentry := strconv.Itoa(entry.entry) 323 | metricChannel <- prometheus.MustNewConstMetric(pkgmem_used, prometheus.GaugeValue, float64(entry.used), sentry) 324 | metricChannel <- prometheus.MustNewConstMetric(pkgmem_free, prometheus.GaugeValue, float64(entry.free), sentry) 325 | metricChannel <- prometheus.MustNewConstMetric(pkgmem_real, prometheus.GaugeValue, float64(entry.real_used), sentry) 326 | metricChannel <- prometheus.MustNewConstMetric(pkgmem_size, prometheus.GaugeValue, float64(entry.total_size), sentry) 327 | metricChannel <- prometheus.MustNewConstMetric(pkgmem_frags, prometheus.GaugeValue, float64(entry.total_frags), sentry) 328 | } 329 | 330 | // fetch tcp details 331 | cookie, err = binrpc.WritePacket(conn, "core.tcp_info") 332 | if err != nil { 333 | log.Error("Can not request core.tcp_info: ", err) 334 | return 335 | } 336 | 337 | records, err = binrpc.ReadPacket(conn, cookie) 338 | if err != nil || len(records) == 0 { 339 | log.Error("Can not fetch core.tcp_info: ", err) 340 | return 341 | } 342 | items, _ = records[0].StructItems() 343 | var v int 344 | for _, item := range items { 345 | switch item.Key { 346 | case "readers": 347 | v, _ = item.Value.Int() 348 | metricChannel <- prometheus.MustNewConstMetric(tcp_readers, prometheus.GaugeValue, float64(v)) 349 | case "max_connections": 350 | v, _ = item.Value.Int() 351 | metricChannel <- prometheus.MustNewConstMetric(tcp_max_connections, prometheus.GaugeValue, float64(v)) 352 | case "max_tls_connections": 353 | v, _ = item.Value.Int() 354 | metricChannel <- prometheus.MustNewConstMetric(tls_max_connections, prometheus.GaugeValue, float64(v)) 355 | case "opened_tls_connections": 356 | v, _ = item.Value.Int() 357 | metricChannel <- prometheus.MustNewConstMetric(tls_connections, prometheus.GaugeValue, float64(v)) 358 | } 359 | } 360 | 361 | // fetch rtpengine disabled status and url 362 | cookie, err = binrpc.WritePacket(conn, "rtpengine.show", "all") 363 | if err != nil { 364 | log.Error("Can not request rtpengine.show: ", err) 365 | return 366 | } 367 | 368 | records, err = binrpc.ReadPacket(conn, cookie) 369 | if err != nil || len(records) == 0 { 370 | log.Error("Can not fetch rtpengine.show: ", err) 371 | return 372 | } 373 | 374 | for _, record := range records { 375 | items, _ = record.StructItems() 376 | if len(items) == 0 { 377 | log.Debug("Rtpengine.show all has empty items in record - probably because rtpengine is disabled") 378 | continue 379 | } 380 | var url string 381 | var setInt, indexInt, weightInt int 382 | var set, index, weight string 383 | for _, item := range items { 384 | switch item.Key { 385 | case "disabled": 386 | v, _ = item.Value.Int() 387 | case "url": 388 | url, _ = item.Value.String() 389 | case "set": 390 | setInt, _ = item.Value.Int() 391 | set = strconv.Itoa(setInt) 392 | case "index": 393 | indexInt, _ = item.Value.Int() 394 | index = strconv.Itoa(indexInt) 395 | case "weight": 396 | weightInt, _ = item.Value.Int() 397 | weight = strconv.Itoa(weightInt) 398 | } 399 | } 400 | if url == "" { 401 | log.Error("No valid url found for rtpengine, failed to construct metric rtpengine_enabled") 402 | continue 403 | } 404 | //invert the disabled status to fit the metric name "rtpengine_enabled" 405 | if v == 1 { 406 | v = 0 407 | } else { 408 | v = 1 409 | } 410 | metricChannel <- prometheus.MustNewConstMetric(rtpengine_enabled, prometheus.GaugeValue, float64(v), url, set, index, weight) 411 | } 412 | } 413 | 414 | // produce a series of prometheus.Metric values by converting "well-known" prometheus stats 415 | func produceMetrics(completeStatMap map[string]string, metricChannel chan<- prometheus.Metric) { 416 | 417 | // kamailio_core_request_total 418 | convertStatToMetric(completeStatMap, "core.drop_requests", "drop", core_request_total, metricChannel, prometheus.CounterValue) 419 | convertStatToMetric(completeStatMap, "core.err_requests", "err", core_request_total, metricChannel, prometheus.CounterValue) 420 | convertStatToMetric(completeStatMap, "core.fwd_requests", "fwd", core_request_total, metricChannel, prometheus.CounterValue) 421 | convertStatToMetric(completeStatMap, "core.rcv_requests", "rcv", core_request_total, metricChannel, prometheus.CounterValue) 422 | 423 | // kamailio_core_rcv_request_total 424 | convertStatToMetric(completeStatMap, "core.rcv_requests_ack", "ack", core_rcv_request_total, metricChannel, prometheus.CounterValue) 425 | convertStatToMetric(completeStatMap, "core.rcv_requests_bye", "bye", core_rcv_request_total, metricChannel, prometheus.CounterValue) 426 | convertStatToMetric(completeStatMap, "core.rcv_requests_cancel", "cancel", core_rcv_request_total, metricChannel, prometheus.CounterValue) 427 | convertStatToMetric(completeStatMap, "core.rcv_requests_info", "info", core_rcv_request_total, metricChannel, prometheus.CounterValue) 428 | convertStatToMetric(completeStatMap, "core.rcv_requests_invite", "invite", core_rcv_request_total, metricChannel, prometheus.CounterValue) 429 | convertStatToMetric(completeStatMap, "core.rcv_requests_message", "message", core_rcv_request_total, metricChannel, prometheus.CounterValue) 430 | convertStatToMetric(completeStatMap, "core.rcv_requests_notify", "notify", core_rcv_request_total, metricChannel, prometheus.CounterValue) 431 | convertStatToMetric(completeStatMap, "core.rcv_requests_options", "options", core_rcv_request_total, metricChannel, prometheus.CounterValue) 432 | convertStatToMetric(completeStatMap, "core.rcv_requests_prack", "prack", core_rcv_request_total, metricChannel, prometheus.CounterValue) 433 | convertStatToMetric(completeStatMap, "core.rcv_requests_publish", "publish", core_rcv_request_total, metricChannel, prometheus.CounterValue) 434 | convertStatToMetric(completeStatMap, "core.rcv_requests_refer", "refer", core_rcv_request_total, metricChannel, prometheus.CounterValue) 435 | convertStatToMetric(completeStatMap, "core.rcv_requests_register", "register", core_rcv_request_total, metricChannel, prometheus.CounterValue) 436 | convertStatToMetric(completeStatMap, "core.rcv_requests_subscribe", "subscribe", core_rcv_request_total, metricChannel, prometheus.CounterValue) 437 | convertStatToMetric(completeStatMap, "core.rcv_requests_update", "update", core_rcv_request_total, metricChannel, prometheus.CounterValue) 438 | convertStatToMetric(completeStatMap, "core.unsupported_methods", "unsupported", core_rcv_request_total, metricChannel, prometheus.CounterValue) 439 | 440 | // kamailio_core_reply_total 441 | convertStatToMetric(completeStatMap, "core.drop_replies", "drop", core_reply_total, metricChannel, prometheus.CounterValue) 442 | convertStatToMetric(completeStatMap, "core.err_replies", "err", core_reply_total, metricChannel, prometheus.CounterValue) 443 | convertStatToMetric(completeStatMap, "core.fwd_replies", "fwd", core_reply_total, metricChannel, prometheus.CounterValue) 444 | convertStatToMetric(completeStatMap, "core.rcv_replies", "rcv", core_reply_total, metricChannel, prometheus.CounterValue) 445 | 446 | // kamailio_core_rcv_reply_total 447 | convertStatToMetric(completeStatMap, "core.rcv_replies_18x", "18x", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 448 | convertStatToMetric(completeStatMap, "core.rcv_replies_1xx", "1xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 449 | convertStatToMetric(completeStatMap, "core.rcv_replies_2xx", "2xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 450 | convertStatToMetric(completeStatMap, "core.rcv_replies_3xx", "3xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 451 | convertStatToMetric(completeStatMap, "core.rcv_replies_401", "401", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 452 | convertStatToMetric(completeStatMap, "core.rcv_replies_404", "404", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 453 | convertStatToMetric(completeStatMap, "core.rcv_replies_407", "407", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 454 | convertStatToMetric(completeStatMap, "core.rcv_replies_408", "408", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 455 | convertStatToMetric(completeStatMap, "core.rcv_replies_480", "480", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 456 | convertStatToMetric(completeStatMap, "core.rcv_replies_486", "486", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 457 | convertStatToMetric(completeStatMap, "core.rcv_replies_4xx", "4xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 458 | convertStatToMetric(completeStatMap, "core.rcv_replies_5xx", "5xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 459 | convertStatToMetric(completeStatMap, "core.rcv_replies_6xx", "6xx", core_rcv_reply_total, metricChannel, prometheus.CounterValue) 460 | 461 | // kamailio_shm_bytes 462 | convertStatToMetric(completeStatMap, "shmem.free_size", "free", shmem_bytes, metricChannel, prometheus.GaugeValue) 463 | convertStatToMetric(completeStatMap, "shmem.max_used_size", "max_used", shmem_bytes, metricChannel, prometheus.GaugeValue) 464 | convertStatToMetric(completeStatMap, "shmem.real_used_size", "real_used", shmem_bytes, metricChannel, prometheus.GaugeValue) 465 | convertStatToMetric(completeStatMap, "shmem.total_size", "total", shmem_bytes, metricChannel, prometheus.GaugeValue) 466 | convertStatToMetric(completeStatMap, "shmem.used_size", "used", shmem_bytes, metricChannel, prometheus.GaugeValue) 467 | 468 | convertStatToMetric(completeStatMap, "shmem.fragments", "", shmem_fragments, metricChannel, prometheus.GaugeValue) 469 | convertStatToMetric(completeStatMap, "dns.failed_dns_request", "", dns_failed, metricChannel, prometheus.CounterValue) 470 | convertStatToMetric(completeStatMap, "core.bad_URIs_rcvd", "", bad_uri, metricChannel, prometheus.CounterValue) 471 | convertStatToMetric(completeStatMap, "core.bad_msg_hdr", "", bad_msg_hdr, metricChannel, prometheus.CounterValue) 472 | 473 | // kamailio_sl_reply_total 474 | convertStatToMetric(completeStatMap, "sl.1xx_replies", "1xx", sl_reply_total, metricChannel, prometheus.CounterValue) 475 | convertStatToMetric(completeStatMap, "sl.200_replies", "200", sl_reply_total, metricChannel, prometheus.CounterValue) 476 | convertStatToMetric(completeStatMap, "sl.202_replies", "202", sl_reply_total, metricChannel, prometheus.CounterValue) 477 | convertStatToMetric(completeStatMap, "sl.2xx_replies", "2xx", sl_reply_total, metricChannel, prometheus.CounterValue) 478 | convertStatToMetric(completeStatMap, "sl.300_replies", "300", sl_reply_total, metricChannel, prometheus.CounterValue) 479 | convertStatToMetric(completeStatMap, "sl.301_replies", "301", sl_reply_total, metricChannel, prometheus.CounterValue) 480 | convertStatToMetric(completeStatMap, "sl.302_replies", "302", sl_reply_total, metricChannel, prometheus.CounterValue) 481 | convertStatToMetric(completeStatMap, "sl.3xx_replies", "3xx", sl_reply_total, metricChannel, prometheus.CounterValue) 482 | convertStatToMetric(completeStatMap, "sl.400_replies", "400", sl_reply_total, metricChannel, prometheus.CounterValue) 483 | convertStatToMetric(completeStatMap, "sl.401_replies", "401", sl_reply_total, metricChannel, prometheus.CounterValue) 484 | convertStatToMetric(completeStatMap, "sl.403_replies", "403", sl_reply_total, metricChannel, prometheus.CounterValue) 485 | convertStatToMetric(completeStatMap, "sl.404_replies", "404", sl_reply_total, metricChannel, prometheus.CounterValue) 486 | convertStatToMetric(completeStatMap, "sl.407_replies", "407", sl_reply_total, metricChannel, prometheus.CounterValue) 487 | convertStatToMetric(completeStatMap, "sl.408_replies", "408", sl_reply_total, metricChannel, prometheus.CounterValue) 488 | convertStatToMetric(completeStatMap, "sl.483_replies", "483", sl_reply_total, metricChannel, prometheus.CounterValue) 489 | convertStatToMetric(completeStatMap, "sl.4xx_replies", "4xx", sl_reply_total, metricChannel, prometheus.CounterValue) 490 | convertStatToMetric(completeStatMap, "sl.500_replies", "500", sl_reply_total, metricChannel, prometheus.CounterValue) 491 | convertStatToMetric(completeStatMap, "sl.5xx_replies", "5xx", sl_reply_total, metricChannel, prometheus.CounterValue) 492 | convertStatToMetric(completeStatMap, "sl.6xx_replies", "6xx", sl_reply_total, metricChannel, prometheus.CounterValue) 493 | 494 | // kamailio_sl_type_total 495 | convertStatToMetric(completeStatMap, "sl.failures", "failure", sl_type_total, metricChannel, prometheus.CounterValue) 496 | convertStatToMetric(completeStatMap, "sl.received_ACKs", "received_ack", sl_type_total, metricChannel, prometheus.CounterValue) 497 | convertStatToMetric(completeStatMap, "sl.sent_err_replies", "sent_err_reply", sl_type_total, metricChannel, prometheus.CounterValue) 498 | convertStatToMetric(completeStatMap, "sl.sent_replies", "sent_reply", sl_type_total, metricChannel, prometheus.CounterValue) 499 | convertStatToMetric(completeStatMap, "sl.xxx_replies", "xxx_reply", sl_type_total, metricChannel, prometheus.CounterValue) 500 | 501 | // kamailio_tcp_total 502 | convertStatToMetric(completeStatMap, "tcp.con_reset", "con_reset", tcp_total, metricChannel, prometheus.CounterValue) 503 | convertStatToMetric(completeStatMap, "tcp.con_timeout", "con_timeout", tcp_total, metricChannel, prometheus.CounterValue) 504 | convertStatToMetric(completeStatMap, "tcp.connect_failed", "connect_failed", tcp_total, metricChannel, prometheus.CounterValue) 505 | convertStatToMetric(completeStatMap, "tcp.connect_success", "connect_success", tcp_total, metricChannel, prometheus.CounterValue) 506 | convertStatToMetric(completeStatMap, "tcp.established", "established", tcp_total, metricChannel, prometheus.CounterValue) 507 | convertStatToMetric(completeStatMap, "tcp.local_reject", "local_reject", tcp_total, metricChannel, prometheus.CounterValue) 508 | convertStatToMetric(completeStatMap, "tcp.passive_open", "passive_open", tcp_total, metricChannel, prometheus.CounterValue) 509 | convertStatToMetric(completeStatMap, "tcp.send_timeout", "send_timeout", tcp_total, metricChannel, prometheus.CounterValue) 510 | convertStatToMetric(completeStatMap, "tcp.sendq_full", "sendq_full", tcp_total, metricChannel, prometheus.CounterValue) 511 | // kamailio_tcp_connections 512 | convertStatToMetric(completeStatMap, "tcp.current_opened_connections", "", tcp_connections, metricChannel, prometheus.GaugeValue) 513 | // kamailio_tcp_writequeue 514 | convertStatToMetric(completeStatMap, "tcp.current_write_queue_size", "", tcp_writequeue, metricChannel, prometheus.GaugeValue) 515 | 516 | // kamailio_tmx_code_total 517 | convertStatToMetric(completeStatMap, "tmx.2xx_transactions", "2xx", tmx_code_total, metricChannel, prometheus.CounterValue) 518 | convertStatToMetric(completeStatMap, "tmx.3xx_transactions", "3xx", tmx_code_total, metricChannel, prometheus.CounterValue) 519 | convertStatToMetric(completeStatMap, "tmx.4xx_transactions", "4xx", tmx_code_total, metricChannel, prometheus.CounterValue) 520 | convertStatToMetric(completeStatMap, "tmx.5xx_transactions", "5xx", tmx_code_total, metricChannel, prometheus.CounterValue) 521 | convertStatToMetric(completeStatMap, "tmx.6xx_transactions", "6xx", tmx_code_total, metricChannel, prometheus.CounterValue) 522 | // kamailio_tmx_type_total 523 | convertStatToMetric(completeStatMap, "tmx.UAC_transactions", "uac", tmx_type_total, metricChannel, prometheus.CounterValue) 524 | convertStatToMetric(completeStatMap, "tmx.UAS_transactions", "uas", tmx_type_total, metricChannel, prometheus.CounterValue) 525 | // kamailio_tmx 526 | convertStatToMetric(completeStatMap, "tmx.active_transactions", "active", tmx, metricChannel, prometheus.GaugeValue) 527 | convertStatToMetric(completeStatMap, "tmx.inuse_transactions", "inuse", tmx, metricChannel, prometheus.GaugeValue) 528 | 529 | // kamailio_tmx_rpl_total 530 | convertStatToMetric(completeStatMap, "tmx.rpl_absorbed", "absorbed", tmx_rpl_total, metricChannel, prometheus.CounterValue) 531 | convertStatToMetric(completeStatMap, "tmx.rpl_generated", "generated", tmx_rpl_total, metricChannel, prometheus.CounterValue) 532 | convertStatToMetric(completeStatMap, "tmx.rpl_received", "received", tmx_rpl_total, metricChannel, prometheus.CounterValue) 533 | convertStatToMetric(completeStatMap, "tmx.rpl_relayed", "relayed", tmx_rpl_total, metricChannel, prometheus.CounterValue) 534 | convertStatToMetric(completeStatMap, "tmx.rpl_sent", "sent", tmx_rpl_total, metricChannel, prometheus.CounterValue) 535 | 536 | // kamailio_dialog 537 | convertStatToMetric(completeStatMap, "dialog.active_dialogs", "active_dialogs", dialog, metricChannel, prometheus.CounterValue) 538 | convertStatToMetric(completeStatMap, "dialog.early_dialogs", "early_dialogs", dialog, metricChannel, prometheus.CounterValue) 539 | convertStatToMetric(completeStatMap, "dialog.expired_dialogs", "expired_dialogs", dialog, metricChannel, prometheus.CounterValue) 540 | convertStatToMetric(completeStatMap, "dialog.failed_dialogs", "failed_dialogs", dialog, metricChannel, prometheus.CounterValue) 541 | convertStatToMetric(completeStatMap, "dialog.processed_dialogs", "processed_dialogs", dialog, metricChannel, prometheus.CounterValue) 542 | } 543 | 544 | // Iterate all reported "stats" keys and find those with a prefix of "script." 545 | // These values are user-defined and populated within the kamailio script. 546 | // See https://www.kamailio.org/docs/modules/5.2.x/modules/statistics.html 547 | func convertScriptedMetrics(data map[string]string, prom chan<- prometheus.Metric) { 548 | for k := range data { 549 | // k = "script.custom_total" 550 | if strings.HasPrefix(k, "script.") { 551 | // metricName = "custom_total" 552 | metricName := strings.TrimPrefix(k, "script.") 553 | metricName = strings.ToLower(metricName) 554 | var valueType prometheus.ValueType 555 | // deduce the metrics value type by following https://prometheus.io/docs/practices/naming/ 556 | if strings.HasSuffix(k, "_total") || strings.HasSuffix(k, "_seconds") || strings.HasSuffix(k, "_bytes") { 557 | valueType = prometheus.CounterValue 558 | } else { 559 | valueType = prometheus.GaugeValue 560 | } 561 | // create a metric description on the fly 562 | description := prometheus.NewDesc("kamailio_"+metricName, "Scripted metric "+metricName, []string{}, nil) 563 | // and produce a metric 564 | convertStatToMetric(data, k, "", description, prom, valueType) 565 | } 566 | } 567 | } 568 | 569 | // convert a single "stat" value to a prometheus metric 570 | // invalid "stat" paires are skipped but logged 571 | func convertStatToMetric(completeStatMap map[string]string, statKey string, optionalLabelValue string, metricDescription *prometheus.Desc, metricChannel chan<- prometheus.Metric, valueType prometheus.ValueType) { 572 | // check wether we got a labelValue or not 573 | var labelValues []string 574 | if optionalLabelValue != "" { 575 | labelValues = []string{optionalLabelValue} 576 | } else { 577 | labelValues = []string{} 578 | } 579 | // get the stat-value ... 580 | if valueAsString, ok := completeStatMap[statKey]; ok { 581 | // ... convert it to a float 582 | if value, err := strconv.ParseFloat(valueAsString, 64); err == nil { 583 | // and produce a prometheus metric 584 | metric, err := prometheus.NewConstMetric( 585 | metricDescription, 586 | valueType, 587 | value, 588 | labelValues..., 589 | ) 590 | if err == nil { 591 | // handover the metric to prometheus api 592 | metricChannel <- metric 593 | } else { 594 | // or skip and complain 595 | log.Warnf("Could not convert stat value [%s]: %s", statKey, err) 596 | } 597 | } 598 | } else { 599 | // skip stat values not found in completeStatMap 600 | // can happen if some kamailio modules are not loaded 601 | // and thus certain stat entries are not created 602 | log.Debugf("Skipping stat value [%s], it was not returned by kamailio", statKey) 603 | } 604 | } 605 | --------------------------------------------------------------------------------