pax_global_header00006660000000000000000000000064144474054570014530gustar00rootroot0000000000000052 comment=d1c70405e5e532ed47048f75bc122a9bb8ed9c9b pgbouncer_exporter-0.7.0/000077500000000000000000000000001444740545700154505ustar00rootroot00000000000000pgbouncer_exporter-0.7.0/.circleci/000077500000000000000000000000001444740545700173035ustar00rootroot00000000000000pgbouncer_exporter-0.7.0/.circleci/config.yml000066400000000000000000000032321444740545700212730ustar00rootroot00000000000000--- version: 2.1 orbs: prometheus: prometheus/prometheus@0.17.1 executors: # This must match .promu.yml. golang: docker: - image: cimg/go:1.20 jobs: test: executor: golang steps: - prometheus/setup_environment - run: make - prometheus/store_artifact: file: pgbouncer_exporter workflows: version: 2 pgbouncer_exporter: jobs: - test: filters: tags: only: /.*/ - prometheus/build: name: build parallelism: 3 promu_opts: "-p linux/amd64 -p windows/amd64 -p linux/arm64 -p darwin/amd64 -p darwin/arm64 -p linux/386" filters: tags: ignore: /^v.*/ branches: ignore: /^(main|master|release-.*|.*build-all.*)$/ - prometheus/build: name: build_all parallelism: 12 filters: branches: only: /^(main|master|release-.*|.*build-all.*)$/ tags: only: /^v.*/ - prometheus/publish_master: context: org-context docker_hub_organization: prometheuscommunity quay_io_organization: prometheuscommunity requires: - test - build_all filters: branches: only: master - prometheus/publish_release: context: org-context docker_hub_organization: prometheuscommunity quay_io_organization: prometheuscommunity requires: - test - build_all filters: tags: only: /^v.*/ branches: ignore: /.*/ pgbouncer_exporter-0.7.0/.github/000077500000000000000000000000001444740545700170105ustar00rootroot00000000000000pgbouncer_exporter-0.7.0/.github/dependabot.yml000066400000000000000000000001561444740545700216420ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" pgbouncer_exporter-0.7.0/.github/workflows/000077500000000000000000000000001444740545700210455ustar00rootroot00000000000000pgbouncer_exporter-0.7.0/.github/workflows/golangci-lint.yml000066400000000000000000000013731444740545700243230ustar00rootroot00000000000000name: golangci-lint on: push: paths: - "go.sum" - "go.mod" - "**.go" - "scripts/errcheck_excludes.txt" - ".github/workflows/golangci-lint.yml" - ".golangci.yml" pull_request: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: install Go uses: actions/setup-go@v3 with: go-version: 1.20.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint uses: golangci/golangci-lint-action@v3.4.0 with: version: v1.51.2 pgbouncer_exporter-0.7.0/.gitignore000066400000000000000000000005511444740545700174410ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe dependencies-stamp /pgbouncer_exporter /.build /.deps /.release /.tarballs /vendor # Intellij /.idea *.iml pgbouncer_exporter-0.7.0/.golangci.yml000066400000000000000000000002361444740545700200350ustar00rootroot00000000000000--- issues: exclude-rules: - path: _test.go linters: - errcheck linters-settings: errcheck: exclude: scripts/errcheck_excludes.txt pgbouncer_exporter-0.7.0/.promu.yml000066400000000000000000000011471444740545700174160ustar00rootroot00000000000000go: # This must match .circle/config.yml. version: 1.20 repository: path: github.com/prometheus-community/pgbouncer_exporter build: binaries: - name: pgbouncer_exporter flags: -a -tags 'netgo static_build' ldflags: | -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.Revision={{.Revision}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: files: - LICENSE pgbouncer_exporter-0.7.0/.yamllint000066400000000000000000000007111444740545700173010ustar00rootroot00000000000000--- extends: default rules: braces: max-spaces-inside: 1 level: error brackets: max-spaces-inside: 1 level: error commas: disable comments: disable comments-indentation: disable document-start: disable indentation: spaces: consistent indent-sequences: consistent key-duplicates: ignore: | config/testdata/section_key_dup.bad.yml line-length: disable truthy: ignore: | .github/workflows/*.yml pgbouncer_exporter-0.7.0/CHANGELOG.md000066400000000000000000000025031444740545700172610ustar00rootroot00000000000000## master / unreleased ## 0.7.0 / 2023-06-29 * [CHANGE] Require Go 1.19 and update CI with Go 1.20 #120 * [CHANGE] Synchronize common files from prometheus/prometheus #123 ## 0.6.0 / 2023-01-27 * [FEATURE] Add config metrics #93 * [FEATURE] Add TLS and Basic auth to the metrics endpoint #101 ## 0.5.1 / 2022-10-03 * No changes, just retagging due to a VERSION fix. ## 0.5.0 / 2022-10-03 * [CHANGE] Update Go to 1.18. * [CHANGE] Update upstream dependencies. ## 0.4.1 / 2022-01-27 * [BUGFIX] Fix startup log message typo #50 * [BUGFIX] Fix typo in reserve_pool metric #67 ## 0.4.0 / 2020-07-09 Counter names have been updated to match Prometheus naming conventions. * `pgbouncer_stats_queries_duration_seconds` -> `pgbouncer_stats_queries_duration_seconds_total` * `pgbouncer_stats_client_wait_seconds` -> `pgbouncer_stats_client_wait_seconds_total` * `pgbouncer_stats_server_in_transaction_seconds` -> `pgbouncer_stats_server_in_transaction_seconds_total` * [CHANGE] Cleanup exporter metrics #33 * [CHANGE] Update counter metric names #35 * [FEATURE] Add support for SHOW LISTS metrics #36 ## 0.3.0 / 2020-05-27 * [CHANGE] Switch logging to promlog #29 * [FEATURE] Add pgbouncer process metrics #27 ## 0.2.0 / 2020-04-29 * [BUGFIX] Fix byte slice values not receiving conversion factor #18 Initial prometheus-community release. pgbouncer_exporter-0.7.0/CODE_OF_CONDUCT.md000066400000000000000000000002301444740545700202420ustar00rootroot00000000000000# Prometheus Community Code of Conduct Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). pgbouncer_exporter-0.7.0/Dockerfile000066400000000000000000000006151444740545700174440ustar00rootroot00000000000000ARG ARCH="amd64" ARG OS="linux" FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest LABEL maintainer="The Prometheus Authors " ARG ARCH="amd64" ARG OS="linux" COPY .build/${OS}-${ARCH}/pgbouncer_exporter /bin/pgbouncer_exporter COPY LICENSE /LICENSE USER nobody ENTRYPOINT ["/bin/pgbouncer_exporter"] EXPOSE 9127 pgbouncer_exporter-0.7.0/LICENSE000066400000000000000000000021251444740545700164550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Kristoffer K Larsen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pgbouncer_exporter-0.7.0/MAINTAINERS.md000066400000000000000000000001151444740545700175410ustar00rootroot00000000000000* Ben Kochie @SuperQ * Stan Hu @stanhu pgbouncer_exporter-0.7.0/Makefile000066400000000000000000000014431444740545700171120ustar00rootroot00000000000000# Copyright 2020 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Needs to be defined before including Makefile.common to auto-generate targets DOCKER_ARCHS ?= amd64 armv7 arm64 DOCKER_REPO ?= prometheuscommunity include Makefile.common DOCKER_IMAGE_NAME ?= pgbouncer-exporter pgbouncer_exporter-0.7.0/Makefile.common000066400000000000000000000216551444740545700204100ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A common Makefile that includes rules to be reused in different prometheus projects. # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! # Example usage : # Create the main Makefile in the root project directory. # include Makefile.common # customTarget: # @echo ">> Running customTarget" # # Ensure GOBIN is not set during build so that promu is installed to the correct path unexport GOBIN GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) GOOPTS ?= GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') PROMU := $(FIRST_GOPATH)/bin/promu pkgs = ./... ifeq (arm, $(GOHOSTARCH)) GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) else GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) endif GOTEST := $(GO) test GOTEST_DIR := ifneq ($(CIRCLE_JOB),) ifneq ($(shell command -v gotestsum > /dev/null),) GOTEST_DIR := test-results GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- endif endif PROMU_VERSION ?= 0.14.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.51.2 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) GOLANGCI_LINT := else ifeq (,$(CIRCLE_JOB)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint endif endif endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 test-flags := -race endif endif # This rule is used to forward a target like "build" to "common-build". This # allows a new "build" target to be defined in a Makefile which includes this # one and override "common-build" without override warnings. %: common-% ; .PHONY: common-all common-all: precheck style check_license lint yamllint unused build test .PHONY: common-style common-style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: common-check_license common-check_license: @echo ">> checking license header" @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: common-deps common-deps: @echo ">> getting dependencies" $(GO) mod download .PHONY: update-go-deps update-go-deps: @echo ">> updating Go dependencies" @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ $(GO) get -d $$m; \ done $(GO) mod tidy .PHONY: common-test-short common-test-short: $(GOTEST_DIR) @echo ">> running short tests" $(GOTEST) -short $(GOOPTS) $(pkgs) .PHONY: common-test common-test: $(GOTEST_DIR) @echo ">> running all tests" $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) $(GOTEST_DIR): @mkdir -p $@ .PHONY: common-format common-format: @echo ">> formatting code" $(GO) fmt $(pkgs) .PHONY: common-vet common-vet: @echo ">> vetting code" $(GO) vet $(GOOPTS) $(pkgs) .PHONY: common-lint common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. # Otherwise staticcheck might fail randomly for some reason not yet explained. $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif .PHONY: common-yamllint common-yamllint: @echo ">> running yamllint on all YAML files in the repository" ifeq (, $(shell command -v yamllint > /dev/null)) @echo "yamllint not installed so skipping" else yamllint . endif # For backward-compatibility. .PHONY: common-staticcheck common-staticcheck: lint .PHONY: common-unused common-unused: @echo ">> running check for unused/missing packages in go.mod" $(GO) mod tidy @git diff --exit-code -- go.sum go.mod .PHONY: common-build common-build: promu @echo ">> building binaries" $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) .PHONY: common-tarball common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ $(DOCKERBUILD_CONTEXT) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" .PHONY: common-docker-manifest common-docker-manifest: DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) $(PROMU): $(eval PROMU_TMP := $(shell mktemp -d)) curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) mkdir -p $(FIRST_GOPATH)/bin cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) .PHONY: proto proto: @echo ">> generating code from proto files" @./scripts/genproto.sh ifdef GOLANGCI_LINT $(GOLANGCI_LINT): mkdir -p $(FIRST_GOPATH)/bin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ | sed -e '/install -d/d' \ | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif .PHONY: precheck precheck:: define PRECHECK_COMMAND_template = precheck:: $(1)_precheck PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) .PHONY: $(1)_precheck $(1)_precheck: @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ exit 1; \ fi endef pgbouncer_exporter-0.7.0/README.md000077500000000000000000000072161444740545700167400ustar00rootroot00000000000000# PgBouncer exporter [![Build Status](https://circleci.com/gh/prometheus-community/pgbouncer_exporter.svg?style=svg)](https://circleci.com/gh/prometheus-community/pgbouncer_exporter) Prometheus exporter for PgBouncer. Exports metrics at `9127/metrics` ## Building and running make build ./pgbouncer_exporter To see all available configuration flags: ./pgbouncer_exporter -h ## PGBouncer configuration The pgbouncer\_exporter requires a configuration change to pgbouncer to ignore a PostgreSQL driver connection parameter. In the `pgbouncer.ini` please include this option: ignore_startup_parameters = extra_float_digits ## Run with docker ``` docker run prometheuscommunity/pgbouncer-exporter ``` ## Metrics |PgBouncer column|Prometheus Metric|Description| |----------------|-----------------|-----------| stats_total_query_count | pgbouncer_stats_queries_pooled_total | Total number of SQL queries pooled stats.total_query_time | pgbouncer_stats_queries_duration_seconds_total | Total number of seconds spent by pgbouncer when actively connected to PostgreSQL, executing queries stats.total_received | pgbouncer_stats_received_bytes_total | Total volume in bytes of network traffic received by pgbouncer, shown as bytes stats.total_requests | pgbouncer_stats_queries_total | Total number of SQL requests pooled by pgbouncer, shown as requests stats.total_sent | pgbouncer_stats_sent_bytes_total | Total volume in bytes of network traffic sent by pgbouncer, shown as bytes stats.total_wait_time | pgbouncer_stats_client_wait_seconds_total | Time spent by clients waiting for a server in seconds stats.total_xact_count | pgbouncer_stats_sql_transactions_pooled_total | Total number of SQL transactions pooled stats.total_xact_time | pgbouncer_stats_server_in_transaction_seconds_total | Total number of seconds spent by pgbouncer when connected to PostgreSQL in a transaction, either idle in transaction or executing queries pools.cl_active | pgbouncer_pools_client_active_connections | Client connections linked to server connection and able to process queries, shown as connection pools.cl_waiting | pgbouncer_pools_client_waiting_connections | Client connections waiting on a server connection, shown as connection pools.sv_active | pgbouncer_pools_server_active_connections | Server connections linked to a client connection, shown as connection pools.sv_idle | pgbouncer_pools_server_idle_connections | Server connections idle and ready for a client query, shown as connection pools.sv_used | pgbouncer_pools_server_used_connections | Server connections idle more than server_check_delay, needing server_check_query, shown as connection pools.sv_tested | pgbouncer_pools_server_testing_connections | Server connections currently running either server_reset_query or server_check_query, shown as connection pools.sv_login | pgbouncer_pools_server_login_connections | Server connections currently in the process of logging in, shown as connection pools.maxwait | pgbouncer_pools_client_maxwait_seconds | Age of oldest unserved client connection, shown as second config.max_client_conn | pgbouncer_config_max_client_connections | Configured maximum number of client connections config.max_user_connections | pgbouncer_config_max_user_connections | Configured maximum number of server connections per user ## TLS and basic authentication The pgbouncer exporter supports TLS and basic authentication. To use TLS and/or basic authentication, you need to pass a configuration file using the `--web.config.file` parameter. The format of the file is described [in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). pgbouncer_exporter-0.7.0/SECURITY.md000066400000000000000000000002541444740545700172420ustar00rootroot00000000000000# Reporting a security issue The Prometheus security policy, including how to report vulnerabilities, can be found here: pgbouncer_exporter-0.7.0/VERSION000066400000000000000000000000061444740545700165140ustar00rootroot000000000000000.7.0 pgbouncer_exporter-0.7.0/collector.go000066400000000000000000000460231444740545700177720ustar00rootroot00000000000000// Copyright 2020 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "database/sql" "errors" "fmt" "math" "os" "strconv" "time" "unicode/utf8" "github.com/go-kit/log" "github.com/go-kit/log/level" _ "github.com/lib/pq" "github.com/prometheus/client_golang/prometheus" ) var ( metricMaps = map[string]map[string]ColumnMapping{ "databases": { "name": {LABEL, "N/A", 1, "N/A"}, "host": {LABEL, "N/A", 1, "N/A"}, "port": {LABEL, "N/A", 1, "N/A"}, "database": {LABEL, "N/A", 1, "N/A"}, "force_user": {LABEL, "N/A", 1, "N/A"}, "pool_size": {GAUGE, "pool_size", 1, "Maximum number of server connections"}, "reserve_pool": {GAUGE, "reserve_pool", 1, "Maximum number of additional connections for this database"}, "pool_mode": {LABEL, "N/A", 1, "N/A"}, "max_connections": {GAUGE, "max_connections", 1, "Maximum number of allowed connections for this database"}, "current_connections": {GAUGE, "current_connections", 1, "Current number of connections for this database"}, "paused": {GAUGE, "paused", 1, "1 if this database is currently paused, else 0"}, "disabled": {GAUGE, "disabled", 1, "1 if this database is currently disabled, else 0"}, }, "stats": { "database": {LABEL, "N/A", 1, "N/A"}, "total_query_count": {COUNTER, "queries_pooled_total", 1, "Total number of SQL queries pooled"}, "total_query_time": {COUNTER, "queries_duration_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when actively connected to PostgreSQL, executing queries"}, "total_received": {COUNTER, "received_bytes_total", 1, "Total volume in bytes of network traffic received by pgbouncer, shown as bytes"}, "total_requests": {COUNTER, "queries_total", 1, "Total number of SQL requests pooled by pgbouncer, shown as requests"}, "total_sent": {COUNTER, "sent_bytes_total", 1, "Total volume in bytes of network traffic sent by pgbouncer, shown as bytes"}, "total_wait_time": {COUNTER, "client_wait_seconds_total", 1e-6, "Time spent by clients waiting for a server in seconds"}, "total_xact_count": {COUNTER, "sql_transactions_pooled_total", 1, "Total number of SQL transactions pooled"}, "total_xact_time": {COUNTER, "server_in_transaction_seconds_total", 1e-6, "Total number of seconds spent by pgbouncer when connected to PostgreSQL in a transaction, either idle in transaction or executing queries"}, }, "pools": { "database": {LABEL, "N/A", 1, "N/A"}, "user": {LABEL, "N/A", 1, "N/A"}, "cl_active": {GAUGE, "client_active_connections", 1, "Client connections linked to server connection and able to process queries, shown as connection"}, "cl_waiting": {GAUGE, "client_waiting_connections", 1, "Client connections waiting on a server connection, shown as connection"}, "sv_active": {GAUGE, "server_active_connections", 1, "Server connections linked to a client connection, shown as connection"}, "sv_idle": {GAUGE, "server_idle_connections", 1, "Server connections idle and ready for a client query, shown as connection"}, "sv_used": {GAUGE, "server_used_connections", 1, "Server connections idle more than server_check_delay, needing server_check_query, shown as connection"}, "sv_tested": {GAUGE, "server_testing_connections", 1, "Server connections currently running either server_reset_query or server_check_query, shown as connection"}, "sv_login": {GAUGE, "server_login_connections", 1, "Server connections currently in the process of logging in, shown as connection"}, "maxwait": {GAUGE, "client_maxwait_seconds", 1, "Age of oldest unserved client connection, shown as second"}, }, } listsMap = map[string]*(prometheus.Desc){ "databases": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "databases"), "Count of databases", nil, nil), "users": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "users"), "Count of users", nil, nil), "pools": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "pools"), "Count of pools", nil, nil), "free_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "free_clients"), "Count of free clients", nil, nil), "used_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "used_clients"), "Count of used clients", nil, nil), "login_clients": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "login_clients"), "Count of clients in login state", nil, nil), "free_servers": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "free_servers"), "Count of free servers", nil, nil), "used_servers": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "used_servers"), "Count of used servers", nil, nil), "dns_names": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "cached_dns_names"), "Count of DNS names in the cache", nil, nil), "dns_zones": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "cached_dns_zones"), "Count of DNS zones in the cache", nil, nil), "dns_queries": prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "in_flight_dns_queries"), "Count of in-flight DNS queries", nil, nil), } configMap = map[string]*(prometheus.Desc){ "max_client_conn": prometheus.NewDesc( prometheus.BuildFQName(namespace, "config", "max_client_connections"), "Config maximum number of client connections", nil, nil), "max_user_connections": prometheus.NewDesc( prometheus.BuildFQName(namespace, "config", "max_user_connections"), "Config maximum number of server connections per user", nil, nil), } ) // Metric descriptors. var ( bouncerVersionDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "version", "info"), "The pgbouncer version info", []string{"version"}, nil, ) scrapeSuccessDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "up"), "The pgbouncer scrape succeeded", nil, nil, ) ) func NewExporter(connectionString string, namespace string, logger log.Logger) *Exporter { db, err := getDB(connectionString) if err != nil { level.Error(logger).Log("msg", "error setting up DB connection", "err", err) os.Exit(1) } return &Exporter{ metricMap: makeDescMap(metricMaps, namespace, logger), db: db, logger: logger, } } // Query SHOW LISTS, which has a series of rows, not columns. func queryShowLists(ch chan<- prometheus.Metric, db *sql.DB, logger log.Logger) error { rows, err := db.Query("SHOW LISTS;") if err != nil { return fmt.Errorf("error running SHOW LISTS on database: %w", err) } defer rows.Close() columnNames, err := rows.Columns() if err != nil || len(columnNames) != 2 { return fmt.Errorf("error retrieving columns list from SHOW LISTS: %w", err) } var list string var items sql.RawBytes for rows.Next() { if err = rows.Scan(&list, &items); err != nil { return fmt.Errorf("error retrieving SHOW LISTS rows: %w", err) } value, err := strconv.ParseFloat(string(items), 64) if err != nil { return fmt.Errorf("error parsing SHOW LISTS column: %v, error: %w", list, err) } if metric, ok := listsMap[list]; ok { ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value) } else { level.Debug(logger).Log("msg", "SHOW LISTS unknown list", "list", list) } } return nil } // Query SHOW CONFIG, which has a series of rows, not columns. func queryShowConfig(ch chan<- prometheus.Metric, db *sql.DB, logger log.Logger) error { rows, err := db.Query("SHOW CONFIG;") if err != nil { return fmt.Errorf("error running SHOW CONFIG on database: %w", err) } defer rows.Close() columnNames, err := rows.Columns() numColumns := len(columnNames) if err != nil { return fmt.Errorf("error retrieving columns list from SHOW CONFIG: %w", err) } exposedConfig := make(map[string]bool) for configKey := range configMap { exposedConfig[configKey] = true } var key string var values sql.RawBytes var defaultValue sql.RawBytes var changeable string for rows.Next() { switch numColumns { case 3: if err = rows.Scan(&key, &values, &changeable); err != nil { return fmt.Errorf("error retrieving SHOW CONFIG rows: %w", err) } case 4: if err = rows.Scan(&key, &values, &defaultValue, &changeable); err != nil { return fmt.Errorf("error retrieving SHOW CONFIG rows: %w", err) } default: return fmt.Errorf("invalid number of SHOW CONFIG columns: %d", numColumns) } if !exposedConfig[key] { continue } value, err := strconv.ParseFloat(string(values), 64) if err != nil { return fmt.Errorf("error parsing SHOW CONFIG column: %v, error: %w ", key, err) } if metric, ok := configMap[key]; ok { ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value) } else { level.Debug(logger).Log("msg", "SHOW CONFIG unknown config", "config", key) } } return nil } // Query within a namespace mapping and emit metrics. Returns fatal errors if // the scrape fails, and a slice of errors if they were non-fatal. func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace string, mapping MetricMapNamespace, logger log.Logger) ([]error, error) { query := fmt.Sprintf("SHOW %s;", namespace) // Don't fail on a bad scrape of one metric rows, err := db.Query(query) if err != nil { return []error{}, fmt.Errorf("error running query on database: %v, error: %w", namespace, err) } defer rows.Close() var columnNames []string columnNames, err = rows.Columns() if err != nil { return []error{}, fmt.Errorf("error retrieving column list for: %v, error: %w", namespace, err) } // Make a lookup map for the column indices var columnIdx = make(map[string]int, len(columnNames)) for i, n := range columnNames { columnIdx[n] = i } var columnData = make([]interface{}, len(columnNames)) var scanArgs = make([]interface{}, len(columnNames)) for i := range columnData { scanArgs[i] = &columnData[i] } nonfatalErrors := []error{} for rows.Next() { labelValues := make([]string, len(mapping.labels)) err = rows.Scan(scanArgs...) if err != nil { return []error{}, fmt.Errorf("error retrieving rows: %v, error: %w", namespace, err) } for i, label := range mapping.labels { for idx, columnName := range columnNames { if columnName == label { switch v := columnData[idx].(type) { case int: labelValues[i] = strconv.Itoa(columnData[idx].(int)) case int64: labelValues[i] = strconv.Itoa(int(columnData[idx].(int64))) case float64: labelValues[i] = fmt.Sprintf("%f", columnData[idx].(float64)) case string: labelValues[i] = columnData[idx].(string) case nil: labelValues[i] = "" default: nonfatalErrors = append(nonfatalErrors, fmt.Errorf("column %s in %s has an unhandled type %v for label: %s ", columnName, namespace, v, columnData[idx])) labelValues[i] = "" continue } // Prometheus will fail hard if the database and usernames are not UTF-8 if !utf8.ValidString(labelValues[i]) { nonfatalErrors = append(nonfatalErrors, fmt.Errorf("column %s in %s has an invalid UTF-8 for a label: %s ", columnName, namespace, columnData[idx])) labelValues[i] = "" continue } } } } // Loop over column names, and match to scan data. Unknown columns // will be filled with an untyped metric number *if* they can be // converted to float64s. NULLs are allowed and treated as NaN. for idx, columnName := range columnNames { if metricMapping, ok := mapping.columnMappings[columnName]; ok { // Is this a metricy metric? if metricMapping.discard { continue } value, ok := metricMapping.conversion(columnData[idx]) if !ok { nonfatalErrors = append(nonfatalErrors, fmt.Errorf("unexpected error parsing namespace: %v, column: %v, index: %v", namespace, columnName, columnData[idx])) continue } // Generate the metric ch <- prometheus.MustNewConstMetric(metricMapping.desc, metricMapping.vtype, value, labelValues...) } } } if err := rows.Err(); err != nil { level.Error(logger).Log("msg", "Failed scaning all rows", "err", err) nonfatalErrors = append(nonfatalErrors, fmt.Errorf("Failed to consume all rows due to: %w", err)) } return nonfatalErrors, nil } func getDB(conn string) (*sql.DB, error) { db, err := sql.Open("postgres", conn) if err != nil { return nil, err } rows, err := db.Query("SHOW STATS") if err != nil { return nil, fmt.Errorf("error pinging pgbouncer: %w", err) } defer rows.Close() db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) return db, nil } // Convert database.sql types to float64s for Prometheus consumption. Null types are mapped to NaN. string and []byte // types are mapped as NaN and !ok func dbToFloat64(t interface{}, factor float64) (float64, bool) { switch v := t.(type) { case int64: return float64(v) * factor, true case float64: return v * factor, true case time.Time: return float64(v.Unix()), true case []byte: // Try and convert to string and then parse to a float64 strV := string(v) result, err := strconv.ParseFloat(strV, 64) if err != nil { return math.NaN(), false } return result * factor, true case string: result, err := strconv.ParseFloat(v, 64) if err != nil { return math.NaN(), false } return result * factor, true case nil: return math.NaN(), true default: return math.NaN(), false } } // Iterate through all the namespace mappings in the exporter and run their queries. func queryNamespaceMappings(ch chan<- prometheus.Metric, db *sql.DB, metricMap map[string]MetricMapNamespace, logger log.Logger) map[string]error { // Return a map of namespace -> errors namespaceErrors := make(map[string]error) for namespace, mapping := range metricMap { level.Debug(logger).Log("msg", "Querying namespace", "namespace", namespace) nonFatalErrors, err := queryNamespaceMapping(ch, db, namespace, mapping, logger) // Serious error - a namespace disappeard if err != nil { namespaceErrors[namespace] = err level.Info(logger).Log("msg", "namespace disappeard", "err", err) } // Non-serious errors - likely version or parsing problems. if len(nonFatalErrors) > 0 { for _, err := range nonFatalErrors { level.Info(logger).Log("msg", "error parsing", "err", err.Error()) } } } return namespaceErrors } // Gather the pgbouncer version info. func queryVersion(ch chan<- prometheus.Metric, db *sql.DB) error { rows, err := db.Query("SHOW VERSION;") if err != nil { return fmt.Errorf("error getting pgbouncer version: %w", err) } defer rows.Close() var columnNames []string columnNames, err = rows.Columns() if err != nil { return fmt.Errorf("error retrieving column list for version: %w", err) } if len(columnNames) != 1 || columnNames[0] != "version" { return errors.New("show version didn't return version column") } var bouncerVersion string for rows.Next() { err := rows.Scan(&bouncerVersion) if err != nil { return err } ch <- prometheus.MustNewConstMetric( bouncerVersionDesc, prometheus.GaugeValue, 1.0, bouncerVersion, ) } return nil } // Describe implements prometheus.Collector. func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // We cannot know in advance what metrics the exporter will generate // from Postgres. So we use the poor man's describe method: Run a collect // and send the descriptors of all the collected metrics. The problem // here is that we need to connect to the Postgres DB. If it is currently // unavailable, the descriptors will be incomplete. Since this is a // stand-alone exporter and not used as a library within other code // implementing additional metrics, the worst that can happen is that we // don't detect inconsistent metrics created by this exporter // itself. Also, a change in the monitored Postgres instance may change the // exported metrics during the runtime of the exporter. metricCh := make(chan prometheus.Metric) doneCh := make(chan struct{}) go func() { for m := range metricCh { ch <- m.Desc() } close(doneCh) }() e.Collect(metricCh) close(metricCh) <-doneCh } // Collect implements prometheus.Collector. func (e *Exporter) Collect(ch chan<- prometheus.Metric) { level.Info(e.logger).Log("msg", "Starting scrape") var up float64 = 1.0 err := queryVersion(ch, e.db) if err != nil { level.Error(e.logger).Log("msg", "error getting version", "err", err) up = 0 } if err = queryShowLists(ch, e.db, e.logger); err != nil { level.Error(e.logger).Log("msg", "error getting SHOW LISTS", "err", err) up = 0 } if err = queryShowConfig(ch, e.db, e.logger); err != nil { level.Error(e.logger).Log("msg", "error getting SHOW CONFIG", "err", err) up = 0 } errMap := queryNamespaceMappings(ch, e.db, e.metricMap, e.logger) if len(errMap) > 0 { level.Error(e.logger).Log("msg", "error querying namespace mappings", "err", errMap) up = 0 } if len(errMap) == len(e.metricMap) { up = 0 } ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, up) } // Turn the MetricMap column mapping into a prometheus descriptor mapping. func makeDescMap(metricMaps map[string]map[string]ColumnMapping, namespace string, logger log.Logger) map[string]MetricMapNamespace { var metricMap = make(map[string]MetricMapNamespace) for metricNamespace, mappings := range metricMaps { thisMap := make(map[string]MetricMap) var labels = make([]string, 0) // First collect all the labels since the metrics will need them for columnName, columnMapping := range mappings { if columnMapping.usage == LABEL { level.Debug(logger).Log("msg", "Adding label", "column_name", columnName, "metric_namespace", metricNamespace) labels = append(labels, columnName) } } for columnName, columnMapping := range mappings { factor := columnMapping.factor // Determine how to convert the column based on its usage. switch columnMapping.usage { case COUNTER: thisMap[columnName] = MetricMap{ vtype: prometheus.CounterValue, desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_%s", namespace, metricNamespace, columnMapping.metric), columnMapping.description, labels, nil), conversion: func(in interface{}) (float64, bool) { return dbToFloat64(in, factor) }, } case GAUGE: thisMap[columnName] = MetricMap{ vtype: prometheus.GaugeValue, desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_%s", namespace, metricNamespace, columnMapping.metric), columnMapping.description, labels, nil), conversion: func(in interface{}) (float64, bool) { return dbToFloat64(in, factor) }, } } } metricMap[metricNamespace] = MetricMapNamespace{thisMap, labels} } return metricMap } pgbouncer_exporter-0.7.0/docker-compose.yml000066400000000000000000000004051444740545700211040ustar00rootroot00000000000000version: '2' services: postgres: image: postgres:9.4 ports: - "5432" pgbouncer: image: starefossen/pgbouncer:latest links: - postgres ports: - "127.0.0.1:6543:6543" volumes: - ./pgbouncer.ini:/pgbouncer.ini pgbouncer_exporter-0.7.0/go.mod000066400000000000000000000026521444740545700165630ustar00rootroot00000000000000module github.com/prometheus-community/pgbouncer_exporter go 1.19 require ( github.com/alecthomas/kingpin/v2 v2.3.2 github.com/go-kit/log v0.2.1 github.com/lib/pq v1.10.9 github.com/prometheus/client_golang v1.14.0 github.com/prometheus/common v0.42.0 github.com/prometheus/exporter-toolkit v0.10.0 ) require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/crypto v0.8.0 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) pgbouncer_exporter-0.7.0/go.sum000066400000000000000000000172231444740545700166100ustar00rootroot00000000000000github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/exporter-toolkit v0.10.0 h1:yOAzZTi4M22ZzVxD+fhy1URTuNRj/36uQJJ5S8IPza8= github.com/prometheus/exporter-toolkit v0.10.0/go.mod h1:+sVFzuvV5JDyw+Ih6p3zFxZNVnKQa3x5qPmDSiPu4ZY= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= pgbouncer_exporter-0.7.0/pgbouncer.ini000066400000000000000000000003171444740545700201360ustar00rootroot00000000000000[databases] postgres = host=postgres dbname=postgres [pgbouncer] user = pgbouncer pool_mode = session listen_port = 6543 listen_addr = 0.0.0.0 auth_type = any ignore_startup_parameters = extra_float_digits pgbouncer_exporter-0.7.0/pgbouncer_exporter.go000066400000000000000000000070321444740545700217150ustar00rootroot00000000000000// Copyright 2020 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "net/http" "os" "github.com/alecthomas/kingpin/v2" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog/flag" "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web/kingpinflag" ) const namespace = "pgbouncer" func main() { const pidFileHelpText = `Path to PgBouncer pid file. If provided, the standard process metrics get exported for the PgBouncer process, prefixed with 'pgbouncer_process_...'. The pgbouncer_process exporter needs to have read access to files owned by the PgBouncer process. Depends on the availability of /proc. https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics.` promlogConfig := &promlog.Config{} flag.AddFlags(kingpin.CommandLine, promlogConfig) var ( connectionStringPointer = kingpin.Flag("pgBouncer.connectionString", "Connection string for accessing pgBouncer.").Default("postgres://postgres:@localhost:6543/pgbouncer?sslmode=disable").String() metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String() pidFilePath = kingpin.Flag("pgBouncer.pid-file", pidFileHelpText).Default("").String() ) toolkitFlags := kingpinflag.AddFlags(kingpin.CommandLine, ":9127") kingpin.Version(version.Print("pgbouncer_exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() logger := promlog.New(promlogConfig) connectionString := *connectionStringPointer exporter := NewExporter(connectionString, namespace, logger) prometheus.MustRegister(exporter) prometheus.MustRegister(version.NewCollector("pgbouncer_exporter")) level.Info(logger).Log("msg", "Starting pgbouncer_exporter", "version", version.Info()) level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) if *pidFilePath != "" { procExporter := collectors.NewProcessCollector( collectors.ProcessCollectorOpts{ PidFn: prometheus.NewPidFileFn(*pidFilePath), Namespace: namespace, }, ) prometheus.MustRegister(procExporter) } http.Handle(*metricsPath, promhttp.Handler()) if *metricsPath != "/" && *metricsPath != "" { landingConfig := web.LandingConfig{ Name: "PgBouncer Exporter", Description: "Prometheus Exporter for PgBouncer servers", Version: version.Info(), Links: []web.LandingLinks{ { Address: *metricsPath, Text: "Metrics", }, }, } landingPage, err := web.NewLandingPage(landingConfig) if err != nil { level.Error(logger).Log("err", err) os.Exit(1) } http.Handle("/", landingPage) } srv := &http.Server{} if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil { level.Error(logger).Log("err", err) os.Exit(1) } } pgbouncer_exporter-0.7.0/scripts/000077500000000000000000000000001444740545700171375ustar00rootroot00000000000000pgbouncer_exporter-0.7.0/scripts/errcheck_excludes.txt000066400000000000000000000002521444740545700233610ustar00rootroot00000000000000// Used in HTTP handlers, any error is handled by the server itself. (net/http.ResponseWriter).Write // Never check for logger errors. (github.com/go-kit/log.Logger).Log pgbouncer_exporter-0.7.0/struct.go000066400000000000000000000057371444740545700173370ustar00rootroot00000000000000// Copyright 2020 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main // Elasticsearch Node Stats Structs import ( "database/sql" "fmt" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" ) type columnUsage int // convert a string to the corresponding columnUsage func stringTocolumnUsage(s string) (u columnUsage, err error) { switch s { case "DISCARD": u = DISCARD case "LABEL": u = LABEL case "COUNTER": u = COUNTER case "GAUGE": u = GAUGE case "MAPPEDMETRIC": u = MAPPEDMETRIC case "DURATION": u = DURATION default: err = fmt.Errorf("wrong columnUsage given : %s", s) } return } // Implements the yaml.Unmarshaller interface func (this *columnUsage) UnmarshalYAML(unmarshal func(interface{}) error) error { var value string if err := unmarshal(&value); err != nil { return err } columnUsage, err := stringTocolumnUsage(value) if err != nil { return err } *this = columnUsage return nil } const ( DISCARD columnUsage = iota // Ignore this column LABEL columnUsage = iota // Use this column as a label COUNTER columnUsage = iota // Use this column as a counter GAUGE columnUsage = iota // Use this column as a gauge MAPPEDMETRIC columnUsage = iota // Use this column with the supplied mapping of text values DURATION columnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds) ) // Groups metric maps under a shared set of labels type MetricMapNamespace struct { columnMappings map[string]MetricMap // Column mappings in this namespace labels []string } // Stores the prometheus metric description which a given column will be mapped // to by the collector type MetricMap struct { discard bool // Should metric be discarded during mapping? vtype prometheus.ValueType // Prometheus valuetype desc *prometheus.Desc // Prometheus descriptor conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64 } type ColumnMapping struct { usage columnUsage `yaml:"usage"` metric string `yaml:"metric"` factor float64 `yaml:"factor"` description string `yaml:"description"` } // Exporter collects PgBouncer stats from the given server and exports // them using the prometheus metrics package. type Exporter struct { metricMap map[string]MetricMapNamespace db *sql.DB logger log.Logger }